/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_TextControlState_h
#define mozilla_TextControlState_h

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/EnumSet.h"
#include "mozilla/Maybe.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/Nullable.h"
#include "nsCycleCollectionParticipant.h"
#include "nsITimer.h"

class nsTextControlFrame;
class nsISelectionController;
class nsFrameSelection;
class nsFrame;

namespace mozilla {

class AutoTextControlHandlingState;
class ErrorResult;
class IMEContentObserver;
class TextEditor;
class TextInputListener;
class TextInputSelectionController;

enum class SelectionDirection : uint8_t {
  None,
  Forward,
  Backward,
};

namespace dom {
enum class SelectionMode : uint8_t;
class Element;
class HTMLInputElement;
}  // namespace dom

/**
 * PasswordMaskData stores making information and necessary timer for
 * `TextEditor` instances.
 */
struct PasswordMaskData final {
  // Timer to mask unmasked characters automatically.  Used only when it's
  // a password field.
  nsCOMPtr<nsITimer> mTimer;

  // Unmasked character range.  Used only when it's a password field.
  // If mUnmaskedLength is 0, it means there is no unmasked characters.
  uint32_t mUnmaskedStart = UINT32_MAX;
  uint32_t mUnmaskedLength = 0;

  // Set to true if all characters are masked or waiting notification from
  // `mTimer`.  Otherwise, i.e., part of or all of password is unmasked
  // without setting `mTimer`, set to false.
  bool mIsMaskingPassword = true;

  // Set to true if a manager of the instance wants to disable echoing
  // password temporarily.
  bool mEchoingPasswordPrevented = false;

  MOZ_ALWAYS_INLINE bool IsAllMasked() const {
    return mUnmaskedStart == UINT32_MAX && mUnmaskedLength == 0;
  }
  MOZ_ALWAYS_INLINE uint32_t UnmaskedEnd() const {
    return mUnmaskedStart + mUnmaskedLength;
  }
  MOZ_ALWAYS_INLINE void MaskAll() {
    mUnmaskedStart = UINT32_MAX;
    mUnmaskedLength = 0;
  }
  MOZ_ALWAYS_INLINE void Reset() {
    MaskAll();
    mIsMaskingPassword = true;
  }
  enum class ReleaseTimer { No, Yes };
  MOZ_ALWAYS_INLINE void CancelTimer(ReleaseTimer aReleaseTimer) {
    if (mTimer) {
      mTimer->Cancel();
      if (aReleaseTimer == ReleaseTimer::Yes) {
        mTimer = nullptr;
      }
    }
    if (mIsMaskingPassword) {
      MaskAll();
    }
  }
};

/**
 * TextControlState is a class which is responsible for managing the state of
 * plaintext controls.  This currently includes the following HTML elements:
 *   <input type=text>
 *   <input type=search>
 *   <input type=url>
 *   <input type=telephone>
 *   <input type=email>
 *   <input type=password>
 *   <textarea>
 *
 * This class is held as a member of HTMLInputElement and HTMLTextAreaElement.
 * The public functions in this class include the public APIs which dom/
 * uses. Layout code uses the TextControlElement interface to invoke
 * functions on this class.
 *
 * The design motivation behind this class is maintaining all of the things
 * which collectively are considered the "state" of the text control in a single
 * location. This state includes several things:
 *
 *  * The control's value.  This value is stored in the mValue member, and is
 * only used when there is no frame for the control, or when the editor object
 * has not been initialized yet.
 *
 *  * The control's associated frame.  This value is stored in the mBoundFrame
 * member. A text control might never have an associated frame during its life
 * cycle, or might have several different ones, but at any given moment in time
 * there is a maximum of 1 bound frame to each text control.
 *
 *  * The control's associated editor.  This value is stored in the mTextEditor
 * member. An editor is initialized for the control only when necessary (that
 * is, when either the user is about to interact with the text control, or when
 * some other code needs to access the editor object.  Without a frame bound to
 * the control, an editor is never initialized.  Once initialized, the editor
 * might outlive the frame, in which case the same editor will be used if a new
 * frame gets bound to the text control.
 *
 *  * The anonymous content associated with the text control's frame, including
 * the value div (the DIV element responsible for holding the value of the text
 * control) and the placeholder div (the DIV element responsible for holding the
 * placeholder value of the text control.)  These values are stored in the
 * mRootNode and mPlaceholderDiv members, respectively.  They will be created
 * when a frame is bound to the text control.  They will be destroyed when the
 * frame is unbound from the object.  We could try and hold on to the anonymous
 * content between different frames, but unfortunately that is not currently
 * possible because they are not unbound from the document in time.
 *
 *  * The frame selection controller.  This value is stored in the mSelCon
 * member. The frame selection controller is responsible for maintaining the
 * selection state on a frame.  It is created when a frame is bound to the text
 * control element, and will be destroy when the frame is being unbound from the
 * text control element. It is created alongside with the frame selection object
 * which is stored in the mFrameSel member.
 *
 *  * The editor text listener.  This value is stored in the mTextListener
 * member. Its job is to listen to selection and keyboard events, and act
 * accordingly. It is created when an a frame is first bound to the control, and
 * will be destroyed when the frame is unbound from the text control element.
 *
 *  * The editor's cached value.  This value is stored in the mCachedValue
 * member. It is used to improve the performance of append operations to the
 * text control.  A mutation observer stored in the mMutationObserver has the
 * job of invalidating this cache when the anonymous contect containing the
 * value is changed.
 *
 *  * The editor's cached selection properties.  These vales are stored in the
 *    mSelectionProperties member, and include the selection's start, end and
 *    direction. They are only used when there is no frame available for the
 *    text field.
 *
 *
 * As a general rule, TextControlState objects own the value of the text
 * control, and any attempt to retrieve or set the value must be made through
 * those objects.  Internally, the value can be represented in several different
 * ways, based on the state the control is in.
 *
 *   * When the control is first initialized, its value is equal to the default
 * value of the DOM node.  For <input> text controls, this default value is the
 * value of the value attribute.  For <textarea> elements, this default value is
 * the value of the text node children of the element.
 *
 *   * If the value has been changed through the DOM node (before the editor for
 * the object is initialized), the value is stored as a simple string inside the
 * mValue member of the TextControlState object.
 *
 *   * If an editor has been initialized for the control, the value is set and
 * retrievd via the nsIEditor interface, and is internally managed by the
 * editor as the native anonymous content tree attached to the control's frame.
 *
 *   * If the text control state object is unbound from the control's frame, the
 * value is transferred to the mValue member variable, and will be managed there
 * until a new frame is bound to the text editor state object.
 */

class RestoreSelectionState;

class TextControlState final : public SupportsWeakPtr {
 public:
  using Element = dom::Element;
  using HTMLInputElement = dom::HTMLInputElement;

