blob: 237ed211b44e786acbec0c3f0706618d8ef2feac [file] [log] [blame]
// Copyright 2018 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 "content/browser/idle/idle_manager_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_type.h"
#include "content/public/test/mock_permission_manager.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/test_render_frame_host.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "services/service_manager/public/cpp/bind_source_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/idle/idle_manager.mojom.h"
using blink::mojom::IdleManagerError;
using blink::mojom::IdleMonitorPtr;
using blink::mojom::IdleStatePtr;
using blink::mojom::ScreenIdleState;
using blink::mojom::UserIdleState;
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
using url::Origin;
namespace content {
namespace {
const char kTestUrl[] = "https://www.google.com";
constexpr base::TimeDelta kThreshold = base::TimeDelta::FromSeconds(60);
class MockIdleMonitor : public blink::mojom::IdleMonitor {
public:
MOCK_METHOD1(Update, void(IdleStatePtr));
};
class MockIdleTimeProvider : public IdleManager::IdleTimeProvider {
public:
MockIdleTimeProvider() = default;
~MockIdleTimeProvider() override = default;
MockIdleTimeProvider(const MockIdleTimeProvider&) = delete;
MockIdleTimeProvider& operator=(const MockIdleTimeProvider&) = delete;
MOCK_METHOD0(CalculateIdleTime, base::TimeDelta());
MOCK_METHOD0(CheckIdleStateIsLocked, bool());
};
class IdleManagerTest : public RenderViewHostImplTestHarness {
protected:
IdleManagerTest() = default;
~IdleManagerTest() override = default;
IdleManagerTest(const IdleManagerTest&) = delete;
IdleManagerTest& operator=(const IdleManagerTest&) = delete;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
permission_manager_ = new ::testing::NiceMock<MockPermissionManager>();
static_cast<TestBrowserContext*>(browser_context())
->SetPermissionControllerDelegate(
base::WrapUnique(permission_manager_));
idle_manager_ = std::make_unique<IdleManagerImpl>(browser_context());
}
void TearDown() override {
idle_manager_.reset();
RenderViewHostImplTestHarness::TearDown();
}
IdleManagerImpl* GetIdleManager() { return idle_manager_.get(); }
void SetPermissionStatus(const GURL& origin,
blink::mojom::PermissionStatus permission_status) {
ON_CALL(*permission_manager_,
GetPermissionStatus(PermissionType::NOTIFICATIONS, origin, origin))
.WillByDefault(Return(permission_status));
}
Origin origin() const { return Origin::Create(url_); }
const GURL& url() const { return url_; }
private:
std::unique_ptr<IdleManagerImpl> idle_manager_;
MockPermissionManager* permission_manager_;
GURL url_ = GURL(kTestUrl);
};
} // namespace
TEST_F(IdleManagerTest, AddMonitor) {
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
mojo::Remote<blink::mojom::IdleManager> service_remote;
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
base::RunLoop loop;
service_remote.set_disconnect_handler(base::BindLambdaForTesting([&]() {
ADD_FAILURE() << "Unexpected connection error";
loop.Quit();
}));
// Initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(0)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindOnce(
[](base::OnceClosure callback, IdleManagerError error,
IdleStatePtr state) {
// The initial state of the status of the user is to be active.
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(UserIdleState::kActive, state->user);
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
std::move(callback).Run();
},
loop.QuitClosure()));
loop.Run();
}
// Disabled test: https://crbug.com/1062668
TEST_F(IdleManagerTest, DISABLED_Idle) {
mojo::Remote<blink::mojom::IdleManager> service_remote;
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(0)));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(UserIdleState::kActive, state->user);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user going idle.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(60)));
// Expects Update to be notified about the change to idle.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(UserIdleState::kIdle, state->user);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user going active, calling a callback under the threshold.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(0)));
// Expects Update to be notified about the change to active.
// auto quit = loop.QuitClosure();
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(UserIdleState::kActive, state->user);
// Ends the test.
loop.Quit();
}));
loop.Run();
}
}
// TODO(crbug.com/1069706): Test is flaky on all platforms except Mac.
#if defined(OS_MACOSX)
#define MAYBE_UnlockingScreen UnlockingScreen
#else
#define MAYBE_UnlockingScreen DISABLED_UnlockingScreen
#endif
TEST_F(IdleManagerTest, MAYBE_UnlockingScreen) {
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
mojo::Remote<blink::mojom::IdleManager> service_remote;
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(true));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(ScreenIdleState::kLocked, state->screen);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user unlocking the screen.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
// Expects Update to be notified about the change to unlocked.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
loop.Quit();
}));
loop.Run();
}
}
// Disabled test: https://crbug.com/1062668
TEST_F(IdleManagerTest, DISABLED_LockingScreen) {
mojo::Remote<blink::mojom::IdleManager> service_remote;
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user locking the screen.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(true));
// Expects Update to be notified about the change to unlocked.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(ScreenIdleState::kLocked, state->screen);
loop.Quit();
}));
loop.Run();
}
}
// Disabled test: https://crbug.com/1062668
TEST_F(IdleManagerTest, DISABLED_LockingScreenThenIdle) {
mojo::Remote<blink::mojom::IdleManager> service_remote;
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(UserIdleState::kActive, state->user);
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user locking screen.
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(true));
// Expects Update to be notified about the change to locked.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(ScreenIdleState::kLocked, state->screen);
EXPECT_EQ(UserIdleState::kActive, state->user);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user going idle, whilte the screen is still locked.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(60)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(true));
// Expects Update to be notified about the change to active.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(UserIdleState::kIdle, state->user);
EXPECT_EQ(ScreenIdleState::kLocked, state->screen);
// Ends the test.
loop.Quit();
}));
loop.Run();
}
}
// Disabled test: https://crbug.com/1062668
TEST_F(IdleManagerTest, DISABLED_LockingScreenAfterIdle) {
mojo::Remote<blink::mojom::IdleManager> service_remote;
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(0)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(UserIdleState::kActive, state->user);
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates a user going idle, but with the screen still unlocked.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(60)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
// Expects Update to be notified about the change to idle.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(UserIdleState::kIdle, state->user);
EXPECT_EQ(ScreenIdleState::kUnlocked, state->screen);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
// Simulates the screeng getting locked by the system after the user goes
// idle (e.g. screensaver kicks in first, throwing idleness, then getting
// locked).
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(60)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(true));
// Expects Update to be notified about the change to locked.
EXPECT_CALL(monitor, Update(_)).WillOnce(Invoke([&](IdleStatePtr state) {
EXPECT_EQ(ScreenIdleState::kLocked, state->screen);
EXPECT_EQ(UserIdleState::kIdle, state->user);
// Ends the test.
loop.Quit();
}));
loop.Run();
}
}
TEST_F(IdleManagerTest, RemoveMonitorStopsPolling) {
// Simulates the renderer disconnecting (e.g. on page reload) and verifies
// that the polling stops for the idle detection.
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
mojo::Remote<blink::mojom::IdleManager> service_remote;
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
{
base::RunLoop loop;
service_remote->AddMonitor(
kThreshold, monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) { loop.Quit(); }));
loop.Run();
}
EXPECT_TRUE(impl->IsPollingForTest());
{
base::RunLoop loop;
// Simulates the renderer disconnecting.
monitor_receiver.reset();
// Wait for the IdleManager to observe the pipe close.
loop.RunUntilIdle();
}
EXPECT_FALSE(impl->IsPollingForTest());
}
TEST_F(IdleManagerTest, Threshold) {
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
mojo::Remote<blink::mojom::IdleManager> service_remote;
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
base::RunLoop loop;
// Initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime())
.WillRepeatedly(Return(base::TimeDelta::FromSeconds(91)));
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).WillRepeatedly(Return(false));
service_remote->AddMonitor(
base::TimeDelta::FromSeconds(90),
monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting(
[&](IdleManagerError error, IdleStatePtr state) {
EXPECT_EQ(IdleManagerError::kSuccess, error);
EXPECT_EQ(UserIdleState::kIdle, state->user);
loop.Quit();
}));
loop.Run();
}
TEST_F(IdleManagerTest, InvalidThreshold) {
SetPermissionStatus(url(), blink::mojom::PermissionStatus::GRANTED);
mojo::test::BadMessageObserver bad_message_observer;
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
mojo::Remote<blink::mojom::IdleManager> service_remote;
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
// Should not start initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime()).Times(0);
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).Times(0);
service_remote->AddMonitor(base::TimeDelta::FromSeconds(50),
monitor_receiver.BindNewPipeAndPassRemote(),
base::NullCallback());
EXPECT_EQ("Minimum threshold is 60 seconds.",
bad_message_observer.WaitForBadMessage());
}
TEST_F(IdleManagerTest, NotificationPermissionDisabled) {
SetPermissionStatus(url(), blink::mojom::PermissionStatus::DENIED);
auto* impl = GetIdleManager();
auto* mock = new NiceMock<MockIdleTimeProvider>();
impl->SetIdleTimeProviderForTest(base::WrapUnique(mock));
mojo::Remote<blink::mojom::IdleManager> service_remote;
impl->CreateService(service_remote.BindNewPipeAndPassReceiver(), origin());
MockIdleMonitor monitor;
mojo::Receiver<blink::mojom::IdleMonitor> monitor_receiver(&monitor);
// Should not start initial state of the system.
EXPECT_CALL(*mock, CalculateIdleTime()).Times(0);
EXPECT_CALL(*mock, CheckIdleStateIsLocked()).Times(0);
base::RunLoop loop;
service_remote->AddMonitor(
base::TimeDelta::FromSeconds(90),
monitor_receiver.BindNewPipeAndPassRemote(),
base::BindLambdaForTesting([&](IdleManagerError error,
IdleStatePtr state) {
EXPECT_EQ(blink::mojom::IdleManagerError::kPermissionDisabled, error);
EXPECT_FALSE(state);
loop.Quit();
}));
loop.Run();
}
} // namespace content