// Copyright 2017 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_AUTOFILL_CONTENT_RENDERER_FORM_SUBMISSION_TRACKER_H_
#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_SUBMISSION_TRACKER_H_

#include <memory>
#include <optional>
#include <variant>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/types/strong_alias.h"
#include "components/autofill/content/renderer/timing.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
#include "components/autofill/core/common/unique_ids.h"
#include "content/public/renderer/render_frame_observer.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_input_element.h"
#include "third_party/blink/public/web/web_local_frame_observer.h"

namespace blink {
class WebFormElementObserver;
}

namespace autofill {

class AutofillAgent;
class PasswordAutofillAgent;
class SynchronousFormCache;

// TODO(crbug.com/40550175): Track the select and checkbox change.
// This class is used to track user's change of form or WebFormControlElement,
// notifies observers of form's change and submission.
class FormSubmissionTracker : public content::RenderFrameObserver,
                              public blink::WebLocalFrameObserver {
 public:
  using ElementDidChangeCallback =
      base::OnceCallback<void(const blink::WebFormControlElement&,
                              const SynchronousFormCache&)>;

  explicit FormSubmissionTracker(
      content::RenderFrame* render_frame,
      AutofillAgent& autofill_agent,
      PasswordAutofillAgent* password_autofill_agent);

  FormSubmissionTracker(const FormSubmissionTracker&) = delete;
  FormSubmissionTracker& operator=(const FormSubmissionTracker&) = delete;

  ~FormSubmissionTracker() override;

  // Same methods as those in blink::WebAutofillClient, but invoked by
  // AutofillAgent.
  void AjaxSucceeded();
  virtual void ElementDisappeared(const blink::WebElement& element);

  // Posts a task to `FormControlDidChangeImpl` to handle form control change
  // asynchronously, as we also don't want to process element while it is
  // changing, and also to work-around a WebKit
  // bug http://bugs.webkit.org/show_bug.cgi?id=16976 ,
  void FormControlDidChange(const blink::WebFormControlElement& element,
                            ElementDidChangeCallback callback);

  // Tells the tracker to track the autofilled `element`. Since autofilling a
  // form or field won't trigger the regular *DidChange events, the tracker
  // won't be notified of this `element` otherwise. This is currently only used
  // by PWM.
  void TrackAutofilledElement(FieldRendererId field_id);

  // Called in order to update submission data when a form is autofilled.
  // `filled_fields_and_forms` represent the fields and forms that were affected
  // by the corresponding autofill operation  and is used to determine an
  // appropriate single element to track.
  //
  // Callers must guarantee that form_util::GetFormByRendererId() and
  // form_util::GetFormControlByRendererId() find the elements in
  // `filled_fields_and_forms`.
  void TrackAutofilledElement(
      const base::flat_map<FieldRendererId, FormRendererId>&
          filled_fields_and_forms);

  // Updates submission data according to the JS value-change event.
  void OnJavaScriptChangedValue(const blink::WebFormControlElement& element);

  // A form_id means that the user last interacted with a FormElement.
  // A field_id means that the user last interacted with a formless control.
  void UpdateLastInteractedElement(
      std::variant<blink::WebFormElement, blink::WebFormControlElement>
          element);
  void ResetLastInteractedElements();

  bool IsTracking() const;

  // Called when current form is no longer submittable, submitted_forms_ is
  // cleared in this method.
  void OnFormNoLongerSubmittable() { submitted_forms_.clear(); }

 private:
  friend class FormSubmissionTrackerTestApi;

  // Synchronous task posted by `FormTracker::FormControlDidChange()`.
  void FormControlDidChangeImpl(FieldRendererId element_id,
                                ElementDidChangeCallback callback);

  // content::RenderFrameObserver:
  void DidCommitProvisionalLoad(ui::PageTransition transition) override;
  void DidFinishSameDocumentNavigation() override;
  void DidStartNavigation(
      const GURL& url,
      std::optional<blink::WebNavigationType> navigation_type) override;
  void WillDetach(blink::DetachReason detach_reason) override;
  void WillSubmitForm(const blink::WebFormElement& form) override;
  void OnDestruct() override;

  // The RenderFrame* is nullptr while the AutofillAgent that owns this
  // FormSubmissionTracker is pending deletion, between OnDestruct() and
  // ~FormSubmissionTracker().
  content::RenderFrame* unsafe_render_frame() const {
    return content::RenderFrameObserver::render_frame();
  }

  // Use unsafe_render_frame() instead.
  template <typename T = int>
  content::RenderFrame* render_frame(T* = 0) const {
    static_assert(
        std::is_void_v<T>,
        "Beware that the RenderFrame may become nullptr by OnDestruct() "
        "because AutofillAgent destructs itself asynchronously. Use "
        "unsafe_render_frame() instead and make test that it is non-nullptr.");
  }

  // content::WebLocalFrameObserver:
  void OnFrameDetached() override {}
  void WillSendSubmitEvent(const blink::WebFormElement& form) override;

  FormRendererId last_interacted_form_id() const {
    return last_interacted_.form_id;
  }

  std::optional<FormData>& provisionally_saved_form() {
    return last_interacted_.saved_state;
  }

  // Notifies agents of the submission of `form_data`.
  void FireHostSubmitEvents(const FormData& form_data,
                            mojom::SubmissionSource source);

  // Returns an approximation of the submitted form. The candidates are:
  // - `provisionally_saved_form_` , because it may be the last-known complete
  //   state of the form (i.e., the form or some fields in the form may have
  //   been removed afterwards).
  // - `last_interacted_form_`'s current `FormData`, because this corresponds to
  //   the last form element the user interacted with.
  // - `submitted_form_element`'s current `FormData`, because the caller
  //    specified that this is the form element that was submitted, regardless
  //    of autofill's tracking.
  // When `submitted_form_element` is provided the function makes sure
  // that the returned form corresponds to that DOM element.
  // `source` is the type of submission requesting the submitted form.
  std::optional<FormData> GetSubmittedForm(
      mojom::SubmissionSource source,
      std::optional<blink::WebFormElement> submitted_form_element);

  // Virtual for testing.
  virtual void FireFormSubmission(
      mojom::SubmissionSource source,
      std::optional<blink::WebFormElement> submitted_form_element);
  void FireSubmissionIfFormDisappear(mojom::SubmissionSource source);
  bool CanInferFormSubmitted();

  // Tracks the cached element, as well as its ancestors, until it disappears
  // (removed or hidden), then directly infers submission. `source` is the type
  // of submission to fire when the tracked element disappears.
  // TODO(crbug.com/40281981): Remove.
  void TrackElement(mojom::SubmissionSource source);

  // Invoked when the observed element was either removed from the DOM or it's
  // computed style changed to display: none. `source` is the type of submission
  // to be inferred in case this function is called.
  // TODO(crbug.com/40281981): Remove.
  void ElementWasHiddenOrRemoved(mojom::SubmissionSource source);

  blink::WebDocument GetDocument() const;

  struct {
    FormRendererId form_id;
    FieldRendererId formless_element_id;
    // Used when a FormData version of the last interacted form is needed if
    // we'd like to avoid extracting using `form`.
    std::optional<FormData> saved_state;
  } last_interacted_;

  // TODO(crbug.com/40281981): Remove.
  std::unique_ptr<blink::WebFormElementObserver> form_element_observer_;

  struct {
    bool tracked_element_disappeared = false;
    bool tracked_element_autofilled = false;
    bool finished_same_document_navigation = false;
    bool xhr_succeeded = false;
  } submission_triggering_events_;

  // For each form, identified by its renderer ID, keeps track of the sources of
  // observed submissions, so that we avoid firing duplicate submission signals
  // to the driver. See `AutofillAgent::FireHostSubmitEvent` for more details.
  base::flat_map<FormRendererId, DenseSet<mojom::SubmissionSource>>
      submitted_forms_;

  // The respective agents for Autofill and PasswordManager.
  raw_ref<AutofillAgent> autofill_agent_;
  raw_ptr<PasswordAutofillAgent> password_autofill_agent_ = nullptr;

  SEQUENCE_CHECKER(form_submission_tracker_sequence_checker_);

  base::WeakPtrFactory<FormSubmissionTracker> weak_ptr_factory_{this};
};

}  // namespace autofill

#endif  // COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_SUBMISSION_TRACKER_H_
