910e62b5创建于 1月15日历史提交
// 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 UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_
#define UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_

#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <variant>

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_specifier.h"
#include "ui/base/interaction/element_tracker.h"

namespace ui {

// Follows an expected sequence of user-UI interactions and provides callbacks
// at each step. Useful for creating interaction tests and user tutorials.
//
// An interaction sequence consists of an ordered series of steps, each of which
// refers to an interface element tagged with a ElementIdentifier and each of
// which represents that element being either shown, activated, or hidden. Other
// unrelated events such as element hover or focus are ignored (but could be
// supported in the future).
//
// Each step has an optional callback that is triggered when the expected
// interaction happens, and an optional callback that is triggered when the step
// ends - either because the next step has started or because the user has
// aborted the sequence (typically by dismissing UI such as a dialog or menu,
// resulting in the element from the current step being hidden/destroyed). Once
// the first callback is called/the step starts, the second callback will always
// be called.
//
// Furthermore, when the last step in the sequence completes, in addition to its
// end callback, an optional sequence-completed callback will be called. If the
// user aborts the sequence or if this object is destroyed, then an optional
// sequence-aborted callback is called instead.
//
// To use a InteractionSequence, start with a builder:
//
//  sequence_ = InteractionSequence::Builder()
//      .SetCompletedCallback(base::BindOnce(...))
//      .AddStep(InteractionSequence::WithInitialElement(initial_element))
//      .AddStep(InteractionSequence::StepBuilder()
//          .SetElement(kDialogElementID)
//          .SetType(StepType::kShown)
//          .SetStartCallback(...)
//          .Build())
//      .AddStep(...)
//      .Build();
//  sequence_->Start();
//
// For more detailed instructions on using the ui/base/interaction library, see
// README.md in this folder.
//
class COMPONENT_EXPORT(UI_BASE_INTERACTION) InteractionSequence {
 public:
  // The type of event that is expected to happen next in the sequence.
  enum class StepType {
    // Represents the element with the specified ID becoming visible to the
    // user, or already being visible when the step starts.
    kShown,
    // Represents an element with the specified ID becoming activated by the
    // user (for buttons or menu items, being clicked). If the element goes away
    // before the step start callback can be called, null will be passed to the
    // step start callback (you can avoid this by setting step start callback
    // mode to immediate).
    kActivated,
    // Represents an element with the specified ID becoming hidden or destroyed,
    // or no elements with the specified ID being visible. If there is no
    // matching element or the element disappears before the start callback can
    // be called, null will be passed to the start callback.
    kHidden,
    // Represents a custom event with a specific custom event type. You may
    // further specify a required element name or ID to filter down which
    // events you actually want to step on vs. ignore.
    kCustomEvent,
    // Represents one or more nested, conditional subsequences. An element may
    // be provided for use in `SubsequenceCondition` checks. See
    // `SubsequenceMode` for more information on how subsequences work.
    //
    // Note that while a subsequence step can have an element name or ID, it is
    // not required. Furthermore, the element will be located at the start of
    // the step and if it is not present, null will be passed to the
    // SubsequenceCondition (unless must_be_visible is true, in which case the
    // step will fail). This allows subsequences to be conditional on the
    // presence of an element.
    //
    // Known limitations:
    // - If the triggering condition for the step following this one occurs
    //   during execution of one of the subsequences, it may be missed/lost.
    // - If there is no element specified, or the element does not exist, then
    //   the following step will not be able to effectively use
    //   ContextMode::kFromPreviousStep.
    kSubsequence,
    kMaxValue = kSubsequence
  };

  // Describes how the subsequences in a `StepType::kSubsequence` step are
  // executed.
  enum class SubsequenceMode {
    // The first subsequence whose condition is met is executed, and the step
    // finishes if the subsequence completes. If no subsequences run, the step
    // succeeds.
    kAtMostOne,
    // The first subsequence whose condition is met is executed, and the step
    // finishes if the subsequence completes. If no subsequences run, the step
    // fails.
    kExactlyOne,
    // All subsequences whose conditions are met are executed, and the step
    // finishes if any of the subsequences completes successfully. The others
    // may fail, and are destroyed immediately as soon as the first succeeds. If
    // no sequences run, the step fails.
    kAtLeastOne,
    // All subsequences whose conditions are met are executed, and the step
    // finishes if all of the subsequences complete. If no sequences run, the
    // step succeeds.
    //
    // This is the default behavior.
    kAll,
    kMaxValue = kAll
  };

