910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_COMPOSE_PROACTIVE_NUDGE_TRACKER_H_
#define CHROME_BROWSER_COMPOSE_PROACTIVE_NUDGE_TRACKER_H_

#include <map>
#include <memory>
#include <optional>
#include <string>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "chrome/browser/compose/proto/compose_optimization_guide.pb.h"
#include "components/autofill/core/browser/foundations/scoped_autofill_managers_observation.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/compose/core/browser/compose_metrics.h"
#include "components/segmentation_platform/public/result.h"

namespace content {
class WebContents;
}

namespace segmentation_platform {
class SegmentationPlatformService;
}

namespace compose {

// This class is a state machine tracking whether the proactive nudge should
// show for Compose. It has the following states:
//   - kInitial,
//   - kWaitingForTimerToStop,
//   - kTimerCanceled,
//   - kWaitingForSegmentation,
//   - kWaitingForProactiveNudgeRequest,
//   - kBlockedBySegmentation,
//   - kShown
//
// Generally, states transition forward through the list (skipping states if
// required). If the active form field changes (or the form loses focus), the
// state is reset to `kInitial`.
//
// The state is represented by a unique pointer to a `State` struct that is
// reset whenever a field loses focus.
// * If the struct is `null` then the state is `kInitial`.
// * The state remains in `kInitial` until any of the three delay times can be
//   triggered.
// * If the struct has a value, the value of `show_state` differentiates between
//   the remaining states.
// * The Delegate is called at the transition from `kWaitingForSegmentation` to
//   `kWaitingForProactiveNudgeRequest`.
// * Unintuitively, `ProactiveNudgeRequestedForFormField` can cause a transition
//   from kWaitingForProactiveNudgeRequest to `kShown`. Compose interacts with
//   Autofill such that it cannot directly show the nudge; instead it requests
//   the Autofill Agent for the current frame to ask for values to fill. Thus,
//   the entry point is the same both for new nudge states, and for the final
//   step of actually showing the nudge. Thus, the only way to transition to
//   `kShown` is to call after the tracker has entered the state
//   `kWaitingForProactiveNudgeRequest`.
class ProactiveNudgeTracker : public autofill::AutofillManager::Observer {
 public:
  using FallbackShowResult = base::RepeatingCallback<float()>;

  class Delegate {
   public:
    virtual void ShowProactiveNudge(autofill::FormGlobalId form,
                                    autofill::FieldGlobalId field,
                                    compose::ComposeEntryPoint entry_point) = 0;

    virtual compose::PageUkmTracker* GetPageUkmTracker() = 0;

    // Return the ComposeHintMetadata for the associated page. If no hint is
    // available return an empty ComposeHintMetadata object.
    virtual compose::ComposeHintMetadata GetComposeHintMetadata() = 0;

    // Compared with compose's Config random nudge probability to determine if
    // we should show the nudge if segmentation fails.
    virtual float SegmentationFallbackShowResult();

    // Returns a random number between 0 and 1. Controls whether the proactive
    // nudge is force-shown when segmentation is enabled.
    virtual float SegmentationForceShowResult();
  };

  enum class ShowState {
    kInitial,
    kWaitingForTimerToStop,
    kTimerCanceled,
    kWaitingForSegmentation,
    kWaitingForProactiveNudgeRequest,
    kBlockedBySegmentation,
    kShown
  };

  // Signals that determine whether the nudge should be shown.
  struct Signals {
    Signals();
    Signals(Signals&&);
    Signals& operator=(Signals&&);
    ~Signals();

    ukm::SourceId ukm_source_id;
    url::Origin page_origin;
    GURL page_url;
    autofill::FormData form;
    autofill::FormFieldData field;
    // Time the page started to show in a tab.
    base::TimeTicks page_change_time;
  };

  class State final {
   public:
    State();
    ~State();

    Signals signals;
    std::u16string initial_text_value;
    std::optional<segmentation_platform::ClassificationResult>
        segmentation_result;
    bool segmentation_result_ignored_for_training = false;
    base::OneShotTimer timer;
    bool selection_nudge_requested = false;
    bool selection_nudge_shown = false;
    bool timer_canceled = false;

