This library contains the code that will allow you to implement In-Product-Help (IPH) and Tutorials in any framework, as well as display the "New" Badge on menus and labels.
[TOC]
Familiarity with these libraries are strongly recommended; feel free to reach out to their respective OWNERS if you have any questions.
- UI Interaction
- ElementTracker - supplies anchor points for help bubbles
- InteractionSequence - describes the situations in which a Tutorial advances to the next step
- Feature Engagement - used to evaluate triggering conditions for IPH and New Badge.
- common - contains platform- and framework-agnostic APIs for
working with
HelpBubble
s, IPH, and Tutorials. - test - contains common code for testing user education primitives
- views - contains code required to display a
HelpBubble
in a Views-based UI, as well as "New" Badge primitives. - webui - contains code required to display a
HelpBubble
on a WebUI surface.
The necessary IPH services have already been implemented in Chrome. If you are interested in extending User Education to another platform, see the section below.
Note: The rest of this document introduces User Education concepts and focuses on using existing services to create in-product help experiences.
The core presentation element for both IPH and Tutorials is the
HelpBubble. A HelpBubble
is a blue bubble that
appears anchored to an element in your application's UI and which contains
information about that element. For example, a HelpBubble
might appear
underneath the profile button the first time the user starts Chrome after
adding a second profile, showing the user how they can switch between profiles.
Different UI frameworks have different HelpBubble
implementations; for
example, HelpBubbleViews. Each type of
HelpBubble
is created by a different
HelpBubbleFactory, which is registered at
startup in the global
HelpBubbleFactoryRegistry. So for
example, Chrome registers separate factories for Views, WebUI, and a
Mac-specific factory that can attach a Views-based HelpBubble
to a Mac
native menu.
When it comes time to actually show the bubble, the
HelpBubbleFactoryRegistry
will need two things:
- The
TrackedElement
the bubble will be anchored to - The HelpBubbleParams describing the bubble
The HelpBubbleFactoryRegistry
will search its registered factories for
one able to produce a help bubble in the framework that sees this
element. It can then create our help bubble with the given
HelpBubbleParams
.
You will notice that this is an extremely bare-bones system. You are not
expected to call HelpBubbleFactoryRegistry
directly! Rather, the IPH and
Tutorial systems use this API to show help bubbles.
In-Product Help is the simpler of the two ways to display help bubbles, and can even be the entry point for a Tutorial.
IPH are:
- Spontaneous - they are shown to the user when a set of conditions are met; the user does not choose to see them.
- Rate-limited - the user will only ever see a specific IPH a certain number of times, and will only see a certain total number of different IPH per session.
- Simple - only a small number of templates approved by UX are available, for different kinds of User Education journeys.
In the code, an IPH is described by a FeaturePromo
. Your application
will provide
a FeaturePromoController with
a FeaturePromoRegistry. In order to
add a new IPH, you will need to:
- Add the
base::Feature
corresponding to the IPH. - Register the appropriate FeaturePromoSpecification describing your IPH journey (Registering your IPH).
- Configure the Feature Engagement backend for your IPH journey (Configuring when your IPH runs).
- Add hooks into your code to show/hide your IPH and dispatch events (Talking to the FE backend).
- Enable the feature via a trade study or Finch.
In reality, you will likely never interact directly with the
FeaturePromoController
. In Chrome, these methods are wrapped by the
BrowserWindow
. You may access them by calling:
BrowserWindow::MaybeShowFeaturePromo()
BrowserWindow::MaybeShowStartupFeaturePromo()
BrowserWindow::CloseFeaturePromo()
BrowserWindow::CloseFeaturePromoAndContinue()
You will want to create a FeaturePromoSpecification
and register it with
FeaturePromoRegistry::RegisterFeature()
. There should be a common function
your application uses to register IPH journeys during startup; in Chrome it's
MaybeRegisterChromeFeaturePromos()
.
There are several factory methods on FeaturePromoSpecification for different types of IPH:
- CreateForToastPromo - creates a small, short-lived promo with no buttons
that disappears after a short time.
- These are designed to point out a specific UI element; you will not expect the user to interact with the bubble.
- Because of this a screen reader message and accelerator to access the relevant feature are required; this will be used to make sure that screen reader users can find the thing the bubble is talking about.
- CreateForSnoozePromo - creates a promo with "got it" and "remind me later" buttons and if the user picks the latter, snoozes the promo so it can be displayed again later.
- CreateForTutorialPromo - similar to
CreateForSnoozePromo()
except that the "got it" button is replaced by a "learn more" button that launches a Tutorial. - CreateForLegacyPromo (DEPRECATED) - creates a toast-like promo with no buttons, but which does not require accessible text and has no or a long timeout. For backwards compatibility with older promos; do not use.
You may also call the following methods to add additional features to a bubble:
- SetBubbleTitleText() - adds an optional title to the bubble; this will be in a larger font above the body text.
- SetBubbleIcon() - adds an optional icon to the bubble; this will be displayed to the left (right in RTL) of the title/body.
- SetBubbleArrow() - sets the position of the arrow relative to the bubble; this in turn changes the bubble's default orientation relative to its anchor.
These are advanced features
- SetInAnyContext() - allows the system to search for the anchor element in any context rather than only the window in which the IPH is triggered.
- SetAnchorElementFilter() - allows the system to narrow down the anchor
from a collection of candidates, if there is more than one element maching
the anchor's
ElementIdentifier
.
The Feature Engagement (FE) backend does all the heavy-lifting when it comes to showing your IPH at the right time. All you need to do is configure how often your IPH should show and how it interacts with other IPH.
You will need to become familiar with the terminology in the FE docs, but you will instead create the configuration through the FeatureConfig API.
Now that the IPH feature is created and configured, you will need to add hooks into your code to interact with the FE backend.
You should attempt to show the IPH at an appropriate point in the code.
In Chrome, this would be a call to
BrowserWindow::MaybeShowFeaturePromo()
, or if your promo should run
immediately at startup, BrowserWindow::MaybeShowStartupFeaturePromo()
.
You will also add additional calls to
feature_engagement::Tracker::NotifyEvent()
for events that should
affect whether the IPH should display.
- These events should also be referenced in the Feature Engagement
configuration (
FeatureConfig
). - This should include the user actually engaging with the feature being promo'd.
- You can retrieve the tracker and send an event in Chrome via
BrowserView::NotifyFeatureEngagementEvent()
.
Optionally: you may add calls to programmatically end the promo when the
user engages with your feature. In Chrome, you can use
BrowserWindow::CloseFeaturePromo()
or
BrowserWindow::CloseFeaturePromoAndContinue()
.
Tutorials are the more complicated, in-depth way to display a series of help bubbles. Often an IPH is an entry point to a Tutorial but Tutorials can also be launched from e.g. a "What's New" or "Tips" page.
Tutorials are:
- Intentional - the user must always opt-in to seeing a Tutorial.
- Repeatable - the user may view a Tutorial any number of times, and may view any number of Tutorials.
- In-Depth - a Tutorial can breadcrumb the user around the UI, requesting the user engage in any number of behaviors, and will respond to those actions.
Your application will provide a TutorialService with a TutorialRegistry. In order to add a new Tutorial, you will need to:
- Declare a TutorialIdentifier in an accessible location.
- Register the
TutorialIdentifier
and TutorialDescription (Defining and registering your tutorial). - Create an entry point for the Tutorial, either by:
- Directly calling
TutorialService::StartTutorial()
- Or registering an IPH using the
CreateForTutorialPromo()
factory method. This IPH will prompt the user to start your tutorial.
- Directly calling
Notice that compared to an IPH, the tutorial itself does not require any
base::Feature
, FE configuration, or Finch configuration. This is
because the tutorial is always initiated by the user. However, the IPH
that launches your tutorial will need to be implemented and configured
as outlined above (In-Produce Help (IPH)).
A Tutorial is a stateful, executable object that "runs" the Tutorial itself; since they can't be reused, one needs to be created every time the Tutorial is started.
A TutorialDescription is the template
from which a Tutorial
is built. It describes each step your tutorial
will show the user, similar to the FeaturePromoSpecification
used to
create an IPH. A TutorialDescription
can be restarted, i.e. rebuilt
into a new Tutorial
, if you choose to allow it.
There are only a few fields in a TutorialDescription
:
- steps - Contains a sequence of user actions, UI changes, and the help bubbles that will be shown in each step.
- histograms - Must be populated if you want UMA histograms regarding user
engagement with your Tutorial.
- The preferred syntax is:
const char kMyTutorialHistogramPrefix[] = "MyTutorial"; tutorial_description.histograms = user_education::MakeTutorialHistograms<kMyTutorialHistogramPrefix>( tutorial_description.steps.size());
- The
kMyTutorialHistogramPrefix
needs to be declared as a localconst char[]
and have a globally-unique value. This will appear in UMA histogram entries associated with your tutorial. If this value is duplicated the histogram behavior will be undefined. - Note that this cannot be done automatically by the TutorialRegistry as
the UMA histograms won't work without the static declarations
implemented by the
TutorialHistogramsImpl<>
template class (via C++ template specialization magic).
- The preferred syntax is:
- can_be_restarted - If set to
true
the Tutorial will provide an option to restart on the last step, in case the user wants to see the Tutorial again.- Setting this to
false
(the default) will not prevent the user from triggering the Tutorial again via other means.
- Setting this to
TutorialDescription::Step
is a bit more complex. Steps may
either be created all at once with the omnibus constructor, or created with the
default constructor and then have each field set individually. The fields of the
struct are as follows:
- Help bubble parameters:
- body_text_id - Localized string ID. The result is placed into
HelpBubbleParams::body_text
. If not set, this Tutorial step is a "hidden step" and will have no bubble. - title_text_id - Localized string ID. The result is placed into
HelpBubbleParams::title_text
. - element_id - Specifies the UI element the step refers to. If this is
not a hidden step, the bubble will anchor to this element. Mandatory
unless
element_name
is set. - arrow - Specifies how the
HelpBubble
for this step will anchor to the target element.
- body_text_id - Localized string ID. The result is placed into
- Interaction sequence parameters; see
InteractionSequence for
details:
- step_type - Specifies the triggering condition of this step.
- event_type - If
step_type
iskCustomEvent
, specifies the custom event the step will transition on. Ignored otherwise. - name_elements_callback - Allows either the current element or some
other element to be "named" for use later in the Tutorial. This allows a
Tutorial to remember elements that may otherwise be ambiguous or not have
an
ElementIdentifier
before the Tutorial runs. - element_name - Specifies that the step will target an element named
via
name_elements_callback
in a previous step, rather than usingelement_id
. The element must have been named and still be visible. - transition_only_on_event - When
step_type
iskShown
orkHidden
, causes this step to start only when a UI element actively becomes visible or loses visibility. Corresponds toInteractionSequence::StepBuilder::SetTransitionOnlyOnEvent()
. - must_remain_visible - Overrides the default "must remain visible"
state of the underlying
InteractionSequence::Step
. Should only be set if the Tutorial won't work properly otherwise.
Notes:
TutorialDescription::Step
is copyable and a step can be added to thesteps
member of multiple relatedTutorialDescription
s.- We are aware that the programming interface for
Step
is a little clunky; at some future point they will be moved to a builder pattern likeFeaturePromoSpecification
. - If you're not sure how to construct your Tutorial, reach out to one of the OWNERS of this library.
Once you have defined your Tutorial; call AddTutorial()
on the
TutorialRegistry provided by your application
and pass both your TutorialIdentifier
and your TutorialDescription
.
For implementation on adding a "New" Badge to Chrome, Googlers can refer to the following document: New Badge How-To and Best Practices.
There are a number of virtual methods that must be implemented before you can use these User Education libraries in a new application, mostly centered around localization, accelerators, and global input focus.
Fortunately for Chromium developers, the browser already has the necessary support built in for Views, WebUI, and Mac-native context menus. You may refer to the following locations for an example that could be extended to other platforms such as ChromeOS:
- UserEducationService - sets up the
various registries and
TutorialService
. - BrowserView - sets up
the
FeaturePromoController
. - browser_user_education_service - registers Chrome-specific IPH and Tutorials.
- Concrete implementations of abstract User Education base classes can be found in c/b/ui/user_education and c/b/ui/views/user_education.