File: disruptive_notification_permissions_manager.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (293 lines) | stat: -rw-r--r-- 12,035 bytes parent folder | download | duplicates (5)
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
// 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.

#ifndef CHROME_BROWSER_UI_SAFETY_HUB_DISRUPTIVE_NOTIFICATION_PERMISSIONS_MANAGER_H_
#define CHROME_BROWSER_UI_SAFETY_HUB_DISRUPTIVE_NOTIFICATION_PERMISSIONS_MANAGER_H_

#include <memory>
#include <set>

#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/content_settings_type_set.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "services/metrics/public/cpp/ukm_source_id.h"

class GURL;
class Profile;

namespace site_engagement {
class SiteEngagementService;
}  // namespace site_engagement

// This class keeps track of disruptive notification permissions by checking the
// average daily notification counts and site engagement score.
class DisruptiveNotificationPermissionsManager
    : public content_settings::Observer {
 public:
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // kNotAllowedContentSetting, kInvalidContentSetting,
  // kNotSiteScopedContentSetting, kManagedContentSetting, kNoRevokeDefaultBlock
  // are returned for sites where the permission cannot be revoked.
  //
  // Shadow run: the site is proposed for revocation (kProposedRevoke) and
  // returns kAlreadyInProposedRevokeList for all the next runs.
  //
  // Actual revocation: the site first is marked for revocation (returns
  // kProposedRevoke) and then the permission is actually revoked (return
  // kRevoke). After the permission is revoked, the content setting is removed
  // so the site won't be reported anymore.
  //
  // Undo: If the user has undone the revocation, the site is marked as "ignore"
  // so it won't be revoked on the next runs.
  //
  // LINT.IfChange(RevocationResult)
  enum class RevocationResult {
    kNotAllowedContentSetting = 0,
    kInvalidContentSetting = 1,
    kNotSiteScopedContentSetting = 2,
    kManagedContentSetting = 3,
    kAlreadyInProposedRevokeList = 4,
    // kFalsePositive = 5,  // deprecated, now reported as kNotDisruptive
    kNotDisruptive = 6,
    kProposedRevoke = 7,
    kNoRevokeDefaultBlock = 8,
    // kAlreadyFalsePositive = 9,  // deprecated, now reported as kNotDisruptive
    kRevoke = 10,
    kIgnore = 11,
    kMaxValue = kIgnore,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/settings/enums.xml:DisruptiveNotificationRevocationResult)

  // TODO(crbug.com/406472515): Reevaluate if we should continue reporting false
  // positives for non persistent notification clicks. Non persistent
  // notifications are only shown when the site is visited so the site must be
  // visited first.
  //
  // The reason why a disruptive notification revocation was considered a false
  // positive. If the user interacts with a site after revocation, the
  // revocation was a false positive.
  //
  // LINT.IfChange(FalsePositiveReason)
  enum class FalsePositiveReason {
    kPageVisit = 0,
    kPersistentNotificationClick = 1,
    kNonPersistentNotificationClick = 2,
    kMaxValue = kNonPersistentNotificationClick,
  };
  // LINT.ThenChange(//tools/metrics/histograms/enums.xml:DisruptiveNotificationFalsePositiveReason)

  // LINT.IfChange(RevocationState)
  enum class RevocationState {
    kProposed = 1,
    kRevoked = 2,
    kIgnore = 3,
    kAcknowledged = 4,
    kMaxValue = kAcknowledged,
  };
  // LINT.ThenChange(//tools/metrics/histograms/enums.xml:DisruptiveNotificationRevocationState)

  class SafetyHubNotificationWrapper {
   public:
    virtual ~SafetyHubNotificationWrapper();
    virtual void DisplayNotification(int num_revoked_permissions) = 0;
    virtual void UpdateNotification(int num_revoked_permissions) = 0;
  };

  explicit DisruptiveNotificationPermissionsManager(
      scoped_refptr<HostContentSettingsMap> hcsm,
      site_engagement::SiteEngagementService* site_engagement_service);

  DisruptiveNotificationPermissionsManager(
      const DisruptiveNotificationPermissionsManager&) = delete;
  DisruptiveNotificationPermissionsManager& operator=(
      const DisruptiveNotificationPermissionsManager&) = delete;

  ~DisruptiveNotificationPermissionsManager() override;

  // Revokes notification permissions for disruptive sites and records
  // the revoked websites in the content setting.
  void RevokeDisruptiveNotifications();

  // Returns the list of revoked disruptive notifications, excluding proposed
  // revocations and false positives.
  ContentSettingsForOneType GetRevokedNotifications();

  // Returns true if settings are being changed due to auto revocation;
  bool IsRunning();

  // If the url has a revoked disruptive notification permission, this method
  // allows the notification permissions again and adds a constraint so that
  // this permission is not auto-revoked during future Safety Hub checks.
  void RegrantPermissionForUrl(const GURL& url);

  // If `permission_types` includes notifications, undo the actions from
  // `RegrantPermissionForUrl` by changing the `NOTIFICATIONS` setting back
  // to `CONTENT_SETTING_ASK` and the
  // `REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS` status value back to
  // `safety_hub::kRevokeStr`.
  void UndoRegrantPermissionForUrl(
      const GURL& url,
      std::set<ContentSettingsType> permission_types,
      content_settings::ContentSettingConstraints constraints);

  // Clear the list of revoked notification permissions so they will no longer
  // be shown to the user. Does not change permissions themselves.
  void ClearRevokedPermissionsList();

  // Restores REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS entry for the
  // primary_pattern after it was deleted after user has accepted the revocation
  // (via `ClearRevokedPermissionsList()`). Only restores the value if there is
  // a matching notification engagement entry.
  void RestoreDeletedRevokedPermission(
      const ContentSettingsPattern& primary_pattern,
      content_settings::ContentSettingConstraints constraints);

  // If the URL is in the revoke or proposed revoke list, report a false
  // positive and record metrics.
  static void MaybeReportFalsePositive(Profile* profile,
                                       const GURL& origin,
                                       FalsePositiveReason reason,
                                       ukm::SourceId source_id);

  // Logs metrics for proposed disruptive notification revocation, to be called
  // when displaying a persistent notification.
  static void LogMetrics(Profile* profile,
                         const GURL& url,
                         ukm::SourceId source_id);

  // Returns true if `url` has been revoked notification permissions because of
  // sending disruptive notifications.
  static bool IsUrlRevokedDisruptiveNotification(HostContentSettingsMap* hcsm,
                                                 const GURL& url);

  // content_settings::Observer implementation.
  void OnContentSettingChanged(
      const ContentSettingsPattern& primary_pattern,
      const ContentSettingsPattern& secondary_pattern,
      ContentSettingsTypeSet content_type_set) override;

  // Test support:
  void SetClockForTesting(base::Clock* clock);
  void SetNotificationWrapperForTesting(
      std::unique_ptr<SafetyHubNotificationWrapper> wrapper);

 private:
  friend class DisruptiveNotificationPermissionsManagerTest;
  friend class RevokedPermissionsServiceBrowserTest;
  friend class RevokedPermissionsServiceTest;
  FRIEND_TEST_ALL_PREFIXES(
      PlatformNotificationServiceTest,
      ProposedDisruptiveNotificationRevocationMetricsPersistent);
  FRIEND_TEST_ALL_PREFIXES(
      PlatformNotificationServiceTest,
      ProposedDisruptiveNotificationRevocationMetricsNonPersistent);

  // A revocation entry as stored in content settings
  // (ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS).
  struct RevocationEntry {
    RevocationEntry(RevocationState revocation_state,
                    double site_engagement,
                    int daily_notification_count,
                    base::Time timestamp = base::Time::Now());
    RevocationEntry(const RevocationEntry& other);
    RevocationEntry& operator=(const RevocationEntry& other);
    ~RevocationEntry();

    bool operator==(const RevocationEntry& other) const;

    RevocationState revocation_state;
    double site_engagement;
    int daily_notification_count;

    // Timestamp of proposed or actual revocation.
    base::Time timestamp;

    bool has_reported_proposal = false;
    bool has_reported_false_positive = false;

    int page_visit_count = 0;
    int notification_click_count = 0;
  };

  // Helper class to manage content settings for
  // ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS.
  class ContentSettingHelper {
   public:
    explicit ContentSettingHelper(HostContentSettingsMap& hcsm);

    // Get/store/delete the `REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS`
    // setting.
    std::optional<RevocationEntry> GetRevocationEntry(const GURL& url);
    void PersistRevocationEntry(const GURL& url, const RevocationEntry& entry);
    void DeleteRevocationEntry(const GURL& url);
    std::vector<std::pair<GURL, RevocationEntry>> GetAllEntries();

   private:
    std::optional<RevocationEntry> ToRevocationEntry(
        const base::Value& value,
        const content_settings::RuleMetaData& metadata);

    base::raw_ref<HostContentSettingsMap> hcsm_;
  };

  // If the notifications should be revoked based on whether the metrics were
  // already reported or the cooldown period has run out.
  bool CanRevokeNotifications(const GURL& url,
                              const RevocationEntry& revocation_entry);

  // Revokes notification permission, updates the content setting value to
  // revoke and reports metrics.
  void RevokeNotifications(const GURL& url, RevocationEntry revocation_entry);

  // Whether the notification is disruptive based on the site engagement score
  // for the URL and the daily average notification count.
  bool IsNotificationDisruptive(const GURL& url, int daily_notification_count);

  // Displays the safety hub notification informing the users about revoked
  // notification permissions.
  void DisplayNotification();

  // Updates the count of revoked notification permissions in the safety hub
  // notification informing the users about revoked notification permissions.
  void UpdateNotificationCount();

  // Updates content settings for notification permissions.
  void UpdateNotificationPermission(const GURL& url,
                                    ContentSetting setting_value);

  // Ignores this url for future revocation and reports regrant metrics.
  void OnPermissionRegranted(const GURL& url,
                             RevocationEntry revocation_entry,
                             bool regranted_in_safety_hub);

  // Report metrics for the daily run.
  void ReportDailyRunMetrics();

  scoped_refptr<HostContentSettingsMap> hcsm_;

  raw_ptr<site_engagement::SiteEngagementService> site_engagement_service_;

  // Observer to watch for content settings changed.
  base::ScopedObservation<HostContentSettingsMap, content_settings::Observer>
      content_settings_observation_{this};

  raw_ptr<base::Clock> clock_ = base::DefaultClock::GetInstance();

  bool is_revocation_running_ = false;

  // Track whether this service is responsible for changing notification
  // permissions, in order to ignore this case inside OnContentSettingChanged.
  bool is_changing_notification_permission_ = false;

  std::unique_ptr<SafetyHubNotificationWrapper> notification_wrapper_;
};

#endif  // CHROME_BROWSER_UI_SAFETY_HUB_DISRUPTIVE_NOTIFICATION_PERMISSIONS_MANAGER_H_