File: tutorial_description.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (595 lines) | stat: -rw-r--r-- 22,670 bytes parent folder | download | duplicates (6)
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_TUTORIAL_DESCRIPTION_H_
#define COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_TUTORIAL_DESCRIPTION_H_

#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_education/common/help_bubble/help_bubble_params.h"
#include "components/user_education/common/user_education_metadata.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"

namespace user_education {

// Holds the data required to properly store histograms for a given tutorial.
// Abstract base class because best practice is to statically declare
// histograms and so we need some compile-time polymorphism to actually
// implement the RecordXXX() calls.
//
// Use MakeTutorialHistograms() below to create a concrete instance of this
// class.
class TutorialHistograms {
 public:
  TutorialHistograms() = default;
  TutorialHistograms(const TutorialHistograms& other) = delete;
  void operator=(const TutorialHistograms& other) = delete;
  virtual ~TutorialHistograms() = default;

  // Records whether the tutorial was completed or not.
  virtual void RecordComplete(bool value) = 0;

  // Records the step on which the tutorial was aborted.
  virtual void RecordAbortStep(int step) = 0;

  // Records whether, when an IPH offered the tutorial, the user opted into
  // seeing the tutorial or not.
  virtual void RecordIphLinkClicked(bool value) = 0;

  // Records whether, when an IPH offered the tutorial, the user opted into
  // seeing the tutorial or not.
  virtual void RecordStartedFromWhatsNewPage(bool value) = 0;

  // This is used for consistency-checking only.
  virtual const std::string& GetTutorialPrefix() const = 0;
};

namespace internal {

constexpr char kTutorialHistogramPrefix[] = "Tutorial.";

template <const char histogram_name[]>
class TutorialHistogramsImpl : public TutorialHistograms {
 public:
  explicit TutorialHistogramsImpl(int max_steps)
      : histogram_name_(histogram_name),
        completed_name_(kTutorialHistogramPrefix + histogram_name_ +
                        ".Completion"),
        aborted_name_(kTutorialHistogramPrefix + histogram_name_ +
                      ".AbortStep"),
        iph_link_clicked_name_(kTutorialHistogramPrefix + histogram_name_ +
                               ".IPHLinkClicked"),
        whats_new_page_name_(kTutorialHistogramPrefix + histogram_name_ +
                             ".StartedFromWhatsNewPage"),
        max_steps_(max_steps) {}
  ~TutorialHistogramsImpl() override = default;

 protected:
  void RecordComplete(bool value) override {
    UMA_HISTOGRAM_BOOLEAN(completed_name_, value);
  }

  void RecordAbortStep(int step) override {
    UMA_HISTOGRAM_EXACT_LINEAR(aborted_name_, step, max_steps_);
  }

  void RecordIphLinkClicked(bool value) override {
    UMA_HISTOGRAM_BOOLEAN(iph_link_clicked_name_, value);
  }

  void RecordStartedFromWhatsNewPage(bool value) override {
    UMA_HISTOGRAM_BOOLEAN(whats_new_page_name_, value);
  }

  const std::string& GetTutorialPrefix() const override {
    return histogram_name_;
  }

 private:
  const std::string histogram_name_;
  const std::string completed_name_;
  const std::string aborted_name_;
  const std::string iph_link_clicked_name_;
  const std::string whats_new_page_name_;
  const int max_steps_;
};

}  // namespace internal

// Call to create a tutorial-specific histograms object for use with the
// tutorial. The template parameter should be a reference to a const char[]
// that is a compile-time constant. Also remember to add a matching entry to
// the "TutorialID" variant in histograms.xml corresponding to your tutorial.
//
// Example:
//   const char kMyTutorialName[] = "MyTutorial";
//   tutorial_descriptions.histograms =
//       MakeTutorialHistograms<kMyTutorialName>(
//           tutorial_description.steps.size());
template <const char* histogram_name>
std::unique_ptr<TutorialHistograms> MakeTutorialHistograms(int max_steps) {
  return std::make_unique<internal::TutorialHistogramsImpl<histogram_name>>(
      max_steps);
}

// A class that manages a temporary state associated with the tutorial.
// A new object via the temporary setup callback when a new tutorial is started
// and maintained through the lifetime of the tutorial.
class ScopedTutorialState {
 public:
  explicit ScopedTutorialState(ui::ElementContext context);
  virtual ~ScopedTutorialState();