  // Details why a sequence was aborted.
  enum class AbortedReason {
    // External code destructed this object before the sequence could complete.
    kSequenceDestroyed,
    // The starting element was hidden before the sequence started.
    kElementHiddenBeforeSequenceStart,
    // An element should have been visible at the start of a step but was not.
    kElementNotVisibleAtStartOfStep,
    // An element should have remained visible during a step but did not.
    kElementHiddenDuringStep,
    // An element was hidden during an asynchronous step between trigger and
    // step start.
    kElementHiddenBetweenTriggerAndStepStart,
    // One or more subsequences were expected to run, but none could due to
    // failed preconditions.
    kNoSubsequenceRun,
    // One or more subsequences needed to succeed, but one or more unexpectedly
    // failed. Details will be the failed step from the first subsequence that
    // should have completed but did not.
    kSubsequenceFailed,
    // The sequence was explicitly failed as part of a test.
    kFailedForTesting,
    // A timeout was reached during execution. This might not be fatal, but the
    // current state can be dumped regardless.
    kSequenceTimedOut,
    // A discrete triggering condition for the following step happened before
    // the start callback for the current callback was even run.
    kSubsequentStepTriggeredTooEarly,
    // A triggering condition for the following step happened while waiting for
    // the previous step to complete, but then the state changed in such a way
    // that the trigger became invalid.
    kSubsequentStepTriggerInvalidated,
    // Update this if values are added to the enumeration.
    kMaxValue = kSubsequentStepTriggerInvalidated
  };

  // Specifies how the context for a step is determined.
  enum class ContextMode {
    // Use the initial context for the sequence.
    kInitial,
    // Search for the element in any context.
    //
    // Note that these events are sent after events for specific contexts, if
    // you have two steps in succession that are both `StepType::kActivated`
    // with the second one having `ContextMode::kAny`, then the same activation
    // could conceivably trigger both steps. Fortunately, this sort of thing
    // doesn't happen in any of the real-world use cases; if it does it will be
    // fixed with special case code.
    kAny,
    // Inherits the context from the previous step. Cannot be used on the first
    // step in a sequence.
    kFromPreviousStep
  };

  // Determines how each step's start callback executes - and by extension, when
  // the following step is staged for execution.
  enum class StepStartMode {
    // The start callback will be posted and execute on a fresh call stack,
    // after all immediately-pending tasks. Even if there is no start callback,
    // staging of the following step will be done on that fresh call stack,
    // preventing multiple steps from cascading on the same triggering callback.
    // This is the default.
    kAsynchronous,
    // The start callback will execute immediately as soon as the step trigger
    // is detected. This could be in the middle of an operation, such as a set
    // of UI elements being shown together. Use this only when waiting even a
    // small amount of time would cause a problem (which is almost never).
    kImmediate
  };

  // Determines whether a subsequence will run. `seq` is the parent sequence,
  // and `el` is the reference element, and may be null if the element is not
  // specified or if there is no matching element. This is unlike other steps
  // where an element is typically required to be present before the step can
  // proceed.
  using SubsequenceCondition =
      base::OnceCallback<bool(const InteractionSequence* seq,
                              const TrackedElement* el)>;

  // Returns a callback that causes the subsequence to always run.
  static SubsequenceCondition AlwaysRun();

  // A step context is either an explicit context or a ContextMode.
  using StepContext = std::variant<ElementContext, ContextMode>;

  // Callback when a step in the sequence starts. If |element| is no longer
  // available, it will be null.
  using StepStartCallback =
      base::OnceCallback<void(InteractionSequence* sequence,
                              TrackedElement* element)>;

  // Callback when a step in the sequence ends. If |element| is no longer
  // available, it will be null.
  using StepEndCallback = base::OnceCallback<void(TrackedElement* element)>;

  // Information passed when a sequence fails or is aborted.
  struct COMPONENT_EXPORT(UI_BASE_INTERACTION) AbortedData {
    AbortedData();
    ~AbortedData();
    AbortedData(const AbortedData& other);
    AbortedData& operator=(const AbortedData& other);

    // The index of the step where the failure occurred. 0 before the sequence
    // starts, and is incremented on each step transition after the previous
    // step's end callback is called, or if the next step's precondition fails
    // (so that it refers to the correct step).
    int step_index = 0;

    // The description of the failed step.
    std::string step_description;

