blob: 3e716c7f20ecfbe7b1adf055d734813a02c240fd [file] [log] [blame]
// Copyright 2025 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/safe_browsing/safe_browsing_pref_change_handler.h"
#include "base/feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "content/public/browser/web_contents.h"
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || \
BUILDFLAG(IS_MAC)
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#endif
namespace safe_browsing {
SafeBrowsingPrefChangeHandler::SafeBrowsingPrefChangeHandler(Profile* profile)
: profile_(profile) {
DCHECK(profile);
#if BUILDFLAG(IS_ANDROID)
retry_handler_ = std::make_unique<MessageRetryHandler>(
profile_, prefs::kSafeBrowsingSyncedEnhancedProtectionRetryState,
prefs::kSafeBrowsingSyncedEnhancedProtectionNextRetryTimestamp,
kRetryAttemptStartupDelay, kRetryNextAttemptDelay, kWaitingPeriodInterval,
base::BindOnce(&SafeBrowsingPrefChangeHandler::RetryStateCallback,
weak_ptr_factory_.GetWeakPtr()),
"SafeBrowsing.EnhancedProtection.ShouldRetryOutcome",
prefs::kSafeBrowsingSyncedEnhancedProtectionUpdateTimestamp,
prefs::kEnhancedProtectionEnabledViaTailoredSecurity);
retry_handler_->StartRetryTimer();
#endif
}
SafeBrowsingPrefChangeHandler::~SafeBrowsingPrefChangeHandler() {
#if BUILDFLAG(IS_ANDROID)
RemoveTabModelListObserver();
RemoveTabModelObserver();
#endif
}
// TODO(crbug.com/378888301): Add tests for Chrome Toast and Android modal
// logic.
void SafeBrowsingPrefChangeHandler::
MaybeShowEnhancedProtectionSettingChangeNotification() {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || \
BUILDFLAG(IS_MAC)
if (!profile_ ||
!base::FeatureList::IsEnabled(safe_browsing::kEsbAsASyncedSetting)) {
return;
}
Browser* const browser = chrome::FindBrowserWithProfile(profile_);
if (!browser) {
return;
}
ToastController* const controller =
browser->browser_window_features()->toast_controller();
if (!controller) {
return;
}
// We need to handle the toast for the security settings page differently:
// 1. If the user has turned on ESB and is on the security page, we show
// the toast but without the action button that directs users to the
// settings page.
// 2. If the user has turned off ESB and is on the security page, we do
// not show a toast at all.
TabStripModel* tab_strip_model = browser->GetTabStripModel();
content::WebContents* web_contents = tab_strip_model->GetActiveWebContents();
bool is_security_page =
web_contents ? web_contents->GetLastCommittedURL().spec().starts_with(
"chrome://settings/security")
: false;
// Extract the enhanced protection pref value.
bool is_enhanced_enabled = IsEnhancedProtectionEnabled(*profile_->GetPrefs());
// The enhanced protection setting has been updated. To reflect this
// change, we will show toasts to the user, taking into account both the
// new setting value and whether they are currently on the settings page.
if (is_enhanced_enabled) {
// When the user is currently on the security settings page, show a
// toast without the action button to go to the settings page.
// Otherwise, we should a button that takes user to the settings page to
// change the enhanced protection settings.
controller->MaybeShowToast(
ToastParams(is_security_page ? ToastId::kSyncEsbOnWithoutActionButton
: ToastId::kSyncEsbOn));
} else if (!is_security_page) {
// Toast messages are not displayed on the security page when a user
// disables a security setting. This applies whether the user disables
// the setting on the current device or the change is synced from
// another device.
controller->MaybeShowToast(ToastParams(ToastId::kSyncEsbOff));
}
#endif
// TODO(crbug.com/397966486): Add tests in the android test file.
#if BUILDFLAG(IS_ANDROID)
if (!base::FeatureList::IsEnabled(safe_browsing::kEsbAsASyncedSetting) ||
!profile_) {
return;
}
content::WebContents* web_contents = nullptr;
for (const TabModel* tab_model : TabModelList::models()) {
if (tab_model->GetProfile() != profile_) {
continue;
}
int tab_count = tab_model->GetTabCount();
for (int i = 0; i < tab_count; i++) {
web_contents = tab_model->GetWebContentsAt(i);
if (web_contents) {
break;
}
}
}
if (!web_contents) {
// Instantiate the retry handler here, if it hasn't been already
profile_->GetPrefs()->SetInteger(
prefs::kSafeBrowsingSyncedEnhancedProtectionRetryState,
static_cast<int>(MessageRetryHandler::RetryState::RETRY_NEEDED));
if (!retry_handler_) {
retry_handler_ = std::make_unique<MessageRetryHandler>(
profile_, prefs::kSafeBrowsingSyncedEnhancedProtectionRetryState,
prefs::kSafeBrowsingSyncedEnhancedProtectionNextRetryTimestamp,
kRetryAttemptStartupDelay, kRetryNextAttemptDelay,
kWaitingPeriodInterval,
base::BindOnce(&SafeBrowsingPrefChangeHandler::RetryStateCallback,
weak_ptr_factory_.GetWeakPtr()),
"SafeBrowsing.EnhancedProtection.ShouldRetryOutcome",
prefs::kSafeBrowsingSyncedEnhancedProtectionUpdateTimestamp,
prefs::kEnhancedProtectionEnabledViaTailoredSecurity);
retry_handler_->StartRetryTimer();
}
RegisterObserver();
} else {
// Do not show the notification modal if the user set the setting locally on
// this device.
if (profile_->GetPrefs()->GetBoolean(
prefs::kSafeBrowsingSyncedEnhancedProtectionSetLocally)) {
retry_handler_->SaveRetryState(
MessageRetryHandler::RetryState::NO_RETRY_NEEDED);
return;
}
// Extract the enhanced protection pref value.
bool is_enhanced_enabled =
IsEnhancedProtectionEnabled(*profile_->GetPrefs());
message_ = std::make_unique<TailoredSecurityConsentedModalAndroid>(
web_contents, is_enhanced_enabled,
base::BindOnce(
&SafeBrowsingPrefChangeHandler::ConsentedMessageDismissed,
weak_ptr_factory_.GetWeakPtr()),
/*is_requested_by_synced_esb=*/true);
if (retry_handler_) {
retry_handler_->SaveRetryState(
MessageRetryHandler::RetryState::NO_RETRY_NEEDED);
}
}
#endif
}
#if BUILDFLAG(IS_ANDROID)
void SafeBrowsingPrefChangeHandler::RetryStateCallback() {
profile_->GetPrefs()->SetInteger(
prefs::kSafeBrowsingSyncedEnhancedProtectionRetryState,
static_cast<int>(MessageRetryHandler::RetryState::RETRY_NEEDED));
MaybeShowEnhancedProtectionSettingChangeNotification();
}
void SafeBrowsingPrefChangeHandler::SetTabModelForTesting(TabModel* tab_model) {
observed_tab_model_ = tab_model;
}
bool SafeBrowsingPrefChangeHandler::IsObservingTabModelListForTesting() const {
return observing_tab_model_list_;
}
bool SafeBrowsingPrefChangeHandler::IsObservingTabModelForTesting() const {
return observed_tab_model_ != nullptr;
}
void SafeBrowsingPrefChangeHandler::DidAddTab(TabAndroid* tab,
TabModel::TabLaunchType type) {
RemoveTabModelObserver();
RemoveTabModelListObserver();
// Get the Profile from the TabAndroid
if (!tab || !tab->web_contents()) {
return;
}
RetryStateCallback();
}
void SafeBrowsingPrefChangeHandler::OnTabModelAdded(TabModel* tab_model) {
if (observed_tab_model_) {
return;
}
if (TabModelList::models().empty()) {
return;
}
AddTabModelObserver();
}
void SafeBrowsingPrefChangeHandler::OnTabModelRemoved(TabModel* tab_model) {
RemoveTabModelObserver();
}
void SafeBrowsingPrefChangeHandler::RegisterObserver() {
AddTabModelListObserver();
AddTabModelObserver();
}
void SafeBrowsingPrefChangeHandler::AddTabModelListObserver() {
if (observing_tab_model_list_) {
return;
}
TabModelList::AddObserver(this);
observing_tab_model_list_ = true;
}
void SafeBrowsingPrefChangeHandler::AddTabModelObserver() {
if (observed_tab_model_) {
return;
}
for (TabModel* tab_model : TabModelList::models()) {
if (tab_model->GetProfile() != profile_) {
continue;
}
tab_model->AddObserver(this);
// Saving the tab_model so we can stop observing the tab model after a
// tabmodel is added.
observed_tab_model_ = tab_model;
return;
}
}
void SafeBrowsingPrefChangeHandler::RemoveTabModelListObserver() {
observing_tab_model_list_ = false;
TabModelList::RemoveObserver(this);
}
void SafeBrowsingPrefChangeHandler::RemoveTabModelObserver() {
if (!observed_tab_model_) {
return;
}
observed_tab_model_->RemoveObserver(this);
observed_tab_model_ = nullptr;
}
void SafeBrowsingPrefChangeHandler::ConsentedMessageDismissed() {
message_.reset();
}
#endif
} // namespace safe_browsing