  static TextControlState* Construct(TextControlElement* aOwningElement);

  // Note that this does not run script actually because of `sHasShutDown`
  // is set to true before calling `DeleteOrCacheForReuse()`.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY static void Shutdown();

  /**
   * Destroy() deletes the instance immediately or later.
   */
  MOZ_CAN_RUN_SCRIPT void Destroy();

  TextControlState() = delete;
  explicit TextControlState(const TextControlState&) = delete;
  TextControlState(TextControlState&&) = delete;

  void operator=(const TextControlState&) = delete;
  void operator=(TextControlState&&) = delete;

  void Traverse(nsCycleCollectionTraversalCallback& cb);
  MOZ_CAN_RUN_SCRIPT_BOUNDARY void Unlink();

  bool IsBusy() const { return !!mHandlingState || mValueTransferInProgress; }

  MOZ_CAN_RUN_SCRIPT TextEditor* GetTextEditor();
  TextEditor* GetExtantTextEditor() const;
  nsISelectionController* GetSelectionController() const;
  nsFrameSelection* GetIndependentFrameSelection() const;
  nsresult BindToFrame(nsTextControlFrame* aFrame);
  MOZ_CAN_RUN_SCRIPT void UnbindFromFrame(nsTextControlFrame* aFrame);
  [[nodiscard]] nsTextControlFrame* GetBoundFrame() const {
    return mBoundFrame;
  }
  MOZ_CAN_RUN_SCRIPT nsresult PrepareEditor(const nsAString* aValue = nullptr);
  void InitializeKeyboardEventListeners();

  /**
   * OnEditActionHandled() is called when mTextEditor handles something
   * and immediately before dispatching "input" event.
   */
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled();