    // The step type of the failed step.
    StepType step_type = StepType::kShown;

    // A reference to the element used by the failed step. This is a weak
    // reference and may be null if the element was hidden or destroyed.
    SafeElementReference element;

    // The identifier of the element used by the failed step.
    ElementIdentifier element_id;

    // The context of the element expected by the failed step, or null if
    // unknown/unspecified.
    ui::ElementContext context;

    // The reason the step failed/the sequence was aborted.
    AbortedReason aborted_reason = AbortedReason::kSequenceDestroyed;

    // If this failure was due to a subsequence failing, the failure information
    // for the subsequences will be stored here.
    //
    // This also stores the next step when a step fails due to e.g. an element
    // losing visibility.
    std::vector<std::optional<AbortedData>> subsequence_failures;
  };

  // Callback for when the user aborts the sequence by failing to follow the
  // sequence of steps, or if this object is deleted after the sequence starts,
  // or when the sequence fails for some other reason.
  //
  // The most recent step is described by the `AbortedData` block.
  using AbortedCallback = base::OnceCallback<void(const AbortedData&)>;

  using CompletedCallback = base::OnceClosure;

  struct Configuration;
  class StepBuilder;
  struct SubsequenceData;

  struct COMPONENT_EXPORT(UI_BASE_INTERACTION) Step {
    Step();
    Step(const Step& other) = delete;
    void operator=(const Step& other) = delete;
    ~Step();

    bool uses_named_element() const { return !element_name.empty(); }

    StepType type = StepType::kShown;
    ElementIdentifier id;
    CustomElementEventType custom_event_type;
    std::string element_name;
    StepContext context = ContextMode::kInitial;

    // This is used for testing; while `context` can be updated as part of
    // sequence execution, this will never change.
    bool in_any_context = false;

    // These will always have values when the sequence is built, but can be
    // unspecified during construction. If unspecified, they will be set to
    // appropriate defaults for `type`.
    std::optional<bool> must_be_visible;
    std::optional<bool> must_remain_visible;
    bool transition_only_on_event = false;

    StepStartCallback start_callback;
    std::optional<StepStartMode> step_start_mode;
    StepEndCallback end_callback;
    ElementTracker::Subscription subscription;

    // Tracks the element associated with the step, if known. We could use a
    // SafeElementReference here, but there are cases where we want to do
    // additional processing if this element goes away, so we'll add the
    // listeners manually instead.
    raw_ptr<TrackedElement, DanglingUntriaged> element = nullptr;

    // Provides a useful description for debugging that can be read or passed
    // to the abort callback on failure.
    std::string description;

    // These only apply if the type of the step is kSubsequence.
    SubsequenceMode subsequence_mode = SubsequenceMode::kAll;
    std::vector<SubsequenceData> subsequence_data;
  };

  // Use a Builder to specify parameters when creating an InteractionSequence.
  class COMPONENT_EXPORT(UI_BASE_INTERACTION) Builder {
   public:
    Builder();
    Builder(Builder&& other);
    Builder& operator=(Builder&& other);
    ~Builder();

    // Sets the callback if the user exits the sequence early.
    Builder& SetAbortedCallback(AbortedCallback callback);

    // Sets the callback if the user completes the sequence.
    // Convenience method so that the last step's end callback doesn't need to
    // have special logic in it.
    Builder& SetCompletedCallback(CompletedCallback callback);

    // Adds an expected step in the sequence. All sequences must have at least
    // one step.
    Builder& AddStep(std::unique_ptr<Step> step);

    // Convenience methods to add a step when using a StepBuilder.
    Builder& AddStep(StepBuilder& step_builder);

    // Convenience method for cases where we don't have an lvalue.
    Builder& AddStep(StepBuilder&& step_builder);

    // Sets the context for this sequence. Must be called if no step is added
    // by element or has had SetContext() called. Typically the initial step of
    // a sequence will use WithInitialElement() so it won't be necessary to call
    // this method.
    Builder& SetContext(ElementContext context);

    // Sets the default step start mode for this sequence. This will acquire
    // a default value if not set; for subsequences, the value of the parent
    // sequence is inherited instead if no value is set.
    Builder& SetDefaultStepStartMode(StepStartMode step_start_mode);

    // Creates the InteractionSequence. You must call Start() to initiate the
    // sequence; sequences cannot be re-used, and a Builder is no longer valid
    // after Build() is called.
    std::unique_ptr<InteractionSequence> Build();