  ui::ElementContext context() const { return context_; }

 private:
  const ui::ElementContext context_;
};

// A Struct that provides all of the data necessary to construct a Tutorial.
// A Tutorial Description is a list of Steps for a tutorial. Each step has info
// for constructing the InteractionSequence::Step from the
// TutorialDescription::Step.
struct TutorialDescription {
  using NameElementsCallback =
      base::RepeatingCallback<bool(ui::InteractionSequence*,
                                   ui::TrackedElement*)>;
  using NextButtonCallback =
      base::RepeatingCallback<void(ui::TrackedElement* current_anchor)>;

  using TemporaryStateCallback =
      base::RepeatingCallback<std::unique_ptr<ScopedTutorialState>(
          ui::ElementContext)>;

  TutorialDescription();
  TutorialDescription(TutorialDescription&& other) noexcept;
  TutorialDescription& operator=(TutorialDescription&& other) noexcept;
  ~TutorialDescription();

  using ContextMode = ui::InteractionSequence::ContextMode;
  using ElementSpecifier = std::variant<ui::ElementIdentifier, std::string>;

  // Callback used to determine if the "then" branch of a conditional should be
  // followed. Note that `element` may be null if no matching element exists.
  using ConditionalCallback =
      base::RepeatingCallback<bool(const ui::TrackedElement* element)>;

  class Step {
   public:
    Step();
    Step(const Step& other);
    Step& operator=(const Step& other);
    ~Step();

    // returns true iff all of the required parameters exist to display a
    // bubble.
    bool ShouldShowBubble() const;

    Step& AbortIfVisibilityLost(bool must_remain_visible) {
      must_remain_visible_ = must_remain_visible;
      return *this;
    }

    Step& AbortIfNotVisible() {
      must_be_visible_ = true;
      return *this;
    }

    Step& NameElement(std::string name);

    Step& NameElements(NameElementsCallback name_elements_callback) {
      name_elements_callback_ = std::move(name_elements_callback);
      return *this;
    }

    Step& InAnyContext() {
      context_mode_ = ContextMode::kAny;
      return *this;
    }

    Step& InSameContext() {
      context_mode_ = ContextMode::kFromPreviousStep;
      return *this;
    }

    ui::ElementIdentifier element_id() const { return element_id_; }
    std::string element_name() const { return element_name_; }
    ui::InteractionSequence::StepType step_type() const { return step_type_; }
    ui::CustomElementEventType event_type() const { return event_type_; }
    int title_text_id() const { return title_text_id_; }
    int body_text_id() const { return body_text_id_; }
    int screenreader_text_id() const { return screenreader_text_id_; }
    HelpBubbleArrow arrow() const { return arrow_; }
    std::optional<bool> must_remain_visible() const {
      return must_remain_visible_;
    }
    std::optional<bool> must_be_visible() const { return must_be_visible_; }
    bool transition_only_on_event() const { return transition_only_on_event_; }
    const NameElementsCallback& name_elements_callback() const {
      return name_elements_callback_;
    }
    ContextMode context_mode() const { return context_mode_; }
    const NextButtonCallback& next_button_callback() const {
      return next_button_callback_;
    }
    const HelpBubbleParams::ExtendedProperties& extended_properties() const {
      return extended_properties_;
    }
    ui::InteractionSequence::SubsequenceMode subsequence_mode() const {
      return subsequence_mode_;
    }
    const auto& branches() const { return branches_; }

   protected:
    Step(ElementSpecifier element,
         ui::InteractionSequence::StepType step_type,
         HelpBubbleArrow arrow = HelpBubbleArrow::kNone,
         ui::CustomElementEventType event_type = ui::CustomElementEventType());

    // The element used by interaction sequence to observe and attach a bubble.
    ui::ElementIdentifier element_id_;

    // The element, referred to by name, used by the interaction sequence
    // to observe and potentially attach a bubble. must be non-empty.
    std::string element_name_;

    // The step type for InteractionSequence::Step.
    ui::InteractionSequence::StepType step_type_ =
        ui::InteractionSequence::StepType::kShown;