  enum class ValueSetterOption {
    // The call is for setting value to initial one, computed one, etc.
    ByInternalAPI,
    // The value is changed by a call of setUserInput() API from chrome.
    BySetUserInputAPI,
    // The value is changed by changing value attribute of the element or
    // something like setRangeText().
    ByContentAPI,
    // The value is changed by setRangeText(). Intended to prevent silent
    // selection range change.
    BySetRangeTextAPI,
    // Whether SetValueChanged should be called as a result of this value
    // change.
    SetValueChanged,
    // Whether to move the cursor to end of the value (in the case when we have
    // cached selection offsets), in the case when the value has changed.  If
    // this is not set and MoveCursorToBeginSetSelectionDirectionForward
    // is not set, the cached selection offsets will simply be clamped to
    // be within the length of the new value. In either case, if the value has
    // not changed the cursor won't move.
    // TODO(mbrodesser): update comment and enumerator identifier to reflect
    // that also the direction is set to forward.
    MoveCursorToEndIfValueChanged,

    // The value change should preserve undo history.
    PreserveUndoHistory,

    // Whether it should be tried to move the cursor to the beginning of the
    // text control and set the selection direction to "forward".
    // TODO(mbrodesser): As soon as "none" is supported
    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1541454), it should be set
    // to "none" and only fall back to "forward" if the platform doesn't support
    // it.
    MoveCursorToBeginSetSelectionDirectionForward,
  };
  using ValueSetterOptions = EnumSet<ValueSetterOption, uint32_t>;

  /**
   * SetValue() sets the value to aValue with replacing \r\n and \r with \n.
   *
   * @param aValue      The new value.  Can contain \r.
   * @param aOldValue   Optional.  If you have already know current value,
   *                    set this to it.  However, this must not contain \r
   *                    for the performance.
   * @param aOptions    See ValueSetterOption.
   */
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValue(
      const nsAString& aValue, const nsAString* aOldValue,
      const ValueSetterOptions& aOptions);
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValue(
      const nsAString& aValue, const ValueSetterOptions& aOptions) {
    return SetValue(aValue, nullptr, aOptions);
  }

  /**
   * GetValue() returns current value either with or without TextEditor.
   * The result never includes \r.
   */
  void GetValue(nsAString& aValue, bool aForDisplay) const;

  /**
   * ValueEquals() is designed for internal use so that aValue shouldn't
   * include \r character.  It should be handled before calling this with
   * nsContentUtils::PlatformToDOMLineBreaks().
   */
  bool ValueEquals(const nsAString& aValue) const;
  // The following methods are for textarea element to use whether default
  // value or not.
  // XXX We might have to add assertion when it is into editable,
  // or reconsider fixing bug 597525 to remove these.
  void EmptyValue() {
    if (!mValue.IsVoid()) {
      mValue.Truncate();
    }
  }
  bool IsEmpty() const { return mValue.IsEmpty(); }

  const nsAString& LastInteractiveValueIfLastChangeWasNonInteractive() const {
    return mLastInteractiveValue;
  }
  // When an interactive value change happens, we clear mLastInteractiveValue
  // because it's not needed (mValue is the new interactive value).
  void ClearLastInteractiveValue() { mLastInteractiveValue.SetIsVoid(true); }

  Element* GetRootNode();
  Element* GetPreviewNode();

  bool IsSingleLineTextControl() const {
    return mTextCtrlElement->IsSingleLineTextControl();
  }
  bool IsTextArea() const { return mTextCtrlElement->IsTextArea(); }
  bool IsPasswordTextControl() const {
    return mTextCtrlElement->IsPasswordTextControl();
  }
  int32_t GetColsOrDefault() { return mTextCtrlElement->GetColsOrDefault(); }
  int32_t GetWrapCols() {
    int32_t wrapCols = mTextCtrlElement->GetWrapCols();
    MOZ_ASSERT(wrapCols >= 0);
    return wrapCols;
  }
  int32_t GetRows() { return mTextCtrlElement->GetRows(); }

  // preview methods
  void SetPreviewText(const nsAString& aValue, bool aNotify);
  void GetPreviewText(nsAString& aValue);

