blob: df9c3a820aa8af86e140e771b7771ca192ad0528 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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 "extensions/browser/api/alarms/alarms_api.h"
#include <stddef.h>
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/simple_test_clock.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/fake_local_frame.h"
#include "extensions/browser/api/alarms/alarm_manager.h"
#include "extensions/browser/api/alarms/alarms_api_constants.h"
#include "extensions/browser/api_unittest.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
typedef extensions::api::alarms::Alarm JsAlarm;
namespace extensions {
namespace {
// Test delegate which quits the message loop when an alarm fires.
class AlarmDelegate : public AlarmManager::Delegate {
public:
~AlarmDelegate() override {}
void OnAlarm(const ExtensionId& extension_id, const Alarm& alarm) override {
alarms_seen.push_back(alarm.js_alarm->name);
if (!quit_closure_.is_null()) {
std::move(quit_closure_).Run();
}
}
void WaitForAlarm() {
base::RunLoop loop;
quit_closure_ = loop.QuitClosure();
loop.Run();
}
std::vector<std::string> alarms_seen;
base::OnceClosure quit_closure_;
};
} // namespace
void RunScheduleNextPoll(AlarmManager* alarm_manager) {
alarm_manager->ScheduleNextPoll();
}
class ExtensionAlarmsTest : public ApiUnitTest {
public:
using ApiUnitTest::RunFunction;
void SetUp() override {
ApiUnitTest::SetUp();
alarm_manager_ = AlarmManager::Get(browser_context());
alarm_manager_->SetClockForTesting(&test_clock_);
auto delegate = std::make_unique<AlarmDelegate>();
alarm_delegate_ = delegate.get();
alarm_manager_->set_delegate(std::move(delegate));
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
}
void TearDown() override {
// Drop unowned references before superclass destroys them.
alarm_delegate_ = nullptr;
alarm_manager_ = nullptr;
ApiUnitTest::TearDown();
}
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<JsAlarm> ToAlarmList(const std::optional<base::Value>& value) {
std::vector<JsAlarm> list;
if (!value) {
return list;
}
for (const auto& item : value->GetList()) {
auto alarm = JsAlarm::FromValue(item);
if (!alarm) {
ADD_FAILURE() << "Failed to parse JsAlarm." << item;
return list;
}
list.push_back(std::move(alarm).value());
}
return list;
}
// Creates up to 3 alarms using the extension API.
void CreateAlarms(size_t num_alarms) {
CHECK_LE(num_alarms, 3U);
static constexpr std::array kCreateArgs = {
"[null, {\"periodInMinutes\": 0.001}]",
"[\"7\", {\"periodInMinutes\": 7}]",
"[\"0\", {\"delayInMinutes\": 0}]",
};
for (size_t i = 0; i < num_alarms; ++i) {
std::optional<base::Value> result = RunFunctionAndReturnValue(
new AlarmsCreateFunction(&test_clock_), kCreateArgs[i]);
EXPECT_FALSE(result);
}
}
base::SimpleTestClock test_clock_;
raw_ptr<AlarmManager> alarm_manager_;
raw_ptr<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);
// Now wait for the alarm to fire. Our test delegate will quit the
// MessageLoop when that happens.
test->alarm_delegate_->WaitForAlarm();
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::BindOnce(ExtensionAlarmsTestGetAllAlarmsCallback));
}
TEST_F(ExtensionAlarmsTest, Create) {
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
// Create 1 non-repeating alarm.
CreateAlarm("[null, {\"delayInMinutes\": 0}]");
alarm_manager_->GetAlarm(
extension()->id(), std::string(),
base::BindOnce(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::Eq(0.001));
test->test_clock_.Advance(base::Seconds(1));
// Now wait for the alarm to fire. Our test delegate will quit the
// MessageLoop when that happens.
test->alarm_delegate_->WaitForAlarm();
test->test_clock_.Advance(base::Seconds(1));
// Wait again, and ensure the alarm fires again.
RunScheduleNextPoll(test->alarm_manager_);
test->alarm_delegate_->WaitForAlarm();
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::FromSecondsSinceUnixEpoch(10));
// Create 1 repeating alarm.
CreateAlarm("[null, {\"periodInMinutes\": 0.001}]");
alarm_manager_->GetAlarm(
extension()->id(), std::string(),
base::BindOnce(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_FALSE(alarm->js_alarm->period_in_minutes.has_value());
test->test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10.1));
// Now wait for the alarm to fire. Our test delegate will quit the
// MessageLoop when that happens.
test->alarm_delegate_->WaitForAlarm();
test->alarm_manager_->GetAlarm(
test->extension()->id(), std::string(),
base::BindOnce(ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback, test));
}
TEST_F(ExtensionAlarmsTest, CreateAbsolute) {
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(9.99));
CreateAlarm("[null, {\"when\": 10001}]");
alarm_manager_->GetAlarm(
extension()->id(), std::string(),
base::BindOnce(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::FromSecondsSinceUnixEpoch(10.7));
test->alarm_delegate_->WaitForAlarm();
test->alarm_manager_->GetAlarm(
test->extension()->id(), std::string(),
base::BindOnce(
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::Eq(0.001));
test->test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10.1));
// Now wait for the alarm to fire. Our test delegate will quit the
// MessageLoop when that happens.
test->alarm_delegate_->WaitForAlarm();
test->alarm_manager_->GetAlarm(
test->extension()->id(), std::string(),
base::BindOnce(
ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback,
test));
}
TEST_F(ExtensionAlarmsTest, CreateRepeatingWithQuickFirstCall) {
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(9.99));
CreateAlarm("[null, {\"when\": 10001, \"periodInMinutes\": 0.001}]");
alarm_manager_->GetAlarm(
extension()->id(), std::string(),
base::BindOnce(
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::FromSecondsSinceUnixEpoch(10));
// Create 2 duplicate alarms. The first should be overridden.
CreateAlarm("[\"dup\", {\"delayInMinutes\": 1}]");
CreateAlarm("[\"dup\", {\"delayInMinutes\": 7}]");
alarm_manager_->GetAllAlarms(
extension()->id(),
base::BindOnce(ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback));
}
class ConsoleLogMessageLocalFrame : public content::FakeLocalFrame {
public:
void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
const std::string& message,
bool discard_duplicates) override {
message_count_++;
last_level_ = level;
last_message_ = message;
}
unsigned message_count() const { return message_count_; }
const std::string& last_message() const { return last_message_; }
blink::mojom::ConsoleMessageLevel last_level() const {
return last_level_.value();
}
private:
unsigned message_count_ = 0;
std::optional<blink::mojom::ConsoleMessageLevel> last_level_;
std::string last_message_;
};
class ExtensionAlarmsLogTest : public ExtensionAlarmsTest {
void SetUp() override {
ExtensionAlarmsTest::SetUp();
// Make sure there's a RenderViewHost for alarms to warn into.
CreateExtensionPage();
}
};
TEST_F(ExtensionAlarmsLogTest, CreateDelayBelowMinimum) {
// Create an alarm with delay below the minimum accepted value.
ConsoleLogMessageLocalFrame local_frame;
local_frame.Init(
contents()->GetPrimaryMainFrame()->GetRemoteAssociatedInterfaces());
base::RunLoop().RunUntilIdle();
ASSERT_EQ(local_frame.message_count(), 0u);
CreateAlarm("[\"negative\", {\"delayInMinutes\": -0.2}]");
base::RunLoop().RunUntilIdle();
ASSERT_EQ(local_frame.message_count(), 1u);
EXPECT_EQ(blink::mojom::ConsoleMessageLevel::kWarning,
local_frame.last_level());
EXPECT_THAT(local_frame.last_message(),
testing::HasSubstr(
"delay is less than the minimum duration of 30 seconds"));
}
TEST_F(ExtensionAlarmsTest, Get) {
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(4));
// Create 2 alarms, and make sure we can query them.
CreateAlarms(2);
// Get the default one.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsGetFunction(), "[null]");
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_dict());
auto alarm = JsAlarm::FromValue(result->GetDict());
EXPECT_TRUE(alarm);
EXPECT_EQ("", alarm->name);
EXPECT_DOUBLE_EQ(4060, alarm->scheduled_time);
EXPECT_THAT(alarm->period_in_minutes, testing::Eq(0.001));
}
// Get "7".
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsGetFunction(), "[\"7\"]");
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_dict());
auto alarm = JsAlarm::FromValue(result->GetDict());
EXPECT_TRUE(alarm);
EXPECT_EQ("7", alarm->name);
EXPECT_EQ(424000, alarm->scheduled_time);
EXPECT_THAT(alarm->period_in_minutes, testing::Eq(7));
}
// Get a non-existent one.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsGetFunction(), "[\"nobody\"]");
ASSERT_FALSE(result);
}
}
TEST_F(ExtensionAlarmsTest, GetAll) {
// Test getAll with 0 alarms.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsGetAllFunction(), "[]");
std::vector<JsAlarm> alarms = ToAlarmList(result);
EXPECT_EQ(0u, alarms.size());
}
// Create 2 alarms, and make sure we can query them.
CreateAlarms(2);
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsGetAllFunction(), "[null]");
std::vector<JsAlarm> alarms = ToAlarmList(result);
EXPECT_EQ(2u, alarms.size());
// Test the "7" alarm.
JsAlarm* alarm = &alarms[0];
if (alarm->name != "7") {
alarm = &alarms[1];
}
EXPECT_EQ("7", alarm->name);
EXPECT_THAT(alarm->period_in_minutes, testing::Eq(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::Eq(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::Eq(0.001));
// Now wait for the alarms to fire, and ensure the cancelled alarms don't
// fire.
test->test_clock_.Advance(base::Milliseconds(60));
RunScheduleNextPoll(test->alarm_manager_);
test->alarm_delegate_->WaitForAlarm();
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::BindOnce(ExtensionAlarmsTestClearGetAllAlarms2Callback));
}
TEST_F(ExtensionAlarmsTest, Clear) {
// Clear a non-existent one.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"nobody\"]");
ASSERT_TRUE(result->is_bool());
EXPECT_FALSE(result->GetBool());
}
// Create 3 alarms.
CreateAlarms(3);
// Clear all but the 0.001-minute alarm.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"7\"]");
ASSERT_TRUE(result->is_bool());
EXPECT_TRUE(result->GetBool());
}
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"0\"]");
ASSERT_TRUE(result->is_bool());
EXPECT_TRUE(result->GetBool());
}
alarm_manager_->GetAllAlarms(
extension()->id(),
base::BindOnce(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::BindOnce(ExtensionAlarmsTestClearAllGetAllAlarms2Callback));
}
TEST_F(ExtensionAlarmsTest, ClearAll) {
// ClearAll with no alarms set.
{
std::optional<base::Value> result =
RunFunctionAndReturnValue(new AlarmsClearAllFunction(), "[]");
ASSERT_TRUE(result->is_bool());
EXPECT_TRUE(result->GetBool());
}
// Create 3 alarms.
CreateAlarms(3);
alarm_manager_->GetAllAlarms(
extension()->id(),
base::BindOnce(ExtensionAlarmsTestClearAllGetAllAlarms1Callback, this));
}
class ExtensionAlarmsSchedulingTest : public ExtensionAlarmsTest {
void GetAlarmCallback(Alarm* alarm) {
CHECK(alarm);
const base::Time scheduled_time =
base::Time::FromMillisecondsSinceUnixEpoch(
alarm->js_alarm->scheduled_time);
EXPECT_EQ(scheduled_time, alarm_manager_->next_poll_time_);
}
static void RemoveAlarmCallback(bool found) { EXPECT_TRUE(found); }
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::BindOnce(&ExtensionAlarmsSchedulingTest::GetAlarmCallback,
base::Unretained(this)));
}
void RemoveAlarm(const std::string& name) {
alarm_manager_->RemoveAlarm(
extension()->id(), name,
base::BindOnce(&ExtensionAlarmsSchedulingTest::RemoveAlarmCallback));
}
void RemoveAllAlarms() {
alarm_manager_->RemoveAllAlarms(
extension()->id(),
base::BindOnce(
&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::FromSecondsSinceUnixEpoch(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 = 30;
alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm));
VerifyScheduledTime("a");
RemoveAllAlarms();
}
{
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(3 * 60 + 1));
Alarm alarm;
alarm.js_alarm->name = "bb";
alarm.js_alarm->scheduled_time = 3 * 60000;
alarm.js_alarm->period_in_minutes = 3;
alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm));
alarm_delegate_->WaitForAlarm();
EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3 * 60) + base::Minutes(3),
alarm_manager_->next_poll_time_);
RemoveAllAlarms();
}
{
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(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 = 4;
alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm2));
Alarm alarm3;
alarm3.js_alarm->name = "ccc";
alarm3.js_alarm->scheduled_time = 25 * 60000;
alarm3.js_alarm->period_in_minutes = 25;
alarm_manager_->AddAlarmImpl(extension()->id(), std::move(alarm3));
alarm_delegate_->WaitForAlarm();
EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4 * 60) + base::Minutes(4),
alarm_manager_->next_poll_time_);
RemoveAllAlarms();
}
}
TEST_F(ExtensionAlarmsSchedulingTest, ReleasedExtensionPollsInfrequently) {
set_extension(ExtensionBuilder("Test")
.SetLocation(mojom::ManifestLocation::kInternal)
.Build());
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(300));
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_.InMillisecondsFSinceUnixEpoch());
alarm_manager_->last_poll_time_ = base::Time::FromSecondsSinceUnixEpoch(290);
// In released extensions, we set the granularity to at least 30
// seconds, which makes AddAlarm schedule the next poll after the
// extension requested.
alarm_manager_->ScheduleNextPoll();
EXPECT_DOUBLE_EQ(
(alarm_manager_->last_poll_time_ + base::Seconds(30))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}
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::Milliseconds(60));
alarm_delegate_->WaitForAlarm();
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(ExtensionBuilder("Test")
.SetLocation(mojom::ManifestLocation::kInternal)
.Build());
test_clock_.SetNow(base::Time::UnixEpoch());
CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
test_clock_.Advance(base::Seconds(1));
CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
test_clock_.Advance(base::Minutes(2));
alarm_manager_->last_poll_time_ =
base::Time::FromSecondsSinceUnixEpoch(2 * 60);
// In released extensions, we set the granularity to at least 30
// seconds, which makes scheduler set it to 30 seconds, rather than
// 1 second later (when b is supposed to go off).
alarm_manager_->ScheduleNextPoll();
EXPECT_DOUBLE_EQ(
(alarm_manager_->last_poll_time_ + base::Seconds(30))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}
TEST_F(ExtensionAlarmsSchedulingTest, DifferentMinimumGranularities) {
test_clock_.SetNow(base::Time::UnixEpoch());
// 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 30
// seconds. CreateAlarm() uses extension_, so keep a ref of the old one
// around, and repopulate extension_.
scoped_refptr<const Extension> extension2(extension_ref());
set_extension(ExtensionBuilder("Test")
.SetLocation(mojom::ManifestLocation::kInternal)
.Build());
CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
alarm_manager_->last_poll_time_ = base::Time::UnixEpoch();
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::Seconds(12))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}
void FrequencyTestGetAlarmsCallback(ExtensionAlarmsTest* test, Alarm* alarm) {
ASSERT_TRUE(alarm);
EXPECT_EQ("hello", alarm->js_alarm->name);
EXPECT_DOUBLE_EQ(10000, alarm->js_alarm->scheduled_time);
EXPECT_THAT(alarm->js_alarm->period_in_minutes, testing::Eq(0.0001));
test->test_clock_.Advance(base::Milliseconds(10));
// Now wait for the alarm to fire. Our test delegate will quit the
// MessageLoop when that happens.
test->alarm_delegate_->WaitForAlarm();
}
// Tests that alarms with very small period written to storage are also
// subjected to minimum polling interval.
// Regression test for https://crbug.com/618540.
TEST_F(ExtensionAlarmsSchedulingTest, PollFrequencyFromStoredAlarm) {
static constexpr struct {
bool is_unpacked;
int manifest_version;
base::TimeDelta delay_minimum;
} test_data[] = {
{true, 2, alarms_api_constants::kDevDelayMinimum},
{true, 3, alarms_api_constants::kDevDelayMinimum},
{false, 2, alarms_api_constants::kMV2ReleaseDelayMinimum},
{false, 3, alarms_api_constants::kMV3ReleaseDelayMinimum},
{false, 4, alarms_api_constants::kMV3ReleaseDelayMinimum},
};
// Test once for unpacked and once for crx extension.
for (const auto& entry : test_data) {
test_clock_.SetNow(base::Time::FromSecondsSinceUnixEpoch(10));
// Mimic retrieving an alarm from StateStore.
std::string alarm_args =
"[{\"name\": \"hello\", \"scheduledTime\": 10000, "
"\"periodInMinutes\": 0.0001}]";
base::TimeDelta min_delay = alarms_api_constants::GetMinimumDelay(
entry.is_unpacked, entry.manifest_version);
alarm_manager_->ReadFromStorage(extension()->id(), min_delay,
base::test::ParseJson(alarm_args));
// Let the alarm fire once, we will verify the next polling time afterwards.
alarm_manager_->GetAlarm(
extension()->id(), "hello",
base::BindOnce(FrequencyTestGetAlarmsCallback, this));
// The stored alarm's "periodInMinutes" is much smaller than allowed minimum
// in this test (alarms_api_constants::kDevDelayMinimum or
// alarms_api_constants::kReleaseDelayMinimum). Make sure
// our next poll time corresponds to our allowed minimum and not to the
// StateStore specified "periodInMinutes".
base::Time expected_poll_time =
// 10s initial clock.
base::Time::FromSecondsSinceUnixEpoch(10) +
// 10ms in FrequencyTestGetAlarmsCallback.
base::Milliseconds(10) + entry.delay_minimum;
// The alarm should not trigger before our expected poll time...
EXPECT_GE(alarm_manager_->next_poll_time_, expected_poll_time);
// And should trigger within a few seconds of it (to account for test
// differences).
EXPECT_LT(alarm_manager_->next_poll_time_,
expected_poll_time + base::Seconds(10));
RemoveAlarm("hello");
}
}
// 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::UnixEpoch());
CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
alarm_manager_->last_poll_time_ = base::Time::UnixEpoch();
alarm_manager_->ScheduleNextPoll();
// We expect the first poll to happen two minutes from the start.
EXPECT_DOUBLE_EQ(
(alarm_manager_->last_poll_time_ + base::Seconds(120))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
// Poll more than two minutes later.
test_clock_.Advance(base::Seconds(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::Seconds(115))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
// Completely miss a scheduled trigger.
test_clock_.Advance(base::Seconds(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::Seconds(100))
.InMillisecondsFSinceUnixEpoch(),
alarm_manager_->next_poll_time_.InMillisecondsFSinceUnixEpoch());
}
} // namespace extensions