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.
Familiarity with these libraries are strongly recommended; feel free to reach out to their respective OWNERS if you have any questions.
HelpBubble
s, IPH, and Tutorials.HelpBubble
in a Views-based UI, as well as “New” Badge primitives.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 on the platforms page.
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:
TrackedElement
the bubble will be anchored toThe 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:
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:
base::Feature
corresponding to the IPH.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:
CreateForSnoozePromo()
except that the “got it” button is replaced by a “learn more” button that launches a Tutorial.You may also call the following methods to add additional features to a bubble:
These are advanced features
ElementIdentifier
.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.
FeatureConfig
).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()
.
It's important to test your IPH to make sure it works the way you expect. IPH bubbles are instrumented with ElementIdentifiers
for Kombucha Interaction Tests but you can test them any way you like.
When testing an IPH it is important to ensure that the IPH is actually allowed to run in the test environment (many test environments disable IPH by default). See Testing Feature Engagement Features for a detailed explanation.
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:
Your application will provide a TutorialService with a TutorialRegistry. In order to add a new Tutorial, you will need to:
TutorialIdentifier
and TutorialDescription (Defining and registering your tutorial).TutorialService::StartTutorial()
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)).
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
:
const char kMyTutorialHistogramPrefix[] = "MyTutorial"; tutorial_description.histograms = user_education::MakeTutorialHistograms<kMyTutorialHistogramPrefix>( tutorial_description.steps.size());
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.TutorialHistogramsImpl<>
template class (via C++ template specialization magic).true
the Tutorial will provide an option to restart on the last step, in case the user wants to see the Tutorial again.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:
HelpBubbleParams::body_text
. If not set, this Tutorial step is a “hidden step” and will have no bubble.HelpBubbleParams::title_text
.element_name
is set.HelpBubble
for this step will anchor to the target element.step_type
is kCustomEvent
, specifies the custom event the step will transition on. Ignored otherwise.ElementIdentifier
before the Tutorial runs.name_elements_callback
in a previous step, rather than using element_id
. The element must have been named and still be visible.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()
.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.Step
is a little clunky; at some future point they will be moved to a builder pattern like FeaturePromoSpecification
.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.
Note that, like IPH, “New” Badge may use a Feature Engagement configuration to limit how often or how many times the user sees the badge. See this section for information on how to configure the Feature Engagement backend for a New Badge.
You should test that your “New” Badge triggers when it is supposed to. If you choose to limit when your badge displays, you will need to enable the corresponding Feature Engagement feature so it can trigger in your test. See this section for more information.
This functions like a mini-tutorial. It should always be triggered by a user action. It opens a new internal page (typically a Settings page), either in a new tab or in the current tab, scrolls to a particular element, and displays a help bubble attached to that element.
This feature is implemented with a single function call at the desired entry point; documentation can be found here.
The Feature Engagement (FE) backend does all the heavy-lifting when it comes to showing your IPH or New Badge after you‘ve chosen your trigger point in your feature’s code. All you need to do is configure how often your promo should show and how it interacts with other IPH.
You will need to become familiar with the terminology in the FE docs.
There are two ways to specify the configuration your IPH or “New” Badge:
Obviously, (1) is preferred.
The biggest difference between New Badge and IPH is that “New” Badge should typically have “blocking” and “blocked by” set to NONE (as New Badge is not exclusive with other promotions). Whereas IPH should typically have ALL, as we never want to show two IPH at the same time, and rarely even in the same browser session.
The browser_tests
and interactive_ui_tests
builds (and all test jobs that are based on these builds) block the Feature Engagement features by default. This is to prevent a random IPH from popping up during an unrelated test, either stealing focus or changing the UI in a way that would cause the test to fail.
The test fixtures do not actually disable every IPH feature (they used to, but that broke field trials). Instead, they set a global state that blocks all Feature Engagement from triggering.
You can then selectively re-enable specific features:
ScopedIphFeatureList
to your test fixture class.SetUp()
(before calling the base SetUp()
method), call one of the Init...()
methods.There are several different ways to enable a feature:
InitForDemo()
turns on the specified feature and makes it so that the IPH or New Badge always triggers, even if the FE preconditions aren't met. Useful for testing the UI itself, or making sure the trigger point in code is correct.InitWithExistingFeatures()
does not enable or disable any features, but allows the specific IPH(s) to run if they are already enabled by default or via field trial. (experimental - let us know if this does not work)InitAndEnableFeatures()
turns on the listed feature flags (discarding any field trials that overlap with the enabled features) and allows any Feature Engagement features in the list to trigger.InitAndEnableFeaturesWithParameters()
turns on the listed feature flags (discarding any field trials that overlap with the enabled features) and allows any Feature Engagement features in the list to trigger. The specific configurations given in the list will be used.Notes:
ScopedIphFeatureList
works like ScopedFeatureList
, and as such, allows you to enable/disable any feature, not just Feature Engagement features.
Any time you do not specify a configuration when enabling a Feature Engagement feature (or specify an empty configuration), the system will fall back to using the configuration from feature_configurations.cc
. This is useful for avoiding duplicating code - but feel free to override a configuration if you need to.