    // The event type for the step if `step_type` is kCustomEvent.
    ui::CustomElementEventType event_type_ = ui::CustomElementEventType();

    // The title text to be populated in the bubble.
    int title_text_id_ = 0;

    // The body text to be populated in the bubble.
    int body_text_id_ = 0;

    // The screenreader text to be populated in the bubble.
    int screenreader_text_id_ = 0;

    // The positioning of the bubble arrow.
    HelpBubbleArrow arrow_ = HelpBubbleArrow::kNone;

    // Should the element remain visible through the entire step, this should be
    // set to false for hidden steps and for shown steps that precede hidden
    // steps on the same element. if left empty the interaction sequence will
    // decide what its value should be based on the generated
    // InteractionSequence::StepBuilder
    std::optional<bool> must_remain_visible_ = std::nullopt;

    // If set, determines whether the element in question must be visible at the
    // start of the step. If left empty the interaction sequence will choose a
    // reasonable default.
    std::optional<bool> must_be_visible_;

    // Should the step only be completed when an event like shown or hidden only
    // happens during current step. for more information on the implementation
    // take a look at transition_only_on_event in InteractionSequence::Step
    bool transition_only_on_event_ = false;

    // lambda which is called on the start callback of the InteractionSequence
    // which provides the interaction sequence and the current element that
    // belongs to the step. The intention for this functionality is to name one
    // or many elements using the Framework's Specific API finding an element
    // and naming it OR using the current element from the sequence as the
    // element for naming. The return value is a boolean which controls whether
    // the Interaction Sequence should continue or not. If false is returned
    // the tutorial will abort
    NameElementsCallback name_elements_callback_ = NameElementsCallback();

    // Where to search for the step's target element. Default is the context the
    // tutorial started in.
    ContextMode context_mode_ = ContextMode::kInitial;

    // Lambda which is called when the "Next" button is clicked in the help
    // bubble associated with this step. Note that a "Next" button won't render:
    // 1. if `next_button_callback` is null
    // 2. if this step is the last step of a tutorial
    NextButtonCallback next_button_callback_ = NextButtonCallback();

    // Platform-specific properties that can be set for a bubble step. If an
    // extended property evolves to warrant cross-platform support, it should be
    // promoted out of extended properties.
    HelpBubbleParams::ExtendedProperties extended_properties_;

    // Used for if-then-else conditionals.
    ui::InteractionSequence::SubsequenceMode subsequence_mode_ =
        ui::InteractionSequence::SubsequenceMode::kAtMostOne;
    std::vector<std::pair<ConditionalCallback, std::vector<Step>>> branches_;

   private:
    friend class Tutorial;
  };

  // TutorialDescription::BubbleStep
  // A bubble step is a step which shows a bubble anchored to an element
  // This requires that the anchor element be visible, so this is always
  // a kShown step.
  //
  // - A bubble step must be passed an element_id or an element_name
  class BubbleStep : public Step {
   public:
    explicit BubbleStep(ElementSpecifier element_specifier)
        : Step(element_specifier, ui::InteractionSequence::StepType::kShown) {}

    BubbleStep& SetBubbleTitleText(int title_text) {
      title_text_id_ = title_text;
      return *this;
    }

    BubbleStep& SetBubbleBodyText(int body_text_id) {
      body_text_id_ = body_text_id;
      return *this;
    }

    BubbleStep& SetBubbleScreenreaderText(int screenreader_text_id) {
      screenreader_text_id_ = screenreader_text_id;
      return *this;
    }

    BubbleStep& SetBubbleArrow(HelpBubbleArrow arrow) {
      arrow_ = arrow;
      return *this;
    }

    BubbleStep& SetExtendedProperties(
        HelpBubbleParams::ExtendedProperties extended_properties) {
      extended_properties_ = std::move(extended_properties);
      return *this;
    }

    BubbleStep& AddCustomNextButton(NextButtonCallback next_button_callback) {
      next_button_callback_ = std::move(next_button_callback);
      return *this;
    }

    BubbleStep& AddDefaultNextButton();
  };

