blob: 65ae6a3f71ad688138e2a86e75198282e77d5387 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/idle/dialog_manager.h"
#include "chrome/browser/enterprise/idle/idle_service.h"
#include "chrome/browser/policy/profile_policy_connector_builder.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/idle_bubble.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/browser/ui/profiles/profile_ui_test_utils.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/testing_profile.h"
#include "components/enterprise/idle/idle_pref_names.h"
#include "components/enterprise/idle/metrics.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/idle/idle_polling_service.h"
#include "ui/base/idle/idle_time_provider.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/base/test/idle_test_utils.h"
using base::TestMockTimeTaskRunner;
using testing::_;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Not;
using testing::Return;
namespace {
class MockIdleTimeProvider : public ui::IdleTimeProvider {
public:
MockIdleTimeProvider() = default;
MockIdleTimeProvider(const MockIdleTimeProvider&) = delete;
MockIdleTimeProvider& operator=(const MockIdleTimeProvider&) = delete;
~MockIdleTimeProvider() override = default;
MOCK_METHOD0(CalculateIdleTime, base::TimeDelta());
MOCK_METHOD0(CheckIdleStateIsLocked, bool());
};
class BrowserCloseWaiter : public BrowserListObserver {
public:
explicit BrowserCloseWaiter(
std::set<raw_ptr<Browser, SetExperimental>> browsers) {
BrowserList::AddObserver(this);
waiting_browsers_ = std::move(browsers);
}
// BrowserListObserver:
void OnBrowserRemoved(Browser* browser) override {
waiting_browsers_.erase(browser);
if (waiting_browsers_.size() == 0) {
BrowserList::RemoveObserver(this);
run_loop_.QuitWhenIdle();
}
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
std::set<raw_ptr<Browser, SetExperimental>> waiting_browsers_;
};
} // namespace
namespace enterprise_idle {
class IdleServiceTest : public InProcessBrowserTest {
public:
IdleServiceTest() = default;
~IdleServiceTest() override = default;
void SetUp() override {
// Prevent user education from polling idle state.
UserEducationServiceFactory::GetInstance()
->disable_idle_polling_for_testing();
InProcessBrowserTest::SetUp();
}
void SetUpInProcessBrowserTestFixture() override {
task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
polling_service().SetTaskRunnerForTest(task_runner_);
polling_service().SetPollIntervalForTest(base::Seconds(1));
auto time_provider =
std::make_unique<testing::NiceMock<MockIdleTimeProvider>>();
idle_time_provider_ = time_provider.get();
scoped_idle_provider_ =
std::make_unique<ui::test::ScopedIdleProviderForTest>(
std::move(time_provider));
ON_CALL(idle_time_provider(), CheckIdleStateIsLocked())
.WillByDefault(Return(false));
for (auto& provider : policy_providers_) {
policy::PushProfilePolicyConnectorProviderForTesting(&provider);
}
keep_alive_ = std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::BROWSER, KeepAliveRestartOption::DISABLED);
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
}
void TearDownOnMainThread() override {
policy::PolicyMap policies;
for (auto& provider : policy_providers_) {
provider.UpdateChromePolicy(policies);
}
ASSERT_FALSE(polling_service().IsPollingForTest());
polling_service().SetTaskRunnerForTest(
base::SingleThreadTaskRunner::GetCurrentDefault());
// If there are no active browsers, BrowserProcessImpl::Unpin() runs too
// early and interrupts test teardown. `keep_alive_` solves this problem.
if (chrome::GetTotalBrowserCount() > 0) {
// It's safe to release this keepalive here, because browser windows are
// already doing the same thing.
keep_alive_.reset();
}
if (ProfilePicker::IsOpen()) {
// `ProfilePicker` prevents browsers from being closed. We need to destroy
// `ProfilePicker` first, and then tear down browsers.
ProfilePicker::Hide();
}
InProcessBrowserTest::TearDownOnMainThread();
}
void TearDownInProcessBrowserTestFixture() override {
keep_alive_.reset();
InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
}
void SetIdleTimeoutPolicies(
policy::MockConfigurationPolicyProvider& policy_provider,
int idle_timeout,
const std::vector<std::string>& idle_timeout_actions = {
"close_browsers", "show_profile_picker"}) {
base::Value::List actions_list;
for (const std::string& action : idle_timeout_actions) {
actions_list.Append(action);
}
policy::PolicyMap policies;
policies.Set(policy::key::kIdleTimeout, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_PLATFORM,
base::Value(idle_timeout), nullptr);
policies.Set(policy::key::kIdleTimeoutActions,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_PLATFORM,
base::Value(std::move(actions_list)), nullptr);
policy_provider.UpdateChromePolicy(policies);
}
policy::MockConfigurationPolicyProvider& policy_provider(size_t index) {
return policy_providers_[index];
}
MockIdleTimeProvider& idle_time_provider() { return *idle_time_provider_; }
ui::IdlePollingService& polling_service() {
return *ui::IdlePollingService::GetInstance();
}
base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
int GetBrowserCount(Profile* profile) {
int count = 0;
for (Browser* browser : *BrowserList::GetInstance()) {
if (browser->profile() == profile) {
count++;
}
}
return count;
}
bool IsDialogOpen() const {
return enterprise_idle::DialogManager::GetInstance()
->IsDialogOpenForTesting();
}
void ActivateBrowser(Browser* browser) {
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_WAYLAND)
// TODO(nicolaso): BrowserActivationWaiter times out on Wayland. Figure out
// why.
#else
ActivateBrowserImpl(browser);
#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_WAYLAND)
}
void ActivateBrowserImpl(Browser* browser) {
if (GetIdleBubble(browser)) {
return;
}
CHECK(browser);
ui_test_utils::BrowserActivationWaiter waiter(browser);
browser->window()->Activate();
waiter.WaitForActivation();
}
private:
testing::NiceMock<policy::MockConfigurationPolicyProvider>
policy_providers_[2];
raw_ptr<MockIdleTimeProvider, AcrossTasksDanglingUntriaged>
idle_time_provider_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
std::unique_ptr<ui::test::ScopedIdleProviderForTest> scoped_idle_provider_;
std::unique_ptr<ScopedKeepAlive> keep_alive_;
};
IN_PROC_BROWSER_TEST_F(IdleServiceTest, Basic) {
// Set the IdleTimeout policy to 0 minute, which should round up
// to 1 minute (the minimum).
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
std::unique_ptr<base::HistogramTester> histogram_tester =
std::make_unique<base::HistogramTester>();
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/0);
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 59s, does nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(59)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show the dialog.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
BrowserCloseWaiter waiter({browser()});
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_TRUE(ProfilePicker::IsOpen());
// Check that the idle dialog events are recorded in the histogram.
EXPECT_THAT(
histogram_tester->GetAllSamples(
"Enterprise.IdleTimeoutPolicies.IdleTimeoutDialogEvent"),
ElementsAre(
base::Bucket(metrics::IdleTimeoutDialogEvent::kDialogShown, 1),
base::Bucket(metrics::IdleTimeoutDialogEvent::kDialogExpired, 1)));
// Check that the success of idle timeout actions is recorded.
histogram_tester->ExpectUniqueSample(
"Enterprise.IdleTimeoutPolicies.Success.ShowProfilePicker", true, 1);
histogram_tester->ExpectUniqueSample(
"Enterprise.IdleTimeoutPolicies.Success.CloseBrowsers", true, 1);
histogram_tester->ExpectUniqueSample(
"Enterprise.IdleTimeoutPolicies.Success.AllActions", true, 1);
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, DidNotClose) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(59)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1);
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. The user dismisses the dialog though, so we do
// nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(61)));
DialogManager::GetInstance()->DismissDialogForTesting();
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, TenMinutes) {
// Set the IdleTimeout policy to 10 minutes.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(599)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/10);
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 599s, does nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(599)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 600s, threshold is reached. Close browsers, then show the Profile Picker.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(600)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(615)));
BrowserCloseWaiter waiter({browser()});
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_TRUE(ProfilePicker::IsOpen());
}
// TODO(crbug.com/40853067): Test flaky on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MultiProfile DISABLED_MultiProfile
#else
#define MAYBE_MultiProfile MultiProfile
#endif
IN_PROC_BROWSER_TEST_F(IdleServiceTest, MAYBE_MultiProfile) {
// `profile` has the IdleTimeout policy set to 5 minutes.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(299)));
Profile* profile = browser()->profile();
Browser* browser2 = CreateBrowser(profile);
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/5);
// `profile2` has the policy set to 5 minutes, so it will close at the same
// time as `profile`.
Profile* profile2;
{
base::ScopedAllowBlockingForTesting allow_blocking;
profile2 = g_browser_process->profile_manager()->GetProfile(
g_browser_process->profile_manager()->user_data_dir().AppendASCII(
"Profile 2"));
SetIdleTimeoutPolicies(policy_provider(1), /*idle_timeout=*/5);
}
Browser* browser3 = CreateBrowser(profile2);
// `profile3` doesn't have the IdleTimeout policy set, so it will
// never close.
Profile* profile3;
{
base::ScopedAllowBlockingForTesting allow_blocking;
profile3 = g_browser_process->profile_manager()->GetProfile(
g_browser_process->profile_manager()->user_data_dir().AppendASCII(
"Profile 3"));
}
std::ignore = CreateBrowser(profile3);
ActivateBrowser(browser());
EXPECT_EQ(2, GetBrowserCount(profile));
EXPECT_EQ(1, GetBrowserCount(profile2));
EXPECT_EQ(1, GetBrowserCount(profile3));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 299s, does nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(299)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(2, GetBrowserCount(profile));
EXPECT_EQ(1, GetBrowserCount(profile2));
EXPECT_EQ(1, GetBrowserCount(profile3));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 300s, threshold is reached. Close browsers, then show the Profile Picker.
{
BrowserCloseWaiter waiter({browser(), browser2, browser3});
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(300)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(315)));
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
}
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_EQ(0, GetBrowserCount(profile2));
EXPECT_EQ(1, GetBrowserCount(profile3));
EXPECT_FALSE(IsDialogOpen());
EXPECT_TRUE(ProfilePicker::IsOpen());
}
// TODO(crbug.com/40064501): Flaky on MacOS
#if BUILDFLAG(IS_MAC)
#define MAYBE_MultiProfileWithDifferentThresholds \
DISABLED_MultiProfileWithDifferentThresholds
#else
#define MAYBE_MultiProfileWithDifferentThresholds \
MultiProfileWithDifferentThresholds
#endif
IN_PROC_BROWSER_TEST_F(IdleServiceTest,
MAYBE_MultiProfileWithDifferentThresholds) {
// `profile` has the IdleTimeout policy set to 5 minutes.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(299)));
Profile* profile = browser()->profile();
Browser* browser2 = CreateBrowser(profile);
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/5);
// `profile2` has the policy set to 6 minutes, so it will close one minute
// *after* `profile`.
Profile* profile2;
{
base::ScopedAllowBlockingForTesting allow_blocking;
profile2 = g_browser_process->profile_manager()->GetProfile(
g_browser_process->profile_manager()->user_data_dir().AppendASCII(
"Profile 2"));
SetIdleTimeoutPolicies(policy_provider(1), /*idle_timeout=*/6);
}
Browser* browser3 = CreateBrowser(profile2);
EXPECT_EQ(2, GetBrowserCount(profile));
EXPECT_EQ(1, GetBrowserCount(profile2));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 299s, does nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(299)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(2, GetBrowserCount(profile));
EXPECT_EQ(1, GetBrowserCount(profile2));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 300s, threshold is reached for `profile`. Close its browsers, then show the
// Profile Picker.
{
BrowserCloseWaiter waiter({browser(), browser2});
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(300)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(315)));
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
}
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_EQ(1, GetBrowserCount(profile2));
EXPECT_FALSE(IsDialogOpen());
EXPECT_TRUE(ProfilePicker::IsOpen());
// 360s, threshold is reached for `profile2`. Close its browsers.
{
BrowserCloseWaiter waiter({browser3});
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(360)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(375)));
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
}
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_EQ(0, GetBrowserCount(profile2));
EXPECT_FALSE(IsDialogOpen());
EXPECT_TRUE(ProfilePicker::IsOpen());
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, DialogDismissedByUser) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
std::unique_ptr<base::HistogramTester> histogram_tester =
std::make_unique<base::HistogramTester>();
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1);
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 59s, does nothing.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(59)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. Close browsers, then show the Profile
// Picker.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
DialogManager::GetInstance()->DismissDialogForTesting();
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(75)));
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// Check that the idle dialog events are recorded in the histogram.
EXPECT_THAT(
histogram_tester->GetAllSamples(
"Enterprise.IdleTimeoutPolicies.IdleTimeoutDialogEvent"),
ElementsAre(
base::Bucket(metrics::IdleTimeoutDialogEvent::kDialogShown, 1),
base::Bucket(metrics::IdleTimeoutDialogEvent::kDialogDismissedByUser,
1)));
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, NoActions) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{});
base::Value::List actions;
profile->GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should not show the dialog, because there
// are no actions.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
task_runner()->FastForwardBy(base::Seconds(30));
// Nothing happened: no browsers closed, no Profile Picker.
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, JustCloseBrowsers) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"close_browsers"});
base::Value::List actions;
actions.Append(static_cast<int>(ActionType::kCloseBrowsers));
profile->GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show the dialog.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
BrowserCloseWaiter waiter({browser()});
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
EXPECT_EQ(0, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
// Profile Picker didn't show.
EXPECT_FALSE(ProfilePicker::IsOpen());
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, JustShowProfilePicker) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"show_profile_picker"});
base::Value::List actions;
actions.Append(static_cast<int>(ActionType::kShowProfilePicker));
profile->GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions));
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show NOT show the dialog, which is
// tied to the "close_browsers" action.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_TRUE(ProfilePicker::IsOpen());
EXPECT_EQ(1, GetBrowserCount(profile));
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, ReloadPages) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"reload_pages"});
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
auto* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0);
ASSERT_NE(nullptr, web_contents);
// This callback should run after a navigation happens.
base::MockCallback<base::RepeatingCallback<void(content::NavigationHandle*)>>
cb;
base::RunLoop run_loop;
EXPECT_CALL(cb, Run(_))
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
content::DidFinishNavigationObserver observer(web_contents, cb.Get());
// 60s, threshold is reached. This should show NOT show the dialog, which is
// tied to the "close_browsers" action.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(ProfilePicker::IsOpen());
run_loop.Run();
// Browsers are still open, bubble is visible.
EXPECT_EQ(1, GetBrowserCount(profile));
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, ShowBubbleImmediately) {
browser()->window()->Activate();
BrowserList::SetLastActive(browser());
// Use "reload_pages" as our action, because:
// - It runs synchronously (succeeds immediately).
// - It doesn't close browsers.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"reload_pages"});
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show NOT show the dialog, which is
// tied to the "close_browsers" action.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(ProfilePicker::IsOpen());
ASSERT_EQ(1, GetBrowserCount(profile));
// Bring the browser back into focus.
ActivateBrowser(browser());
// Bubble should be visible on that browser.
EXPECT_TRUE(GetIdleBubble(browser()));
}
// It should be possible to focus the bubble with the "focus dialog" hotkey
// combination (Alt+Shift+A).
// TODO(crbug.com/40856612): Probably flaky on macOS.
#if BUILDFLAG(IS_MAC)
#define MAYBE_CanFocusBubbleWithFocusDialogHotkey \
DISABLED_CanFocusBubbleWithFocusDialogHotkey
#else
#define MAYBE_CanFocusBubbleWithFocusDialogHotkey \
CanFocusBubbleWithFocusDialogHotkey
#endif
IN_PROC_BROWSER_TEST_F(IdleServiceTest,
MAYBE_CanFocusBubbleWithFocusDialogHotkey) {
browser()->window()->Activate();
BrowserList::SetLastActive(browser());
// Use "reload_pages" as our action, because:
// - It runs synchronously (succeeds immediately).
// - It doesn't close browsers.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"reload_pages"});
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show NOT show the dialog, which is
// tied to the "close_browsers" action.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(ProfilePicker::IsOpen());
ASSERT_EQ(1, GetBrowserCount(profile));
// Bring the browser back into focus.
ActivateBrowser(browser());
// Bubble should be visible on that browser, and initially inactive.
views::View* bubble = GetIdleBubble(browser());
EXPECT_TRUE(bubble);
EXPECT_FALSE(bubble->GetWidget()->IsActive());
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
browser_view->FocusInactivePopupForAccessibility();
EXPECT_TRUE(bubble->GetWidget()->IsActive());
}
// It should be possible to focus the bubble with the "rotate pane focus" (F6)
// hotkey.
// TODO(crbug.com/40852599): Probably flaky on macOS.
#if BUILDFLAG(IS_MAC)
#define MAYBE_CanFocusBubbleWithRotatePaneFocusHotkey \
DISABLED_CanFocusBubbleWithRotatePaneFocusHotkey
#else
#define MAYBE_CanFocusBubbleWithRotatePaneFocusHotkey \
CanFocusBubbleWithRotatePaneFocusHotkey
#endif
IN_PROC_BROWSER_TEST_F(IdleServiceTest,
MAYBE_CanFocusBubbleWithRotatePaneFocusHotkey) {
browser()->window()->Activate();
BrowserList::SetLastActive(browser());
// Use "reload_pages" as our action, because:
// - It runs synchronously (succeeds immediately).
// - It doesn't close browsers.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1,
/*idle_timeout_actions=*/{"reload_pages"});
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_FALSE(ProfilePicker::IsOpen());
// 60s, threshold is reached. This should show NOT show the dialog, which is
// tied to the "close_browsers" action.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
task_runner()->FastForwardBy(base::Seconds(30));
EXPECT_FALSE(IsDialogOpen());
EXPECT_FALSE(ProfilePicker::IsOpen());
ASSERT_EQ(1, GetBrowserCount(profile));
// Bring the browser back into focus.
ActivateBrowser(browser());
// Bubble should be visible on that browser, and initially inactive.
views::View* bubble = GetIdleBubble(browser());
EXPECT_TRUE(bubble);
EXPECT_FALSE(bubble->GetWidget()->IsActive());
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
browser_view->RotatePaneFocus(true);
// Rotate pane focus is expected to keep the bubble focused until the user
// deals with it, so a second call should have no effect.
browser_view->RotatePaneFocus(true);
EXPECT_TRUE(bubble->GetWidget()->IsActive());
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, PRE_ShowBubbleOnStartup) {
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(58)));
Profile* profile = browser()->profile();
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1);
EXPECT_EQ(1, GetBrowserCount(profile));
EXPECT_FALSE(GetIdleBubble(browser()));
// 60s, threshold is reached. This should show the dialog.
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillOnce(Return(base::Seconds(60)));
task_runner()->FastForwardBy(base::Seconds(1));
ASSERT_EQ(1, GetBrowserCount(profile));
ASSERT_TRUE(IsDialogOpen());
EXPECT_FALSE(GetIdleBubble(browser()));
EXPECT_CALL(idle_time_provider(), CalculateIdleTime())
.WillRepeatedly(Return(base::Seconds(15)));
BrowserCloseWaiter waiter({browser()});
task_runner()->FastForwardBy(base::Seconds(30));
waiter.Wait();
ASSERT_EQ(0, GetBrowserCount(profile));
// No bubble visible, since there are no browser open. It will appear on next
// startup.
}
IN_PROC_BROWSER_TEST_F(IdleServiceTest, ShowBubbleOnStartup) {
// The bubble is visible, from the last browsing session.
SetIdleTimeoutPolicies(policy_provider(0), /*idle_timeout=*/1);
ActivateBrowser(browser());
EXPECT_TRUE(GetIdleBubble(browser()));
}
} // namespace enterprise_idle