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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
# User Education component library
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]
## Upstream dependencies
Familiarity with these libraries are strongly recommended; feel free to reach
out to their respective OWNERS if you have any questions.
* [UI Interaction](/ui/base/interaction/README.md)
* [ElementTracker](/ui/base/interaction/element_tracker.h) - supplies anchor
points for help bubbles
* [InteractionSequence](/ui/base/interaction/interaction_sequence.h) -
describes the situations in which a Tutorial advances to the next step
* [Feature Engagement](/components/feature_engagement/README.md) - used to
evaluate triggering conditions for IPH and New Badge.
## Directory structure
* [common](./common) - contains platform- and framework-agnostic APIs for
working with `HelpBubble`s, **IPH**, and **Tutorials**.
* [test](./test) - contains common code for testing user education primitives
* [views](./views) - contains code required to display a `HelpBubble` in a
Views-based UI, as well as **"New" Badge** primitives.
* [webui](./webui/README.md) - contains code required to display a `HelpBubble`
on a WebUI surface.
## Application integration
The necessary IPH services have already been implemented in Chrome. If
you are interested in extending User Education to another platform, see
the [section](#Adding-User-Education-to-your-application) below.
*Note: The rest of this document introduces User Education concepts and
focuses on using existing services to create in-product help
experiences.*
# Programming API
## Help bubbles
The core presentation element for both IPH and Tutorials is the
[HelpBubble](./common/help_bubble.h). 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](./views/help_bubble_factory_views.h). Each type of
`HelpBubble` is created by a different
[HelpBubbleFactory](./common/help_bubble_factory.h), which is registered at
startup in the global
[HelpBubbleFactoryRegistry](./common/help_bubble_factory_registry.h). 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](./common/help_bubble_params.h) 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 (IPH)
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](./common/feature_promo_controller.h) with
a [FeaturePromoRegistry](./common/feature_promo_registry.h). In order to
add a new IPH, you will need to:
1. Add the `base::Feature` corresponding to the IPH.
2. Register the appropriate
[FeaturePromoSpecification](./common/feature_promo_specification.h)
describing your IPH journey ([Registering your
IPH](#Registering-your-IPH)).
3. Configure the Feature Engagement backend for your IPH journey
([Configuring when your IPH runs](#Configuring-when-your-IPH-runs)).
4. Add hooks into your code to show/hide your IPH and dispatch events
([Talking to the FE backend](#Talking-to-the-FE-backend)).
5. 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()`
### Registering your IPH
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()`](../../chrome/browser/ui/views/frame/browser_view.cc).
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`.
### Configuring when your IPH runs
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.
<!-- TODO(mickeyburks) Add examples for FeatureConfig usage -->
You will need to become familiar with the terminology in the [FE
docs](/components/feature_engagement/README.md), but you will instead
create the configuration through the [FeatureConfig
API](/components/feature_engagement/public/feature_configurations.cc).
### Talking to the FE backend
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
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](./common/tutorial_service.h) with a
[TutorialRegistry](./common/tutorial_registry.h). In order to add a new
Tutorial, you will need to:
1. Declare a [TutorialIdentifier](./common/tutorial_identifier.h) in an
accessible location.
2. Register the `TutorialIdentifier` and
[TutorialDescription](./common/tutorial_description.h)
([Defining and registering your tutorial](#Defining-and-registering-your-Tutorial)).
3. 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.
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)](#in_product-help-iph)).
### Defining and registering your Tutorial
A [Tutorial](./common/tutorial.h) 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](./common/tutorial_description.h) 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 local
`const 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).
* **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.
`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.
* Interaction sequence parameters; see
[InteractionSequence](/ui/base/interaction/interaction_sequence.h) for
details:
* **step_type** - Specifies the triggering condition of this step.
* **event_type** - If `step_type` is `kCustomEvent`, 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 using
`element_id`. The element must have been named and still be visible.
* **transition_only_on_event** - When `step_type` is `kShown` or `kHidden`,
causes this step to start only when a UI element actively becomes visible
or loses visibility. Corresponds to
`InteractionSequence::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 the
`steps` member of multiple related `TutorialDescription`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 like
`FeaturePromoSpecification`.
* 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](./common/tutorial_registry.h) provided by your application
and pass both your `TutorialIdentifier` and your `TutorialDescription`.
## "New" Badge
For implementation on adding a "New" Badge to Chrome, Googlers can refer to the
following document:
[New Badge How-To and Best Practices](https://goto.google.com/new-badge-how-to).
# Adding User Education to your application
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](
/chrome/browser/ui/user_education/user_education_service.h) - sets up the
various registries and `TutorialService`.
* [BrowserView](/chrome/browser/ui/views/frame/browser_view.cc#831) - sets up
the `FeaturePromoController`.
* [browser_user_education_service](
/chrome/browser/ui/views/user_education/browser_user_education_service.h) -
registers Chrome-specific IPH and Tutorials.
* Concrete implementations of abstract User Education base classes can be
found in [c/b/ui/user_education](/chrome/browser/ui/user_education/) and
[c/b/ui/views/user_education](/chrome/browser/ui/views/user_education/).
|