File: product_messaging_controller.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 (226 lines) | stat: -rw-r--r-- 9,181 bytes parent folder | download | duplicates (6)
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
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_USER_EDUCATION_COMMON_PRODUCT_MESSAGING_CONTROLLER_H_
#define COMPONENTS_USER_EDUCATION_COMMON_PRODUCT_MESSAGING_CONTROLLER_H_

#include <map>
#include <set>

#include "base/callback_list.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "components/user_education/common/session/user_education_session_manager.h"
#include "components/user_education/common/user_education_storage_service.h"
#include "ui/base/interaction/element_identifier.h"

namespace user_education {

class ProductMessagingController;

// Opaque ID for required notices.
//
// Use DECLARE/DEFINE_REQUIRED_NOTICE_IDENTIFIER() below to create these for
// your notices.
using RequiredNoticeId = ui::ElementIdentifier;

// Place this in a .h file:
#define DECLARE_REQUIRED_NOTICE_IDENTIFIER(name) \
  DECLARE_ELEMENT_IDENTIFIER_VALUE(name)

// Place this in a .cc file:
#define DEFINE_REQUIRED_NOTICE_IDENTIFIER(name) \
  DEFINE_ELEMENT_IDENTIFIER_VALUE(name)

// This can be used in tests to avoid name conflicts.
#define DEFINE_LOCAL_REQUIRED_NOTICE_IDENTIFIER(name) \
  DEFINE_MACRO_ELEMENT_IDENTIFIER_VALUE(__FILE__, __LINE__, name)

// This can be used to scope an identifier to a class; use this in the public
// part of the class definition.
#define DECLARE_CLASS_REQUIRED_NOTICE_IDENTIFIER(name) \
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(name)

// Use this in the .cc file to define an identifier scoped to a class, this must
// be paired with the DECLARE macro above.
#define DEFINE_CLASS_REQUIRED_NOTICE_IDENTIFIER(Class, Name) \
  DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(Class, Name)

namespace internal {
// Special value in the "show after" list that causes the notice to happen last.
DECLARE_REQUIRED_NOTICE_IDENTIFIER(kShowAfterAllNotices);
}  // namespace internal

// The owner of this object currently has priority to show a required product
// notice. It must be held while the notice is showing and released immediately
// after the notice is dismissed.
class [[nodiscard]] RequiredNoticePriorityHandle final {
 public:
  RequiredNoticePriorityHandle();
  RequiredNoticePriorityHandle(RequiredNoticePriorityHandle&&) noexcept;
  RequiredNoticePriorityHandle& operator=(
      RequiredNoticePriorityHandle&&) noexcept;
  ~RequiredNoticePriorityHandle();

  // Whether this handle is valid.
  explicit operator bool() const;
  bool operator!() const;

  RequiredNoticeId notice_id() const { return notice_id_; }

  // Set that the notice was actually shown. Cannot be called on a null handle
  // or after releasing. Call to specify that the given notice was actually
  // shown; if you discard or release the handle without calling this function,
  // it is assumed that the notice was not shown.
  void SetShown();

  // Release the handle, resetting to default (null/falsy) value.
  void Release();

 private:
  friend class ProductMessagingController;
  RequiredNoticePriorityHandle(
      RequiredNoticeId notice_id,
      base::WeakPtr<ProductMessagingController> controller);

  bool shown_ = false;
  RequiredNoticeId notice_id_;
  base::WeakPtr<ProductMessagingController> controller_;
};

// Callback when a required notice is ready to show. The notice should show
// immediately.
//
// `handle` should be moved to a semi-permanent location and released when the
// notice is dismissed/closes. Failure to hold or release the handle can cause
// problems with User Education and other required notices.
using RequiredNoticeShowCallback =
    base::OnceCallback<void(RequiredNoticePriorityHandle handle)>;

// Coordinates between critical product messaging (e.g. legal notices) that must
// show in Chrome, to ensure that (a) they do not show over each other and (b)
// no other spontaneous User Education experiences start at the same time.
class ProductMessagingController final {
 public:
  ProductMessagingController();
  ProductMessagingController(const ProductMessagingController&) = delete;
  void operator=(const ProductMessagingController&) = delete;
  ~ProductMessagingController();

  // Register the session provider which is used to clear the set of shown
  // notices and the storage service used to retrieve shown promos.
  void Init(UserEducationSessionProvider& session_provider,
            UserEducationStorageService& storage_service);

