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_
|