summaryrefslogtreecommitdiff
path: root/chromium/components/user_education/README.md
blob: 6e34e0ca3aefc252b50c02e704e5d760d5ab3c66 (plain)
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.

![How to implement IPH diagram](images/iph-diagram.png)

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/).