   private:
    friend class InteractionSequence;

    std::unique_ptr<InteractionSequence> BuildSubsequence(
        const Configuration* owner_config,
        const Step* owning_step);

    std::unique_ptr<Configuration> configuration_;
  };

  // Used inline in calls to Builder::AddStep to specify step parameters.
  //
  // Methods intended to be used in Kombucha test bodies have rvalue versions
  // to reduce the need for std::move().
  class COMPONENT_EXPORT(UI_BASE_INTERACTION) StepBuilder {
   public:
    StepBuilder();
    ~StepBuilder();
    StepBuilder(StepBuilder&& other);
    StepBuilder& operator=(StepBuilder&& other);

    // Sets the identifier or name of the element to use for this step.
    // `SetElement()` or equivalent is required for all step types except
    // `kCustomEvent`.
    StepBuilder& SetElement(ElementSpecifier element_specifier);

    // Sets the unique identifier for this step. Either this or
    // SetElementName() is required for all step types except kCustomEvent.
    // DEPRECATED: use `SetElement()`.
    StepBuilder& SetElementID(ElementIdentifier element_id);

    // Sets the step to refer to a named element instead of an
    // ElementIdentifier. Either this or SetElementID() is required for all
    // step types other than kCustomEvent.
    // DEPRECATED: use `SetElement()`.
    StepBuilder& SetElementName(std::string_view name);

    // Sets the context for the step; useful for setting up the initial
    // element of the sequence if you do not know the context ahead of time, or
    // to specify that a step should not use the default context.
    StepBuilder& SetContext(StepContext context) &;
    StepBuilder&& SetContext(StepContext context) &&;

    // Sets the type of step. Required. You must set `event_type` if and only
    // if `step_type` is kCustomEvent.
    StepBuilder& SetType(
        StepType step_type,
        CustomElementEventType event_type = CustomElementEventType());

    // Changes the subsequence mode from the default. See `SubsequenceMode` for
    // details. Implicitly sets the step type to kSubsequence.
    StepBuilder& SetSubsequenceMode(SubsequenceMode subsequence_mode);

    // Adds a subsequence to the step. The subsequence will run if `condition`
    // returns true. Implicitly changes the step type to kSubsequence.
    //
    // The subsequence will not actually be built until it is needed. It will
    // inherit the named elements of its parent unless otherwise specified.
    StepBuilder& AddSubsequence(Builder subsequence,
                                SubsequenceCondition condition = AlwaysRun());

    // Indicates that the specified element must be visible at the start of the
    // step. Defaults to true for StepType::kActivated, false otherwise. Failure
    // To meet this condition will abort the sequence.
    StepBuilder& SetMustBeVisibleAtStart(bool must_be_visible) &;
    StepBuilder&& SetMustBeVisibleAtStart(bool must_be_visible) &&;

    // Indicates that the specified element must remain visible throughout the
    // step once it has been shown. Defaults to true for StepType::kShown, false
    // otherwise (and incompatible with StepType::kHidden). Failure to meet this
    // condition will abort the sequence.
    StepBuilder& SetMustRemainVisible(bool must_remain_visible) &;
    StepBuilder&& SetMustRemainVisible(bool must_remain_visible) &&;

    // For kShown and kHidden events, if set to true, only allows a step
    // transition to happen when a "shown" or "hidden" event is received, and
    // not if an element is already visible (in the case of kShown steps) or no
    // elements are visible (in the case of kHidden steps).
    //
    // Default is false. Has no effect on kActiated events which are discrete
    // rather than stateful.
    //
    // Note: Does not track events fired during previous step's start callback,
    // so should not be used in automated interaction testing. The default
    // behavior should be fine for these cases.
    //
    // Note: Be careful when setting this value to true, as it increases the
    // likelihood of ending up in a state where a failure cannot be detected;
    // that is, waiting for an element to appear and then it... never does. In
    // this case, you will need an external way to terminate the sequence (a
    // timeout, user interaction, etc.)
    StepBuilder& SetTransitionOnlyOnEvent(bool transition_only_on_event) &;
    StepBuilder&& SetTransitionOnlyOnEvent(bool transition_only_on_event) &&;

    // Sets the callback called at the start of the step.
    StepBuilder& SetStartCallback(StepStartCallback start_callback);