  struct SelectionProperties {
   public:
    bool IsDefault() const {
      return mStart == 0 && mEnd == 0 &&
             mDirection == SelectionDirection::Forward;
    }
    uint32_t GetStart() const { return mStart; }
    bool SetStart(uint32_t value) {
      uint32_t newValue = std::min(value, *mMaxLength);
      return SetStartInternal(newValue);
    }
    uint32_t GetEnd() const { return mEnd; }
    bool SetEnd(uint32_t value) {
      uint32_t newValue = std::min(value, *mMaxLength);
      return SetEndInternal(newValue);
    }
    void CollapseToStart() {
      // 0 is always a fine value regardless of max length.
      SetStartInternal(0);
      SetEndInternal(0);
    }
    SelectionDirection GetDirection() const { return mDirection; }
    bool SetDirection(SelectionDirection value) {
      bool changed = mDirection != value;
      mDirection = value;
      mIsDirty |= changed;
      return changed;
    }
    void SetMaxLength(uint32_t aMax) {
      mMaxLength = Some(aMax);
      // recompute against the new max length
      SetStart(GetStart());
      SetEnd(GetEnd());
    }
    bool HasMaxLength() { return mMaxLength.isSome(); }

    // return true only if mStart, mEnd, or mDirection have been modified,
    // or if SetIsDirty() was explicitly called.
    bool IsDirty() const { return mIsDirty; }
    void SetIsDirty() { mIsDirty = true; }

   private:
    bool SetStartInternal(uint32_t aNewValue) {
      bool changed = mStart != aNewValue;
      mStart = aNewValue;
      mIsDirty |= changed;
      return changed;
    }

    bool SetEndInternal(uint32_t aNewValue) {
      bool changed = mEnd != aNewValue;
      mEnd = aNewValue;
      mIsDirty |= changed;
      return changed;
    }

    uint32_t mStart = 0;
    uint32_t mEnd = 0;
    Maybe<uint32_t> mMaxLength;
    bool mIsDirty = false;
    SelectionDirection mDirection = SelectionDirection::Forward;
  };

  bool IsSelectionCached() const { return mSelectionCached; }
  SelectionProperties& GetSelectionProperties() { return mSelectionProperties; }
  MOZ_CAN_RUN_SCRIPT void SetSelectionProperties(SelectionProperties& aProps);
  bool HasNeverInitializedBefore() const { return !mEverInited; }
  // Sync up our selection properties with our editor prior to being destroyed.
  // This will invoke UnbindFromFrame() to ensure that we grab whatever
  // selection state may be at the moment.
  MOZ_CAN_RUN_SCRIPT void SyncUpSelectionPropertiesBeforeDestruction();

  // Get the selection range start and end points in our text.
  void GetSelectionRange(uint32_t* aSelectionStart, uint32_t* aSelectionEnd,
                         ErrorResult& aRv);

  // Get the selection direction
  SelectionDirection GetSelectionDirection(ErrorResult& aRv);

  enum class ScrollAfterSelection { No, Yes };

  // Set the selection range (start, end, direction).  aEnd is allowed to be
  // smaller than aStart; in that case aStart will be reset to the same value as
  // aEnd.  This basically implements
  // https://html.spec.whatwg.org/multipage/forms.html#set-the-selection-range
  // but with the start/end already coerced to zero if null (and without the
  // special infinity value), and the direction already converted to a
  // SelectionDirection.
  //
  // If we have a frame, this method will scroll the selection into view.
  MOZ_CAN_RUN_SCRIPT void SetSelectionRange(
      uint32_t aStart, uint32_t aEnd, SelectionDirection aDirection,
      ErrorResult& aRv,
      ScrollAfterSelection aScroll = ScrollAfterSelection::Yes);

  // Set the selection range, but with an optional string for the direction.
  // This will convert aDirection to an nsITextControlFrame::SelectionDirection
  // and then call our other SetSelectionRange overload.
  MOZ_CAN_RUN_SCRIPT void SetSelectionRange(
      uint32_t aSelectionStart, uint32_t aSelectionEnd,
      const dom::Optional<nsAString>& aDirection, ErrorResult& aRv,
      ScrollAfterSelection aScroll = ScrollAfterSelection::Yes);

  // Set the selection start.  This basically implements the
  // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionstart
  // setter.
  MOZ_CAN_RUN_SCRIPT void SetSelectionStart(
      const dom::Nullable<uint32_t>& aStart, ErrorResult& aRv);

  // Set the selection end.  This basically implements the
  // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionend
  // setter.
  MOZ_CAN_RUN_SCRIPT void SetSelectionEnd(const dom::Nullable<uint32_t>& aEnd,
                                          ErrorResult& aRv);

