1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
|
// 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_
|