  // TutorialDescription::HiddenStep
  // A hidden step has no bubble and waits for a UI event to occur on
  // a particular element.
  //
  // - A hidden step must be passed an element_id or an element_name
  class HiddenStep : public Step {
   public:
    // Transition to the next step after a show event occurs
    static HiddenStep WaitForShowEvent(ElementSpecifier element_specifier) {
      HiddenStep step(element_specifier,
                      ui::InteractionSequence::StepType::kShown);
      step.transition_only_on_event_ = true;
      return step;
    }

    // Transition to the next step after a hide event occurs
    static HiddenStep WaitForHideEvent(ElementSpecifier element_specifier) {
      HiddenStep step(element_specifier,
                      ui::InteractionSequence::StepType::kHidden);
      step.transition_only_on_event_ = true;
      return step;
    }

    // Transition to the next step if anchor is, or becomes, visible
    static HiddenStep WaitForShown(ElementSpecifier element_specifier) {
      HiddenStep step(element_specifier,
                      ui::InteractionSequence::StepType::kShown);
      step.transition_only_on_event_ = false;
      return step;
    }

    // Transition to the next step if anchor is, or becomes, hidden
    static HiddenStep WaitForHidden(ElementSpecifier element_specifier) {
      HiddenStep step(element_specifier,
                      ui::InteractionSequence::StepType::kHidden);
      step.transition_only_on_event_ = false;
      return step;
    }

    // Transition to the next step if anchor is, or becomes, activated
    static HiddenStep WaitForActivated(ElementSpecifier element_specifier) {
      return HiddenStep(element_specifier,
                        ui::InteractionSequence::StepType::kActivated);
    }

    template <typename... Args>
    static HiddenStep WaitForOneOf(Args&&... args) {}

   protected:
    explicit HiddenStep(ElementSpecifier element_specifier,
                        ui::InteractionSequence::StepType step_type)
        : Step(element_specifier, step_type) {}
  };

  // TutorialDescription::EventStep
  // An event step is a special case of a HiddenStep that waits for
  // a custom event to be fired programmatically.
  //
  // - This step must be passed an event_id
  // - Additionally, you can also pass an element_id or element_name if
  // the event should occur specifically on a given element
  class EventStep : public Step {
   public:
    explicit EventStep(ui::CustomElementEventType event_type)
        : Step(ui::ElementIdentifier(),
               ui::InteractionSequence::StepType::kCustomEvent,
               HelpBubbleArrow::kNone,
               event_type) {}

    EventStep(ui::CustomElementEventType event_type,
              ElementSpecifier element_specifier)
        : Step(element_specifier,
               ui::InteractionSequence::StepType::kCustomEvent,
               HelpBubbleArrow::kNone,
               event_type) {}
  };

  // TutorialDescription::Create<"Prefix">(step1, step2, ...)
  //
  // Create a tutorial description with the given steps
  // This will also generate the histograms with the given prefix
  template <const char histogram_name[], typename... Args>
  static TutorialDescription Create(Args... steps) {
    TutorialDescription description;
    description.steps = Steps(std::move(steps)...);
    description.histograms =
        user_education::MakeTutorialHistograms<histogram_name>(
            description.steps.size());
    return description;
  }

  // TutorialDescription::Steps(step1, step2, {step3, step4}, ...)
  //
  // Turn steps and step vectors into a flattened vector of steps
  template <typename... Args>
  static std::vector<Step> Steps(Args... steps) {
    std::vector<Step> flat_steps;
    (AddStep(flat_steps, std::move(steps)), ...);
    return flat_steps;
  }

