blob: 498224b3e7c6ea57d9b7849b216a883069151d26 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_
#define CHROME_BROWSER_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/download/download_item_warning_data.h"
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "components/browsing_data/core/browsing_data_utils.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/password_protection/metrics_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
using PasswordProtectionUIType = safe_browsing::WarningUIType;
using PasswordProtectionUIAction = safe_browsing::WarningAction;
inline constexpr base::TimeDelta kPasswordChangeInactivity = base::Minutes(30);
inline constexpr base::TimeDelta kSafetyHubSurveyDelay = base::Minutes(10);
// Service which receives events from Trust & Safety features and determines
// whether or not to launch a HaTS survey on the NTP for the user.
class TrustSafetySentimentService
: public KeyedService,
public ProfileObserver,
public metrics::DesktopSessionDurationTracker::Observer {
public:
explicit TrustSafetySentimentService(Profile* profile);
~TrustSafetySentimentService() override;
// Called when the user opens an NTP. This allows the service to update its
// eligibility logic, and potentially show a survey. Virtual to allow mocking
// in tests.
virtual void OpenedNewTabPage();
// Called when the user interacts with the privacy settings on
// chrome://settings in |web_contents|. Interaction in this context could be
// using a link row on the privacy settings card. Calling this allows the
// service to monitor |web_contents| to determine if the user stays on
// settings for the required time. Virtual to allow mocking in tests.
virtual void InteractedWithPrivacySettings(
content::WebContents* web_contents);
// Called when the user runs safety check. This is immediately considered as a
// trigger action. Virtual to allow mocking in tests.
virtual void RanSafetyCheck();
// Called when the user opens Page Info.
virtual void PageInfoOpened();
// Called when the user interacts in some way with Page Info.
virtual void InteractedWithPageInfo();
// Called when the user saves a password via the manage passwords UI. This is
// the native UI shown when Chrome detects a password has been entered into
// the web page.
virtual void SavedPassword();
// Called when the user closes Page Info. If Page Info was opened for the
// target time, or the user interacted with it while it was open, a trigger
// action is recorded.
virtual void PageInfoClosed();
// Called when the user visits chrome://settings/passwords. Calling this
// allows the service to monitor |web_contents| to determine if the user
// remains on settings after visiting the page for the required time. Virtual
// to allow mocking in tests.
virtual void OpenedPasswordManager(content::WebContents* web_contents);
// Called when the user saves a card through the native UI bubble shown after
// the user uses a card on a website.
virtual void SavedCard();
// Called when the user runs password check. Virtual to allow mocking in
// tests.
virtual void RanPasswordCheck();
// Called when the user deletes data from Clear Browsing Data dialog.
virtual void ClearedBrowsingData(browsing_data::BrowsingDataType datatype);
// Called when the user finishes the privacy guide. Virtual to allow mocking
// in tests.
virtual void FinishedPrivacyGuide();
// Profile Observer:
void OnOffTheRecordProfileCreated(Profile* off_the_record) override;
void OnProfileWillBeDestroyed(Profile* profile) override;
// metrics::DesktopSessionDurationTracker::Observer
void OnSessionEnded(base::TimeDelta session_length,
base::TimeTicks session_end) override;
// The feature areas that the service delivers HaTS surveys for. Each feature
// area is associated with a different Listnr survey, and has a different set
// of Product Specific Data (PSD). kIneligible is an exception, and
// is not associated with any survey, but rather represents the collection of
// features for which interaction with should also be considered when
// determining elibigility for a survey.
// These values are persisted to logs and entries should not be renumbered or
// reused and kept up to date with TrustSafetySentimentFeatureArea in
// enums.xml.
enum class FeatureArea {
kIneligible = 0,
kPrivacySettings = 1,
kTrustedSurface = 2,
kTransactions = 3,
// kPrivacySandbox3ConsentAccept = 4, // DEPRECATED.
// kPrivacySandbox3ConsentDecline = 5, // DEPRECATED.
// kPrivacySandbox3NoticeDismiss = 6, // DEPRECATED.
// kPrivacySandbox3NoticeOk = 7, // DEPRECATED.
// kPrivacySandbox3NoticeSettings = 8, // DEPRECATED.
// kPrivacySandbox3NoticeLearnMore = 9, // DEPRECATED.
kSafetyCheck = 10,
kPasswordCheck = 11,
kBrowsingData = 12,
kPrivacyGuide = 13,
kControlGroup = 14,
// kPrivacySandbox4ConsentAccept = 15, // DEPRECATED.
// kPrivacySandbox4ConsentDecline = 16, // DEPRECATED.
// kPrivacySandbox4NoticeOk = 17, // DEPRECATED.
// kPrivacySandbox4NoticeSettings = 18, // DEPRECATED.
kSafeBrowsingInterstitial = 19,
kDownloadWarningUI = 20,
kPasswordProtectionUI = 21,
kSafetyHubNotification = 22,
kSafetyHubInteracted = 23,
kMaxValue = kSafetyHubInteracted,
};
// Called when the user interacts with a safe browsing blocking page.
virtual void InteractedWithSafeBrowsingInterstitial(
bool did_proceed,
safe_browsing::SBThreatType threat_type);
// Called when the user completes terminal action within a download warning.
// These actions can include: DISCARD, and PROCEED.
virtual void InteractedWithDownloadWarningUI(
DownloadItemWarningData::WarningSurface surface,
DownloadItemWarningData::WarningAction action);
// Called when user clicks to protect/reset/check their password on a password
// protection UI. This triggers a survey if the user has not finished changing
// their password after a certain period of time.
virtual void ProtectResetOrCheckPasswordClicked(
PasswordProtectionUIType ui_type);
// Called when a user sees a password protection warning and decides to ignore
// the warning, close the warning, or mark the warning as legitimate.
virtual void PhishedPasswordUpdateNotClicked(
PasswordProtectionUIType ui_type,
PasswordProtectionUIAction action);
// Called when a user finishes updating their phished password after seeing a
// warning.
virtual void PhishedPasswordUpdateFinished();
// Checks that this feature area is valid for the current version.
static bool VersionCheck(FeatureArea feature_area);
// Gets the HaTS trigger for a feature area.
static std::string GetHatsTriggerForFeatureArea(FeatureArea feature_area);
// Performs a FeatureArea and Version-specific dice roll.
// Returns true if succeeds, else false.
static bool ProbabilityCheck(FeatureArea feature_area);
// Triggers a survey for Safety Hub for the given feature area (visiting SH or
// seeing a notification).
virtual void TriggerSafetyHubSurvey(
TrustSafetySentimentService::FeatureArea feature_area,
std::map<std::string, bool> product_specific_data);
private:
friend class TrustSafetySentimentServiceTest;
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
Eligibility_NtpOpens);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, Eligibility_Time);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, TriggerProbability);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
TriggersClearOnLaunch);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
SettingsWatcher_PrivacySettings);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
SettingsWatcher_PasswordManager);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, RanSafetyCheck);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
PrivacySettingsProductSpecificData);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
Eligibility_V1FeatureWhileV2Enabled);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_SafetyCheck);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_TrustedSurface);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_PasswordCheck);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_BrowsingData);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
V2_BrowsingData_NotInterested);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_PrivacyGuide);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest, V2_ControlGroup);
FRIEND_TEST_ALL_PREFIXES(TrustSafetySentimentServiceTest,
SafetyHubInteractionState);
// Struct representing a trigger (user action relevant to T&S) that previously
// occurred, and is awaiting the appropriate eligibility steps before causing
// a survey to be shown.
struct PendingTrigger {
PendingTrigger();
PendingTrigger(const PendingTrigger& other);
PendingTrigger(const std::map<std::string, bool>& product_specific_data,
int remaining_ntps_to_open);
explicit PendingTrigger(int remaining_ntps_to_open);
~PendingTrigger();
std::map<std::string, bool> product_specific_data;
int remaining_ntps_to_open;
base::Time occurred_time;
};
// Class which observes the provided |web_contents| for |required_open_time|
// and then checks if |web_contents| is currently visible, and has settings
// open. Calls |success_callback| if the user stays on settings for the
// required time, calls |complete_callback| when the observation time has
// expired, or |web_contents| has been destroyed.
class SettingsWatcher : content::WebContentsObserver {
public:
SettingsWatcher(content::WebContents* web_contents,
base::TimeDelta required_open_time,
base::OnceCallback<void()> success_callback,
base::OnceCallback<void()> complete_callback);
~SettingsWatcher() override;
// WebContentsObserver:
void WebContentsDestroyed() override;
private:
void TimerComplete();
raw_ptr<content::WebContents> web_contents_;
base::OnceCallback<void()> success_callback_;
base::OnceCallback<void()> complete_callback_;
base::WeakPtrFactory<SettingsWatcher> weak_ptr_factory_{this};
};
// Struct which represents the PageInfo state of interest to the service.
struct PageInfoState {
PageInfoState();
base::Time opened_time;
bool interacted = false;
};
// Struct which represents the PhishedPasswordChange state. When a user clicks
// to change their password, we want to wait to trigger a survey until after
// they change their password or the user has been inactive for some time.
struct PhishedPasswordChangeState {
PhishedPasswordChangeState();
base::Time password_change_click_ts_;
PasswordProtectionUIType ui_type_;
bool finished_action = false;
};
void SettingsWatcherComplete();
// Record that a trigger occurred, placing it in the set of pending triggers.
// Private as the service itself determines when a trigger has occurred, and
// is responsible for generating the appropriate |product_specific_data|.
void TriggerOccurred(
FeatureArea feature_area,
const std::map<std::string, bool>& product_specific_data);
// Record that the user performed an action which should make them temporarily
// ineligible to receive a survey. This records a trigger for the kIneligible
// feature area, which just like any other trigger will prevent a survey from
// being shown, but will not result in a survey.
void PerformedIneligibleAction();
static bool ShouldBlockSurvey(const PendingTrigger& trigger);
// Called by |ProtectResetOrCheckPasswordClicked| and
// |PhishedPasswordUpdateNotClicked|. Triggers a survey if one has not already
// been triggered for the user journey.
void MaybeTriggerPasswordProtectionSurvey(PasswordProtectionUIType ui_type,
PasswordProtectionUIAction action);
const raw_ptr<Profile> profile_;
std::map<FeatureArea, PendingTrigger> pending_triggers_;
std::unique_ptr<SettingsWatcher> settings_watcher_;
std::unique_ptr<PageInfoState> page_info_state_;
std::unique_ptr<PhishedPasswordChangeState> phished_password_change_state_;
base::ScopedMultiSourceObservation<Profile, ProfileObserver>
observed_profiles_{this};
bool performed_control_group_dice_roll_;
base::ScopedObservation<metrics::DesktopSessionDurationTracker,
metrics::DesktopSessionDurationTracker::Observer>
session_duration_observation_{this};
base::WeakPtrFactory<TrustSafetySentimentService> weak_ptr_factory_{this};
};
#endif // CHROME_BROWSER_UI_HATS_TRUST_SAFETY_SENTIMENT_SERVICE_H_