    // Sets the callback called at the start of the step. Convenience method
    // that eliminates the InteractionSequence argument if you do not need it.
    StepBuilder& SetStartCallback(
        base::OnceCallback<void(TrackedElement*)> start_callback);

    // Sets the callback called at the start of the step. Convenience method
    // that eliminates both arguments if you do not need them.
    StepBuilder& SetStartCallback(base::OnceClosure start_callback);

    // Sets the step start mode for this step. If not set, inherits the default
    // mode from its sequence.
    StepBuilder& SetStepStartMode(StepStartMode step_start_mode) &;
    StepBuilder&& SetStepStartMode(StepStartMode step_start_mode) &&;

    // Sets the callback called at the end of the step. Guaranteed to be called
    // if the start callback is called, before the start callback of the next
    // step or the sequence aborted or completed callback. Also called if this
    // object is destroyed while the step is still in-process.
    StepBuilder& SetEndCallback(StepEndCallback end_callback);

    // Sets the callback called at the end of the step. Convenience method if
    // you don't need the parameter.
    StepBuilder& SetEndCallback(base::OnceClosure end_callback);

    // Sets the description of the step.
    StepBuilder& SetDescription(std::string_view description) &;
    StepBuilder&& SetDescription(std::string_view description) &&;

    // Prepends `prefix`, along with a colon and space, to this step's
    // description.
    StepBuilder& AddDescriptionPrefix(std::string_view prefix) &;
    StepBuilder&& AddDescriptionPrefix(std::string_view prefix) &&;

    // Builds the step. The builder will not be valid after calling Build().
    std::unique_ptr<Step> Build();

   private:
    friend class InteractionSequence;
    std::unique_ptr<Step> step_;
  };

  // Returns a step with the following values already set, typically used as the
  // first step in a sequence (because the first element is usually present):
  //   ElementID: element->identifier()
  //   MustBeVisibleAtStart: true
  //   MustRemainVisible: true
  //
  // This is a convenience method and also removes the need to call
  // Builder::SetContext(). Specific framework implementations may provide
  // wrappers around this method that allow direct conversion from framework UI
  // elements (e.g. a views::View) to the target element.
  static std::unique_ptr<Step> WithInitialElement(
      TrackedElement* element,
      StepStartCallback start_callback = StepStartCallback(),
      StepEndCallback end_callback = StepEndCallback());

  ~InteractionSequence();

  // Starts the sequence. All of the elements in the sequence must belong to the
  // same top-level application window (which includes menus, bubbles, etc.
  // associated with that window).
  void Start();

  // Starts the sequence and does not return until the sequence either
  // completes or aborts. Events on the current thread continue to be processed
  // while the method is waiting, so this will not e.g. block the browser UI
  // thread from handling inputs.
  //
  // This is a test-only method since production code applications should
  // always run asynchronously.
  void RunSynchronouslyForTesting();

  // Returns whether the current step uses ContextMode::kAny.
  bool IsCurrentStepInAnyContextForTesting() const;

  // Returns whether the current step is using "immediate" execution mode.
  bool IsCurrentStepImmediateForTesting() const;

  // Explicitly fails the sequence.
  void FailForTesting();

  // Assigns an element to a given name. The name is local to this interaction
  // sequence. It is valid for `element` to be null; in this case, we are
  // explicitly saying "there is no element with this name [yet]".
  //
  // It is safe to call this method from a step start callback, but not a step
  // end or aborted callback, as in the latter case the sequence might be in
  // the process of being destructed.
  void NameElement(TrackedElement* element, std::string_view name);

  // Retrieves a named element, which may be null if we specified "no element"
  // or if the element has gone away.
  //
  // It is safe to call this method from a step start callback, but not a step
  // end or aborted callback, as in the latter case the sequence might be in
  // the process of being destructed.
  TrackedElement* GetNamedElement(const std::string& name);
  const TrackedElement* GetNamedElement(const std::string& name) const;

  // Builds aborted data for the current step and the given reason.
  AbortedData BuildAbortedData(AbortedReason reason) const;

  // Gets a weak pointer to this object.
  base::WeakPtr<InteractionSequence> AsWeakPtr();

 private:
  FRIEND_TEST_ALL_PREFIXES(InteractionSequenceSubsequenceTest, NamedElements);