  // Creates a conditional step. Syntax is:
  // ```
  //   If(element[, condition])
  //       .Then(steps...)
  //       [.Else(steps...)]
  // ```
  //
  // The `element` is retrieved from the current context (it will be null if it
  // is not present). The `Then()` or branch is conditionally executed based on:
  //  * The result of calling `condition`, if specified (please note that the
  //    parameter may be null!)
  //  * Whether `element` exists, if `condition` is not specified.
  //
  // If `Else()` is specified, it will be executed if the `Then()` branch is
  // not. At most one branch can execute, and if it fails, the tutorial fails.
  //
  // Because the step does not wait for `element` to become present if it is
  // not yet visible, you may want to insert a `HiddenStep` before your
  // conditional to ensure the correct state:
  // ```
  //   HiddenStep::WaitForShown(kElementId),
  //   If(kElementId, should_do_optional_steps)
  //       .Then(<optional-steps>),
  // ```
  //
  // If you want to wait for one of several elements to be visible (e.g. when
  // the browser might show different variations of a WebUI page based on
  // viewport size), use `WaitForAnyOf()`:
  // ```
  //   // Wait for either version of the surface to appear:
  //   WaitForAnyOf(kPageVariation1ElementId)
  //       .Or(kPageVariation2ElementId),
  //
  //   // Show different Tutorial steps based on which variation appeared:
  //   If(kPageVariation1ElementId)
  //       .Then(<then-steps>)
  //       .Else(<else-steps>),
  // ```
  //
  // Without the WaitForAnyOf, it is possible that neither element would have
  // yet become visible, resulting in the condition failing and the Else()
  // branch always running.
  class If : public Step {
   public:
    // Executes the `Then()` part of this conditional step if `element`
    // satisfies `if_condition`. If `if_condition` is not specified, the
    // default is to execute the `Then()` portion if `element` is visible.
    explicit If(ElementSpecifier element,
                ConditionalCallback if_condition = RunIfPresent())
        : Step(element, ui::InteractionSequence::StepType::kSubsequence) {
      branches_.emplace_back(std::move(if_condition), std::vector<Step>());
    }

    // These tutorial `then_steps` are executed if `if_condition` returns true.
    // Otherwise they are ignored (and the `Else()` steps will be executed if
    // present).
    template <typename... Args>
    If& Then(Args... then_steps) {
      CHECK_EQ(1U, branches_.size());
      CHECK(branches_[0].second.empty());
      branches_[0].second = Steps(std::move(then_steps)...);
      return *this;
    }

    // The tutorial `else_steps` are executed if `if_condition` returns false.
    // This is optional; if not specified and the condition returns false,
    // nothing happens.
    template <typename... Args>
    If& Else(Args... else_steps) {
      CHECK_EQ(1U, branches_.size());
      branches_.emplace_back(AlwaysRun(), Steps(std::move(else_steps)...));
      subsequence_mode_ = ui::InteractionSequence::SubsequenceMode::kExactlyOne;
      return *this;
    }

    // Provides mutable access to the branches in case steps need to be added
    // individually.
    using Step::branches;
    auto& branches() { return branches_; }
  };

  // Constructs a hidden step that waits for at least one of `first` and any
  // additional elements added through `Or()`.
  //
  // This is usually used before an `If()` to ensure that one of several
  // different elements is present. See documentation for `If()` for more
  // information.
  class WaitForAnyOf : public HiddenStep {
   public:
    explicit WaitForAnyOf(ElementSpecifier first, bool wait_for_event = false);
    WaitForAnyOf& Or(ElementSpecifier element, bool wait_for_event = false);
  };

  // The list of TutorialDescription steps.
  std::vector<Step> steps;

  // The histogram data to use. Use MakeTutorialHistograms() above to create a
  // value to use, if you want to record specific histograms for this tutorial.
  std::unique_ptr<TutorialHistograms> histograms;

  // The callback for the tutorial which returns a scoped object which
  // manages temporary state that is maintained through the lifetime of the
  // tutorial.
  TemporaryStateCallback temporary_state_callback;

  // The ability for the tutorial to be restarted. In some cases tutorials can
  // leave the UI in a state where it can not re-run the tutorial. In these
  // cases this flag should be set to false so that the restart tutorial button
  // is not displayed.
  bool can_be_restarted = false;

  // The text ID to use for the complete button at the end of the tutorial.
  int complete_button_text_id = IDS_TUTORIAL_CLOSE_TUTORIAL;

  // Holds metadata about the tutorial.
  Metadata metadata;

 private:
  static void AddStep(std::vector<Step>& dest, Step step) {
    dest.emplace_back(std::move(step));
  }
  static void AddStep(std::vector<Step>& dest, const std::vector<Step>& src) {
    for (auto& step : src) {
      dest.emplace_back(step);
    }
  }

  static ConditionalCallback AlwaysRun();
  static ConditionalCallback RunIfPresent();
};

}  // namespace user_education

#endif  // COMPONENTS_USER_EDUCATION_COMMON_TUTORIAL_TUTORIAL_DESCRIPTION_H_