  // Get the selection direction as a string.  This implements the
  // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
  // getter.
  void GetSelectionDirectionString(nsAString& aDirection, ErrorResult& aRv);

  // Set the selection direction.  This basically implements the
  // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
  // setter.
  MOZ_CAN_RUN_SCRIPT void SetSelectionDirection(const nsAString& aDirection,
                                                ErrorResult& aRv);

  // Set the range text.  This basically implements
  // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-setrangetext
  MOZ_CAN_RUN_SCRIPT void SetRangeText(const nsAString& aReplacement,
                                       ErrorResult& aRv);
  // The last two arguments are -1 if we don't know our selection range;
  // otherwise they're the start and end of our selection range.
  MOZ_CAN_RUN_SCRIPT void SetRangeText(
      const nsAString& aReplacement, uint32_t aStart, uint32_t aEnd,
      dom::SelectionMode aSelectMode, ErrorResult& aRv,
      const Maybe<uint32_t>& aSelectionStart = Nothing(),
      const Maybe<uint32_t>& aSelectionEnd = Nothing());

 private:
  explicit TextControlState(TextControlElement* aOwningElement);
  MOZ_CAN_RUN_SCRIPT ~TextControlState();

  /**
   * Delete the instance or cache to reuse it if possible.
   */
  MOZ_CAN_RUN_SCRIPT void DeleteOrCacheForReuse();

  MOZ_CAN_RUN_SCRIPT void UnlinkInternal();

  MOZ_CAN_RUN_SCRIPT void DestroyEditor();
  MOZ_CAN_RUN_SCRIPT void Clear();

  nsresult InitializeRootNode();

  void FinishedRestoringSelection();

  bool EditorHasComposition();

  /**
   * SetValueWithTextEditor() modifies the editor value with mTextEditor.
   * This may cause destroying mTextEditor, mBoundFrame, the TextControlState
   * itself.  Must be called when both mTextEditor and mBoundFrame are not
   * nullptr.
   *
   * @param aHandlingSetValue   Must be inner-most handling state for SetValue.
   * @return                    false if fallible allocation failed.  Otherwise,
   *                            true.
   */
  MOZ_CAN_RUN_SCRIPT bool SetValueWithTextEditor(
      AutoTextControlHandlingState& aHandlingSetValue);

  /**
   * SetValueWithoutTextEditor() modifies the value without editor.  I.e.,
   * modifying the value in this instance and mBoundFrame.  Must be called
   * when at least mTextEditor or mBoundFrame is nullptr.
   *
   * @param aHandlingSetValue   Must be inner-most handling state for SetValue.
   * @return                    false if fallible allocation failed.  Otherwise,
   *                            true.
   */
  MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditor(
      AutoTextControlHandlingState& aHandlingSetValue);

  IMEContentObserver* GetIMEContentObserver() const;

  // When this class handles something which may run script, this should be
  // set to non-nullptr.  If so, this class claims that it's busy and that
  // prevents destroying TextControlState instance.
  AutoTextControlHandlingState* mHandlingState = nullptr;

  // The text control element owns this object, and ensures that this object
  // has a smaller lifetime except the owner releases the instance while it
  // does something with this.
  TextControlElement* MOZ_NON_OWNING_REF mTextCtrlElement;
  RefPtr<TextInputSelectionController> mSelCon;
  RefPtr<RestoreSelectionState> mRestoringSelection;
  RefPtr<TextEditor> mTextEditor;
  nsTextControlFrame* mBoundFrame = nullptr;
  RefPtr<TextInputListener> mTextListener;
  UniquePtr<PasswordMaskData> mPasswordMaskData;

  nsString mValue{VoidString()};  // Void if there's no value.

  // If our input's last value change was not interactive (as in, the value
  // change was caused by a ValueChangeKind::UserInteraction), this is the value
  // that the last interaction had.
  nsString mLastInteractiveValue{VoidString()};

  SelectionProperties mSelectionProperties;

  bool mEverInited : 1;  // Have we ever been initialized?
  bool mEditorInitialized : 1;
  bool mValueTransferInProgress : 1;  // Whether a value is being transferred to
                                      // the frame
  bool mSelectionCached : 1;          // Whether mSelectionProperties is valid

  friend class AutoTextControlHandlingState;
  friend class PrepareEditorEvent;
  friend class RestoreSelectionState;
};

}  // namespace mozilla

#endif  // #ifndef mozilla_TextControlState_h