  // Describes the state of the sequence.
  enum State {
    // [Sub]sequence waiting to be started.
    kNotStarted,
    // No transition is currently in progress.
    kIdle,
    // The end callback of the previous step is running.
    kInEndCallback,
    // The next step has been loaded, and the sequence is preparing to run the
    // new step's start callback.
    kWaitingForStartCallback,
    // The start callback for the new step is running.
    kInStartCallback,
  };

  explicit InteractionSequence(std::unique_ptr<Configuration> configuration,
                               const Step* reference_step);

  // Callbacks from the ElementTracker.
  void OnElementShown(TrackedElement* element);
  void OnElementActivated(TrackedElement* element);
  void OnElementHidden(TrackedElement* element);
  void OnCustomEvent(TrackedElement* element);

  // Callbacks used only during step transitions to cache certain events.
  void OnTriggerDuringStepTransition(TrackedElement* element);
  void OnElementHiddenDuringStepTransition(TrackedElement* element);
  void OnElementHiddenWaitingForEvent(TrackedElement* element);

  // While we're transitioning steps or staging a subsequence, it's possible for
  // an activation that would trigger the following step to come in. This method
  // adds a callback that's valid only during the step transition to watch for
  // this event.
  void MaybeWatchForEarlyTrigger(const Step* current_step);

  // A note on the next three methods - DoStepTransition(), StageNextStep(), and
  // Abort(): To prevent re-entrancy issues, they must always be the final call
  // in any method before it returns. This greatly simplifies the consistency
  // checks and safeguards that need to be put into place to make sure we aren't
  // making contradictory changes to state or calling callbacks in the wrong
  // order.

  // Start the transition from the current step to the next step.
  void StartStepTransition(TrackedElement* element);

  // Finish the transition from the current step to the next step.
  void CompleteStepTransition();

  // Looks at the next step to determine what needs to be done. Called at the
  // start of the sequence and after each subsequent step starts.
  void StageNextStep();

  // Cancels the sequence and cleans up.
  void Abort(AbortedReason reason);

  // Returns true if `name` is non-empty and `element` matches the element
  // with the specified name, or if `name` is empty (indicating we don't care
  // about it being a named element). Otherwise returns false.
  bool MatchesNameIfSpecified(const TrackedElement* element,
                              const std::string& name) const;

  // Returns the next step, or null if none.
  Step* next_step();
  const Step* next_step() const;

  // Returns the context for the current sequence.
  ElementContext context() const;

  // Updates the next step context from the current based on its StepContext.
  // Returns an element context if one is determined; null context if the step
  // allows any context.
  // Do not call for named elements.
  ElementContext UpdateNextStepContext(const Step* current_step);

  // Callbacks for when subsequences terminate.
  using SubsequenceHandle = const void*;
  void OnSubsequenceCompleted(SubsequenceHandle subsequence);
  void OnSubsequenceAborted(SubsequenceHandle subsequence,
                            const AbortedData& aborted_data);
  void BuildSubsequences(const Step* current_step);
  SubsequenceData* FindSubsequenceData(SubsequenceHandle subsequence);

  State state_ = State::kNotStarted;
  int active_step_index_ = 0;
  bool missing_first_element_ = false;
  bool trigger_during_callback_ = false;
  std::unique_ptr<Step> current_step_;
  ElementTracker::Subscription next_step_hidden_subscription_;
  std::unique_ptr<Configuration> configuration_;
  std::map<std::string, SafeElementReference> named_elements_;
  base::OnceClosure quit_run_loop_closure_for_testing_;

  // This is necessary because this object could be deleted during any callback,
  // and we don't want to risk a UAF if that happens.
  base::WeakPtrFactory<InteractionSequence> weak_factory_{this};
};

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(InteractionSequence::StepType step_type, std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(InteractionSequence::AbortedReason reason,
                    std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(InteractionSequence::SubsequenceMode mode,
                    std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(InteractionSequence::StepStartMode mode, std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern void PrintTo(const InteractionSequence::AbortedData& aborted_data,
                    std::ostream* os);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                InteractionSequence::StepType step_type);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                InteractionSequence::AbortedReason reason);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                InteractionSequence::StepStartMode mode);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(std::ostream& os,
                                InteractionSequence::SubsequenceMode mode);

COMPONENT_EXPORT(UI_BASE_INTERACTION)
extern std::ostream& operator<<(
    std::ostream& os,
    const InteractionSequence::AbortedData& aborted_data);

}  // namespace ui

#endif  // UI_BASE_INTERACTION_INTERACTION_SEQUENCE_H_