  // Returns whether there are any notices queued or showing. This can be used
  // to prevent other, lower-priority User Education experiences from showing.
  bool has_pending_notices() const {
    return current_notice_ || !pending_notices_.empty();
  }

  // Checks whether the given `notice_id` is queued.
  bool IsNoticeQueued(RequiredNoticeId notice_id) const;

  // Requests that `notice_id` be queued to show. When it is allowed (which
  // might be as soon as the current message queue empties),
  // `ready_to_start_callback` will be called.
  //
  // If `always_show_after` is provided, then this notice is guaranteed to show
  // after the specified notices; otherwise the order of notices is not defined.
  //
  // The `blocked_by` list is similar to `always_show_after`, but if one of the
  // listed notices is successfully shown, this notice will not be shown this
  // session. Be aware that specifying one or more notices on the `blocked_by`
  // list may mean `ready_to_start_callback` is never called.
  //
  // Similarly, re-queueing a notice that is already showing or has been
  // successfully shown will have no effect, and `ready_to_start_callback` will
  // not be called.
  //
  // The expectation is that all of the notices will be queued during browser
  // startup, so that even if A must show after B, but B requests to show just
  // before A, then they will still show in the correct order starting a frame
  // or two later.
  void QueueRequiredNotice(
      RequiredNoticeId notice_id,
      RequiredNoticeShowCallback ready_to_start_callback,
      std::initializer_list<RequiredNoticeId> always_show_after = {},
      std::initializer_list<RequiredNoticeId> blocked_by = {});

  // Removes `notice_id` from the queue, if it is queued.
  // Has no effect if the notice has already started to show.
  void UnqueueRequiredNotice(RequiredNoticeId notice_id);

  // Callback for notifications about other services' activity.
  using StatusUpdateCallback = base::RepeatingCallback<void(RequiredNoticeId)>;

  // Adds a callback that will be called whenever a RequiredNoticeHandle will be
  // granted. This can optionally be used to know when other systems are about
  // to show a notice.
  base::CallbackListSubscription AddRequiredNoticePriorityHandleGrantedCallback(
      StatusUpdateCallback callback);

  // Adds a callback that will be called when the UI of a required notice will
  // actually be shown (not just that the handle is being held).
  base::CallbackListSubscription AddRequiredNoticeShownCallback(
      StatusUpdateCallback callback);

  bool has_current_notice() const { return static_cast<bool>(current_notice_); }

  RequiredNoticeId current_notice_for_testing() const {
    return current_notice_;
  }

 private:
  friend class RequiredNoticePriorityHandle;
  struct RequiredNoticeData;

  bool ready_to_show() const {
    CHECK(storage_service_) << "Must call Init() before queueing notices.";
    return !current_notice_ && !pending_notices_.empty();
  }

  // Called by RequiredNoticePriorityHandle when it is released. Clears the
  // current notice and maybe tries to start the next.
  void ReleaseHandle(RequiredNoticeId notice_id, bool notice_shown);

  // Shows the next notice, if one is eligible, by calling
  // `MaybeShowNextRequiredNoticeImpl()` on a fresh call stack.
  void MaybeShowNextRequiredNotice();

  // Remove any queued notice that should not show.
  //
  // A notice is blocked if another notice in its `blocked_by` list has been
  // shown, or if the same notice has already been shown this session.
  void PurgeBlockedNotices();

  // Actually shows the next notice, if one is eligible. Must be called on a
  // fresh call stack, and should only be queued by
  // `MaybeShowNextRequiredNotice()`.
  void MaybeShowNextRequiredNoticeImpl();

  // Do housekeeping associated with a new session.
  void OnNewSession();

  // Notify that the notice was actually shown.
  void OnNoticeShown(RequiredNoticeId notice_id);

  // Describes the current contents of `pending_notices_` for debugging/error
  // purposes.
  std::string DumpData() const;

  RequiredNoticeId current_notice_;
  raw_ptr<UserEducationStorageService> storage_service_ = nullptr;
  std::map<RequiredNoticeId, RequiredNoticeData> pending_notices_;
  base::CallbackListSubscription session_subscription_;
  base::RepeatingCallbackList<StatusUpdateCallback::RunType>
      handle_granted_callbacks_;
  base::RepeatingCallbackList<StatusUpdateCallback::RunType>
      notice_shown_callbacks_;
  base::WeakPtrFactory<ProductMessagingController> weak_ptr_factory_{this};
};

}  // namespace user_education

#endif  // COMPONENTS_USER_EDUCATION_COMMON_PRODUCT_MESSAGING_CONTROLLER_H_