    int text_change_count = 0;

    ShowState show_state = ShowState::kInitial;

    base::WeakPtr<State> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }

   private:
    base::WeakPtrFactory<State> weak_ptr_factory_{this};
  };

  ProactiveNudgeTracker(
      segmentation_platform::SegmentationPlatformService* segmentation_service,
      Delegate* delegate);

  ~ProactiveNudgeTracker() override;

  // Call so that focus events can be obtained from the AutofillManager for this
  // `web_contents`.
  void StartObserving(content::WebContents* web_contents);

  // If the field from `signals` is not the currently matched field sets up
  // internal state to start tracking the new field waiting in `kInitial` for
  // any possible delay timer to start.
  //
  // If the current state is kWaitingForProactiveNudgeRequest, updates the state
  // to kShown.
  //
  // Returns true if the nudge shown but can be.
  bool ProactiveNudgeRequestedForFormField(Signals signals);

  // Returns whether or not the tracker is currently waiting.
  bool IsTimerRunning();

  void FocusChangedInPage();

  void Clear();

  void ComposeSessionCompleted(autofill::FieldGlobalId field_renderer_id,
                               ComposeSessionCloseReason session_close_reason,
                               const compose::ComposeSessionEvents& events);
  void OnUserDisabledNudge(bool single_site_only);

  // autofill::AutofillManager::Observer:
  void OnAfterFocusOnFormField(autofill::AutofillManager& manager,
                               autofill::FormGlobalId form,
                               autofill::FieldGlobalId field) override;
  void OnAfterCaretMovedInFormField(autofill::AutofillManager& manager,
                                    const autofill::FormGlobalId& form,
                                    const autofill::FieldGlobalId& field,
                                    const std::u16string& selection,
                                    const gfx::Rect& caret_bounds) override;
  void OnAfterTextFieldValueChanged(autofill::AutofillManager& manager,
                                    autofill::FormGlobalId form,
                                    autofill::FieldGlobalId field,
                                    const std::u16string& text_value) override;

 private:
  class EngagementTracker;

  void ResetState();

  void UpdateStateForCurrentFormField();
  std::optional<ShowState> CheckForStateTransition();
  void TransitionToState(ShowState new_show_state);

  void BeginWaitingForTimerToStop();
  void BeginTimerCanceled();
  void BeginSegmentation();
  void BeginWaitingForProactiveNudgeRequest();
  void BeginBlockedBySegmentation();
  void BeginShown();

  void ShowTimerElapsed();
  void StartOrRestartTimer();
  bool CanStartFocusTimer();
  bool CanStartTextSettledTimer();
  bool CanStartSelectionTimer();

  void GotClassificationResult(
      const segmentation_platform::ClassificationResult& result);
  bool MatchesCurrentField(autofill::FormGlobalId form,
                           autofill::FieldGlobalId field);
  void CollectTrainingData(
      const segmentation_platform::TrainingRequestId training_request_id,
      ProactiveNudgeDerivedEngagement engagement);

  std::optional<bool> CachedSegmentationResult();

  std::unique_ptr<State> state_;

  bool nudge_currently_requested_ = false;

  // Map indicating if the classification result from the segmentation platform
  // allows the nudge to be shown for previously queried fields.
  std::map<autofill::FieldGlobalId, bool> seen_fields_;

  std::map<autofill::FieldGlobalId, std::unique_ptr<EngagementTracker>>
      engagement_trackers_;

  const raw_ptr<segmentation_platform::SegmentationPlatformService>
      segmentation_service_;
  const raw_ptr<Delegate> delegate_;

  autofill::ScopedAutofillManagersObservation autofill_managers_observation_{
      this};
  base::WeakPtrFactory<ProactiveNudgeTracker> weak_ptr_factory_{this};
};

}  // namespace compose
#endif  // CHROME_BROWSER_COMPOSE_PROACTIVE_NUDGE_TRACKER_H_