components/user_education/product_messaging/README.md
This is a profile-level controller which mediates spontaneous UI throughout chromium. The goal is to prevent the user from being overwhelmed by spontaneous messaging, and to prevent different spontaneous UI from fighting with each other.
For example, the Product Messaging Controller guarantees that mandatory compliance notices do not have IPH popping up over them.
Current/likely candidates for Product Messaging Controller:
Things that should not interact with the PMC:
The Product Messaging Controller is a multi-lane queue that allows spontaneous messages to ask to be shown, and be granted permission to show when it is safe to do so. Once granted permission, the requesting system holds a handle for as long as the UI is showing, then discards it; discarding the handle allows the next waiting message to show.
When a lower-priority message is showing and a higher-priority message wants to show, the lower-priority system can ask to be notified, and will be told if/when it is superseded. The lower-priority system can then choose to hide or modify its UI to accommodate the higher-priority message. (It can also choose not to do anything in response.)
This is for new message types not covered by the ProductMessageType enum in
product_messaging_types.h. If you're adding a
message of an existing type, skip to the next section entitled "Creating and
Showing Your Message".
You should consult with one of the OWNERS before onboarding a new system.
ProductMessageType enum.
ProductMessagingPolicyImpl::CreateDefault():
SetSelfBlocking(false) if you want your messages to be able to
re-show in the same session, or if you are reusing the same key for
different messages.SetEquivalent() to specify that the two types should share a queue
and priority; they will then block but not supersede each other.SetBlockedBy() indicates that the given message cannot be shown if any of
the specified messages are queued, showing, or have been shown.SetShowAfter() indicates that the given message always goes behind the
other specified messages in the queue.To prepare to show your message, determine if you need a new key. Some systems
use a single key for all messages (this is usually combined with
SetSelfBlocking(false) - see above), while others have a separate key for each
message.
Use the DECLARE/DEFINE_... macros in
product_messaging_types.h to create a new message
key with a specified priority.
When you are ready to show, retrieve the ProductMessagingController from the
UserEducationService for the current profile and call QueueMessage().
You will specify a ProductMessageReadyCallback that receives a
ProductMessagingHandle if/when your message is ready to show. If it is not (or
if you specify a timeout and it elapses), it will be ejected from the queue.
BrowserUserEducationService* const service =
UserEducationServiceFactory::GetForBrowserContext(profile);
service->product_messaging_controller()->QueueMessage(
kMyMessageKey,
base::BindOnce(&MySystem::OnMessageReady, GetAsWeakPtr()));
When your ProductMessageReadyCallback is called, you should:
ProductMessagingHandle::SetShown() to indicate your message was shown.Here's an example of what the code might look like:
void MySystem::OnMessageReady(ProductMessagingHandle handle) {
if (ShouldShowSpontaneousUi()) {
handle->SetShown();
ShowSpontaneousUi(std::move(handle));
}
}
If your UI is lower-priority you may want to respond to higher-priority UI being
shown. When you get the callback to show your UI and you decide to show it,
first call ProductMessagingHandle::SetSupersededCallback().
You will now get callbacks if a higher priority message is shown. You can choose what to do with your UI at this point - you might want to hide or reduce its prominence in some cases but not in others.
void MySystem::OnMessageReady(ProductMessagingHandle handle) {
if (ShouldShowSpontaneousUi()) {
handle->SetShown();
handle->SetSupersededCallback(base::BindOnce(
&MySystem::OnSpontaneousUiSuperseded, GetAsWeakPtr()));
ShowSpontaneousUi(std::move(handle));
}
}
void MySystem::OnSpontaneousUiSuperseded(
ProductMessageKey, ProductMessageStatus) {
CloseSpontaneousUiIfShowing();
}
Q: If the queue is empty, how long will it take to get called back?
A: Callbacks are never synchronous, but will typically happen on the next frame.
Q: Is there a way to tell if my message would be able to show immediately?
A: For now, call ProductMessagingController::GetAllMessages() with appropriate
filters (e.g. priority_higher_than equal to the next priority level strictly
lower than your message). If enough systems end up asking the same question we
can always add a convenience method.
Q: How do I know if my message timed out or was blocked?
A: Call ProductMessagingController::GetMessageStatus() and see if your message
is still queued. We don't [yet] provide a timeout/blocked callback because the
"re-queueing a message resets the timeout" behavior makes determining when to
fire such a callback much harder to evaluate.
Q: I need more sophisticated rate-limiting/prioritization!
A: This system exists only to coordinate different systems which want to display spontaneous UI, not to coordinate rate-limiting or prioritization within a single system.
If the change you need is strictly one for coordinating between different systems, talk to the maintainers and the policies can potentially be made more sophisticated.