| // 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 "chrome/browser/enterprise/idle/action_runner.h" |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/mock_callback.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h" |
| #include "chrome/browser/enterprise/idle/action.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browsing_data_remover.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace enterprise_idle { |
| |
| using content::BrowsingDataRemover; |
| |
| using testing::_; |
| using testing::IsEmpty; |
| using testing::Return; |
| using testing::UnorderedElementsAre; |
| |
| namespace { |
| |
| struct RunEntry { |
| raw_ptr<Profile> profile; |
| base::flat_set<std::string> action_names; |
| }; |
| |
| class FakeActionFactory : public ActionFactory { |
| public: |
| FakeActionFactory() = default; |
| |
| ActionQueue Build(const std::vector<ActionType>& action_types) override { |
| ActionQueue actions; |
| for (ActionType action_type : action_types) { |
| auto it = associations_.find(action_type); |
| if (it != associations_.end()) { |
| actions.push(std::move(it->second)); |
| associations_.erase(it); |
| } |
| } |
| return actions; |
| } |
| |
| void Associate(ActionType action_type, std::unique_ptr<Action> action) { |
| associations_[action_type] = std::move(action); |
| } |
| |
| private: |
| std::map<ActionType, std::unique_ptr<Action>> associations_; |
| }; |
| |
| class MockAction : public Action { |
| public: |
| explicit MockAction(ActionType action_type) : Action(action_type) {} |
| |
| MOCK_METHOD2(Run, void(Profile*, Continuation)); |
| }; |
| |
| // testing::InvokeArgument<N> does not work with base::OnceCallback, so we |
| // define our own gMock action to run the 2nd argument. |
| ACTION_P(RunContinuation, success) { |
| std::move(const_cast<Action::Continuation&>(arg1)).Run(success); |
| } |
| |
| } // namespace |
| |
| // TODO(crbug.com/1316551): Enable this when Android supports >1 Action. |
| #if !BUILDFLAG(IS_ANDROID) |
| // Tests that actions are run in sequence, in order of priority. |
| TEST(IdleActionRunnerTest, RunsActionsInSequence) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kCloseBrowsers)); |
| actions.Append(static_cast<int>(ActionType::kShowProfilePicker)); |
| profile.GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions)); |
| |
| auto close_browsers = |
| std::make_unique<MockAction>(ActionType::kCloseBrowsers); |
| auto show_profile_picker = |
| std::make_unique<MockAction>(ActionType::kShowProfilePicker); |
| testing::InSequence in_sequence; |
| EXPECT_CALL(*close_browsers, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| EXPECT_CALL(*show_profile_picker, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| |
| action_factory.Associate(ActionType::kCloseBrowsers, |
| std::move(close_browsers)); |
| action_factory.Associate(ActionType::kShowProfilePicker, |
| std::move(show_profile_picker)); |
| runner.Run(); |
| } |
| |
| // Tests that the order of actions in the pref doesn't matter. They still run |
| // by order of priority. |
| TEST(IdleActionRunnerTest, PrefOrderDoesNotMatter) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kCloseBrowsers)); |
| actions.Append(static_cast<int>(ActionType::kShowProfilePicker)); |
| profile.GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions)); |
| |
| auto close_browsers = |
| std::make_unique<MockAction>(ActionType::kCloseBrowsers); |
| auto show_profile_picker = |
| std::make_unique<MockAction>(ActionType::kShowProfilePicker); |
| testing::InSequence in_sequence; |
| EXPECT_CALL(*close_browsers, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| EXPECT_CALL(*show_profile_picker, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| |
| action_factory.Associate(ActionType::kCloseBrowsers, |
| std::move(close_browsers)); |
| action_factory.Associate(ActionType::kShowProfilePicker, |
| std::move(show_profile_picker)); |
| |
| runner.Run(); |
| } |
| |
| // Tests that when a higher-priority action fails, the lower-priority actions |
| // don't run. |
| TEST(IdleActionRunnerTest, OtherActionsDontRunOnFailure) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kCloseBrowsers)); |
| actions.Append(static_cast<int>(ActionType::kShowProfilePicker)); |
| profile.GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions)); |
| |
| auto close_browsers = |
| std::make_unique<MockAction>(ActionType::kCloseBrowsers); |
| auto show_profile_picker = |
| std::make_unique<MockAction>(ActionType::kShowProfilePicker); |
| |
| // "show_profile_picker" shouldn't run, because "close_browsers" fails. |
| testing::InSequence in_sequence; |
| EXPECT_CALL(*close_browsers, Run(&profile, _)) |
| .WillOnce(RunContinuation(false)); |
| EXPECT_CALL(*show_profile_picker, Run(_, _)).Times(0); |
| |
| action_factory.Associate(ActionType::kCloseBrowsers, |
| std::move(close_browsers)); |
| action_factory.Associate(ActionType::kShowProfilePicker, |
| std::move(show_profile_picker)); |
| runner.Run(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Tests that it does nothing when the "IdleTimeoutActions" pref is empty. |
| TEST(IdleActionRunnerTest, DoNothingWithEmptyPref) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| // "IdleTimeoutActions" is deliberately unset. |
| auto clear_browsing_history = |
| std::make_unique<MockAction>(ActionType::kClearBrowsingHistory); |
| auto clear_download_history = |
| std::make_unique<MockAction>(ActionType::kClearDownloadHistory); |
| |
| EXPECT_CALL(*clear_browsing_history, Run(_, _)).Times(0); |
| EXPECT_CALL(*clear_download_history, Run(_, _)).Times(0); |
| |
| action_factory.Associate(ActionType::kClearBrowsingHistory, |
| std::move(clear_browsing_history)); |
| action_factory.Associate(ActionType::kClearDownloadHistory, |
| std::move(clear_download_history)); |
| runner.Run(); |
| } |
| |
| // TODO(crbug.com/1316551): Enable this when Android supports >1 Action. |
| #if !BUILDFLAG(IS_ANDROID) |
| // Tests that ActionRunner only runs the actions configured via the |
| // "IdleTimeoutActions" pref. |
| TEST(IdleActionRunnerTest, JustCloseBrowsers) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kCloseBrowsers)); |
| profile.GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions)); |
| |
| auto close_browsers = |
| std::make_unique<MockAction>(ActionType::kCloseBrowsers); |
| auto show_profile_picker = |
| std::make_unique<MockAction>(ActionType::kShowProfilePicker); |
| |
| EXPECT_CALL(*close_browsers, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| EXPECT_CALL(*show_profile_picker, Run(_, _)).Times(0); |
| |
| action_factory.Associate(ActionType::kCloseBrowsers, |
| std::move(close_browsers)); |
| action_factory.Associate(ActionType::kShowProfilePicker, |
| std::move(show_profile_picker)); |
| runner.Run(); |
| } |
| |
| // Tests that ActionRunner only runs the actions configured via the |
| // "IdleTimeoutActions" pref. |
| TEST(IdleActionRunnerTest, JustShowProfilePicker) { |
| content::BrowserTaskEnvironment task_environment; |
| TestingProfile profile; |
| FakeActionFactory action_factory; |
| ActionRunner runner(&profile, &action_factory); |
| |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kShowProfilePicker)); |
| profile.GetPrefs()->SetList(prefs::kIdleTimeoutActions, std::move(actions)); |
| |
| auto close_browsers = |
| std::make_unique<MockAction>(ActionType::kCloseBrowsers); |
| auto show_profile_picker = |
| std::make_unique<MockAction>(ActionType::kShowProfilePicker); |
| |
| EXPECT_CALL(*close_browsers, Run(_, _)).Times(0); |
| EXPECT_CALL(*show_profile_picker, Run(&profile, _)) |
| .WillOnce(RunContinuation(true)); |
| |
| action_factory.Associate(ActionType::kCloseBrowsers, |
| std::move(close_browsers)); |
| action_factory.Associate(ActionType::kShowProfilePicker, |
| std::move(show_profile_picker)); |
| runner.Run(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // A basic implementation of BrowsingDataRemover, that doesn't remove any data. |
| // |
| // Doesn't use gmock methods, since we could technically accept any of the 4 |
| // Remove*() methods. Instead, it saves the arguments for BrowsingDataRemover's |
| class FakeBrowsingDataRemover : public BrowsingDataRemover { |
| public: |
| void SetEmbedderDelegate( |
| content::BrowsingDataRemoverDelegate* embedder_delegate) override { |
| NOTREACHED(); |
| } |
| bool DoesOriginMatchMaskForTesting( |
| uint64_t origin_type_mask, |
| const url::Origin& origin, |
| storage::SpecialStoragePolicy* special_storage_policy) override { |
| NOTREACHED(); |
| return true; |
| } |
| void Remove(const base::Time& delete_begin, |
| const base::Time& delete_end, |
| uint64_t remove_mask, |
| uint64_t origin_type_mask) override { |
| DCHECK_EQ(nullptr, observer_); |
| remove_mask_ = remove_mask; |
| origin_type_mask_ = origin_type_mask; |
| } |
| void RemoveWithFilter(const base::Time& delete_begin, |
| const base::Time& delete_end, |
| uint64_t remove_mask, |
| uint64_t origin_type_mask, |
| std::unique_ptr<content::BrowsingDataFilterBuilder> |
| filter_builder) override { |
| Remove(delete_begin, delete_end, remove_mask, origin_type_mask); |
| } |
| void RemoveAndReply(const base::Time& delete_begin, |
| const base::Time& delete_end, |
| uint64_t remove_mask, |
| uint64_t origin_type_mask, |
| Observer* observer) override { |
| DCHECK_EQ(observer, observer_); |
| remove_mask_ = remove_mask; |
| origin_type_mask_ = origin_type_mask; |
| observer->OnBrowsingDataRemoverDone(failed_data_types_); |
| } |
| void RemoveWithFilterAndReply( |
| const base::Time& delete_begin, |
| const base::Time& delete_end, |
| uint64_t remove_mask, |
| uint64_t origin_type_mask, |
| std::unique_ptr<content::BrowsingDataFilterBuilder> filter_builder, |
| Observer* observer) override { |
| RemoveAndReply(delete_begin, delete_end, remove_mask, origin_type_mask, |
| observer); |
| } |
| void AddObserver(Observer* observer) override { |
| DCHECK_EQ(nullptr, observer_); |
| observer_ = observer; |
| } |
| void RemoveObserver(Observer* observer) override { |
| DCHECK_EQ(observer, observer_); |
| observer_ = nullptr; |
| } |
| void SetWouldCompleteCallbackForTesting( |
| const base::RepeatingCallback< |
| void(base::OnceClosure continue_to_completion)>& callback) override { |
| NOTREACHED(); |
| } |
| const base::Time& GetLastUsedBeginTimeForTesting() override { |
| NOTREACHED(); |
| return begin_time_; |
| } |
| uint64_t GetLastUsedRemovalMaskForTesting() override { return remove_mask_; } |
| uint64_t GetLastUsedOriginTypeMaskForTesting() override { |
| return origin_type_mask_; |
| } |
| |
| void SetFailedDataTypesForTesting(uint64_t failed_data_types) { |
| failed_data_types_ = failed_data_types; |
| } |
| |
| private: |
| base::Time begin_time_; |
| uint64_t remove_mask_ = 0; |
| uint64_t origin_type_mask_ = 0; |
| uint64_t failed_data_types_ = 0; |
| |
| Observer* observer_ = nullptr; |
| }; |
| |
| class IdleActionRunnerClearDataTest : public testing::Test { |
| protected: |
| TestingProfile* profile() { return &profile_; } |
| FakeBrowsingDataRemover* remover() { return &browsing_data_remover_; } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile profile_; |
| FakeBrowsingDataRemover browsing_data_remover_; |
| }; |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearBrowsingHistory) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearBrowsingHistory)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_HISTORY, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearDownloadHistory) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearDownloadHistory)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(BrowsingDataRemover::DATA_TYPE_DOWNLOADS, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearCookies) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearCookiesAndOtherSiteData)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| EXPECT_EQ(BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, |
| remover()->GetLastUsedOriginTypeMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearCache) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearCachedImagesAndFiles)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(BrowsingDataRemover::DATA_TYPE_CACHE, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearPasswordSignin) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearPasswordSignin)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_PASSWORDS, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearAutofill) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearAutofill)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_FORM_DATA, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearSiteSettings) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearSiteSettings)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, ClearHostedAppData) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearHostedAppData)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| EXPECT_EQ(BrowsingDataRemover::ORIGIN_TYPE_PROTECTED_WEB, |
| remover()->GetLastUsedOriginTypeMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, MultipleTypes) { |
| base::Value::List actions; |
| actions.Append(static_cast<int>(ActionType::kClearBrowsingHistory)); |
| actions.Append(static_cast<int>(ActionType::kClearDownloadHistory)); |
| actions.Append(static_cast<int>(ActionType::kClearAutofill)); |
| profile()->GetPrefs()->SetList(prefs::kIdleTimeoutActions, |
| std::move(actions)); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionRunner runner(profile(), ActionFactory::GetInstance()); |
| runner.Run(); |
| |
| EXPECT_EQ(chrome_browsing_data_remover::DATA_TYPE_HISTORY | |
| BrowsingDataRemover::DATA_TYPE_DOWNLOADS | |
| chrome_browsing_data_remover::DATA_TYPE_FORM_DATA, |
| remover()->GetLastUsedRemovalMaskForTesting()); |
| } |
| |
| TEST_F(IdleActionRunnerClearDataTest, MultipleTypesAndFailure) { |
| remover()->SetFailedDataTypesForTesting( |
| chrome_browsing_data_remover::DATA_TYPE_HISTORY | |
| BrowsingDataRemover::DATA_TYPE_DOWNLOADS | |
| chrome_browsing_data_remover::DATA_TYPE_FORM_DATA); |
| |
| ActionFactory::GetInstance()->SetBrowsingDataRemoverForTesting(remover()); |
| ActionFactory::ActionQueue actions = ActionFactory::GetInstance()->Build( |
| {ActionType::kClearBrowsingHistory, ActionType::kClearDownloadHistory, |
| ActionType::kClearAutofill}); |
| ASSERT_EQ(1u, actions.size()); |
| |
| // The callback should run with success=false. |
| base::MockCallback<Action::Continuation> cb; |
| base::RunLoop run_loop; |
| EXPECT_CALL(cb, Run(/*success=*/false)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| actions.top()->Run(profile(), cb.Get()); |
| run_loop.Run(); |
| } |
| |
| } // namespace enterprise_idle |