blob: 021ec3e6bc23d09901e173cba3eb2ad5343333b8 [file] [log] [blame]
// Copyright 2024 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_DOWNLOAD_DOWNLOAD_WARNING_DESKTOP_HATS_UTILS_H_
#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_WARNING_DESKTOP_HATS_UTILS_H_
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "components/download/public/common/download_item.h"
// Type of survey (corresponding to a trigger condition) that should be shown.
// Do not renumber.
enum class DownloadWarningHatsType {
// Kept a suspicious file from the download bubble.
kDownloadBubbleBypass = 0,
// Deleted a warned file from the download bubble.
kDownloadBubbleHeed = 1,
// User did not act on warning despite being active.
kDownloadBubbleIgnore = 2,
// Kept a suspicious/dangerous file from the downloads page or the download
// danger prompt.
kDownloadsPageBypass = 3,
// Deleted a warned file from the downloads page.
kDownloadsPageHeed = 4,
// Navigated away from or closed the downloads page without acting on the
// warning.
kDownloadsPageIgnore = 5,
kMaxValue = kDownloadsPageIgnore,
};
// Stores the PSD for the download warning HaTS survey.
class DownloadWarningHatsProductSpecificData {
public:
// A namespace for the field labels, which are presented to the user in the
// privacy and transparency UI.
struct Fields {
// Bits data fields:
// Whether the download bubble partial view was enabled.
static constexpr char kPartialViewEnabled[] =
"Automatic download bubble enabled (\"Show downloads when they're "
"done\" setting)";
// Whether the user interacted with the partial view (true) or main view
// (false). Only logged for download bubble triggers. Must be added after
// Create(). Defaults to false if no value is added.
static constexpr char kPartialViewInteraction[] =
"User interacted with automatic download bubble";
// Whether the download was initiated by a user gesture.
static constexpr char kUserGesture[] = "Download initiated by user gesture";
// String data fields:
// The outcome of the download: "Bypassed warning", "Heeded warning", or
// "Ignored warning".
static constexpr char kOutcome[] = "Outcome";
// The UI surface on which the outcome occurred: "Download bubble" or
// "Downloads page".
static constexpr char kSurface[] = "UI surface";
// A combination of danger type of the download and tailored verdict (if
// any; see csd.proto).
static constexpr char kDangerType[] = "Danger type";
// Danger UI pattern: "Dangerous", "Suspicious", or "None".
static constexpr char kWarningType[] = "Warning type";
// What the user's Safe Browsing setting is.
static constexpr char kSafeBrowsingState[] = "Safe Browsing state";
// Client channel, "Stable", "Beta", etc.
static constexpr char kChannel[] = "Chrome channel";
// How many total warned downloads were on the downloads page. Only logged
// for downloads page triggers. Must be added after Create(). Defaults to
// a placeholder if no value is added.
static constexpr char kNumPageWarnings[] =
"Number of warnings on downloads page";
// The user's interactions with this specific warning, as a
// comma-separated string of timestamped actions.
// Only logged for users with Enhanced Safe Browsing.
// Other users will have a placeholder value.
static constexpr char kWarningInteractions[] =
"User interactions with this download warning with timestamps (ms)";
// The time elapsed since the download started, in seconds.
static constexpr char kSecondsSinceDownloadStarted[] =
"Time since download started (s)";
// The time elapsed since the warning was shown, in seconds.
static constexpr char kSecondsSinceWarningShown[] =
"Time since warning shown (s)";
// URLs of the downloaded file and referring page.
// These are only logged for users with Safe Browsing enabled.
// Other users will have a placeholder value.
static constexpr char kUrlDownload[] = "Download URL";
static constexpr char kUrlReferrer[] = "Referrer URL";
// Download filename.
// Only logged for users with Safe Browsing enabled.
// Other users will have a placeholder value.
static constexpr char kFilename[] = "Download filename";
// Timeout used for "ignore" survey trigger. Only added for a download
// bubble warning that was ignored.
static constexpr char kIgnoreTimeoutSeconds[] =
"Threshold for ignored warning (s)";
};
// Returns a ProductSpecificData with some basic PSD values filled in.
// This only fills in certain fields derivable from the DownloadItem and the
// profile. Other fields may need to be supplied by the caller directly.
// Note: Caller must ensure that the DownloadItem is dangerous and not done.
static DownloadWarningHatsProductSpecificData Create(
DownloadWarningHatsType survey_type,
download::DownloadItem* download_item);
// Methods to add PSD fields that the caller supplies:
// Must be called for any downloads page trigger before sending the survey.
void AddNumPageWarnings(int num);
// Must be called for any download bubble trigger before sending the survey.
void AddPartialViewInteraction(bool partial_view_interaction);
// Returns the names of the PSD fields, used in creating the survey configs.
// These are CHECKed against the fields ultimately passed to the HaTS
// service.
static std::vector<std::string> GetBitsDataFields(
DownloadWarningHatsType survey_type);
static std::vector<std::string> GetStringDataFields(
DownloadWarningHatsType survey_type);
// Note that the applicable fields must all be present before using this
// object, so AddNumPageWarnings or AddPartialViewInteraction must have been
// called, otherwise using this object will cause a CHECK failure.
const SurveyBitsData& bits_data() const { return bits_data_; }
const SurveyStringData& string_data() const { return string_data_; }
DownloadWarningHatsType survey_type() const { return survey_type_; }
DownloadWarningHatsProductSpecificData(
const DownloadWarningHatsProductSpecificData&);
DownloadWarningHatsProductSpecificData& operator=(
const DownloadWarningHatsProductSpecificData&);
DownloadWarningHatsProductSpecificData(
DownloadWarningHatsProductSpecificData&&);
DownloadWarningHatsProductSpecificData& operator=(
DownloadWarningHatsProductSpecificData&&);
~DownloadWarningHatsProductSpecificData();
private:
explicit DownloadWarningHatsProductSpecificData(
DownloadWarningHatsType survey_type);
DownloadWarningHatsType survey_type_;
SurveyBitsData bits_data_;
SurveyStringData string_data_;
};
// A class that manages delayed download warning HaTS survey tasks. It can be
// given a DownloadItem to launch a survey for in the future after some delay,
// and these tasks can be canceled explicitly or automatically (in case of
// the DownloadItem getting destroyed or becoming ineligible for a HaTS survey).
// It also records the last time the user interacted with the browser, and the
// survey is withheld if the user was (presumably) idle for the entire period of
// the delay. (Client should inform this object of browser activity.)
// Note: Currently this is only used for download bubble ignore triggers.
class DelayedDownloadWarningHatsLauncher
: public download::DownloadItem::Observer {
public:
// A callback that allows the completion of the PSD (addition of post-Create()
// fields).
using PsdCompleter =
base::RepeatingCallback<void(DownloadWarningHatsProductSpecificData&)>;
// Bundles the objects used to control the task and its lifetime. Can only be
// used once per instance. To cancel, delete this object. The `download`
// and `hats_launcher` must outlive this.
class Task {
public:
// Creates and schedules the task.
Task(DelayedDownloadWarningHatsLauncher& hats_launcher,
download::DownloadItem* download,
base::OnceClosure task,
base::TimeDelta delay);
Task(const Task&) = delete;
Task& operator=(const Task&) = delete;
~Task();
private:
void RunTask();
// Controls the observation of the download by the parent object.
base::ScopedObservation<download::DownloadItem,
DelayedDownloadWarningHatsLauncher>
observation_;
// Task to show the survey.
base::OnceClosure task_;
// Used to cancel the scheduled task.
base::WeakPtrFactory<Task> weak_factory_{this};
};
// `profile` is the profile for which the HaTS surveys should be shown.
// `delay` is the delay that applies to all surveys launched by this object.
// `psd_completer` will be called with the product-specific data right before
// attempting to launch each survey.
DelayedDownloadWarningHatsLauncher(
Profile* profile,
base::TimeDelta delay,
PsdCompleter psd_completer = base::DoNothing());
DelayedDownloadWarningHatsLauncher(
const DelayedDownloadWarningHatsLauncher&) = delete;
DelayedDownloadWarningHatsLauncher& operator=(
const DelayedDownloadWarningHatsLauncher&) = delete;
~DelayedDownloadWarningHatsLauncher() override;
// download::DownloadItem::Observer:
// This object is an observer of every download with an entry in `tasks_`.
void OnDownloadUpdated(download::DownloadItem* download) override;
void OnDownloadDestroyed(download::DownloadItem* download) override;
// Updates the last_activity_ time.
void RecordBrowserActivity();
// Schedules a survey to be shown after the delay, if the user has been active
// in the meantime. Does nothing if a task already exists for the download.
// Does nothing if the download is not eligible when scheduling. (The
// scheduled task will also fizzle if the download is not eligible upon
// execution.) Returns whether task was scheduled.
bool TryScheduleTask(DownloadWarningHatsType survey_type,
download::DownloadItem* download);
// Cancels and removes the task for `download` from the map. Is a no-op if the
// download is not in the map.
void RemoveTaskIfAny(download::DownloadItem* download);
private:
// Address of a DownloadItem, derived from a DownloadItem*, but it is not to
// be dereferenced.
using TaskKey = std::uintptr_t;
// Returns the task key to be used for the download, which is just the
// address.
TaskKey GetTaskKey(download::DownloadItem* download);
// Cancels and removes the task from the map. Is a no-op if the key is not in
// the map. In particular, if the DownloadItem has been freed, its key will
// not be found in the map, as guaranteed by the DownloadItem::Observer
// mechanism.
void RemoveTaskByKeyIfAny(TaskKey key);
// Launches the actual survey, if all preconditions are met.
void MaybeLaunchSurveyNow(DownloadWarningHatsType survey_type,
download::DownloadItem* download);
// Returns a callback that is called to clean up after the survey succeeds or
// fails.
base::OnceClosure MakeSurveyDoneCallback(download::DownloadItem* download);
// Whether the user was active in the browser during the delay period.
bool WasUserActive() const;
// Profile to show the surveys for. Must outlive this.
const raw_ptr<Profile> profile_;
// How long to wait before launching the survey.
const base::TimeDelta delay_;
// Time of the most recent user interaction with the browser.
base::Time last_activity_;
// Maps DownloadItem addresses to their corresponding pending tasks.
std::map<TaskKey, Task> tasks_;
// Callback that is run to stamp the PSD with any additional fields right
// before attempting to launch the survey.
PsdCompleter psd_completer_;
// Needed because the cleanup callback produced by MakeSurveyDoneCallback
// may outlive this.
base::WeakPtrFactory<DelayedDownloadWarningHatsLauncher> weak_factory_{this};
};
// Returns if the download item is dangerous and not-done.
bool CanShowDownloadWarningHatsSurvey(download::DownloadItem* download);
// Returns the HaTS trigger string for the survey_type, if the user is eligible
// for that type of survey (according to the fieldtrial config). If the user
// is not eligible, or there is a configuration error, this returns nullopt.
std::optional<std::string> MaybeGetDownloadWarningHatsTrigger(
DownloadWarningHatsType survey_type);
// Returns the time delay used for kDownloadBubbleIgnore triggers.
base::TimeDelta GetIgnoreDownloadBubbleWarningDelay();
// Launches a HaTS survey using the desktop HaTS service, if all preconditions
// are met. The `psd` object encapsulates the data for the survey, including the
// triggering survey type. `profile` is the profile for which the survey should
// be launched. Note that it is potentially different from the profile under
// which the download was made (in the case of OTR profiles which may care about
// downloads made in their original profile), so it needs to be passed and
// cannot be derived from the DownloadItem. However, when `profile` is OTR and
// differs from the DownloadItem's Profile, a HaTS survey won't be shown anyway
// because HaTS surveys are not shown for OTR profiles, so everything is fine
// as long as we pass the correct `profile` for which we are attempting to
// launch the survey.
void MaybeLaunchDownloadWarningHatsSurvey(
Profile* profile,
const DownloadWarningHatsProductSpecificData& psd,
base::OnceClosure success_callback = base::DoNothing(),
base::OnceClosure failure_callback = base::DoNothing());
#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_WARNING_DESKTOP_HATS_UTILS_H_