910e62b5创建于 1月15日历史提交
// 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.

#include <array>
#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/browser_window/public/browser_window_interface_iterator.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/chrome_test_utils.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 "chrome/test/base/ui_test_utils.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);
    }

    // TODO(crbug.com/431671320): This test uses keep-alive to prevent the
    // Browser process from being unpinned before the ProfilePicker is destroyed
    // during test cleanup, to work around its use of the mock time task runner.
    // This keep-alive should be removed and tear-down issues resulting from
    // use of the custom task runner resolved.
    keep_alive_ = std::make_unique<ScopedKeepAlive>(
        KeepAliveOrigin::CHROME_APP_DELEGATE, 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;
    ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
        [profile, &count](BrowserWindowInterface* browser_window_interface) {
          if (browser_window_interface->GetProfile() == profile) {
            count++;
          }
          return true;
        });
    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:
  std::array<testing::NiceMock<policy::MockConfigurationPolicyProvider>, 2>
      policy_providers_;
  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 and Linux
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#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) {
  auto* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0);

  // TODO(crbug.com/430613676): Determine why about:blank refreshes are causing
  // timeouts with RenderDocument enabled and revert this test to being
  // navigated to about:blank.
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL main_url = chrome_test_utils::GetTestUrl(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("empty.html")));
  EXPECT_TRUE(NavigateToURL(web_contents, main_url));

  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());

  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