diff options
Diffstat (limited to '')
-rw-r--r-- | dom/html/TextControlState.h | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/dom/html/TextControlState.h b/dom/html/TextControlState.h new file mode 100644 index 0000000000..7f11ecf48b --- /dev/null +++ b/dom/html/TextControlState.h @@ -0,0 +1,549 @@ +/* -*- 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/HTMLInputElementBinding.h" +#include "mozilla/dom/Nullable.h" +#include "nsCycleCollectionParticipant.h" +#include "nsITextControlFrame.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; + +namespace dom { +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; + using SelectionDirection = nsITextControlFrame::SelectionDirection; + + 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* GetTextEditorWithoutCreation(); + nsISelectionController* GetSelectionController() const; + nsFrameSelection* GetConstFrameSelection(); + nsresult BindToFrame(nsTextControlFrame* aFrame); + MOZ_CAN_RUN_SCRIPT void UnbindFromFrame(nsTextControlFrame* aFrame); + 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 aIgnoreWrap) 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 GetCols() { return mTextCtrlElement->GetCols(); } + 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); + bool changed = mStart != newValue; + mStart = newValue; + mIsDirty |= changed; + return changed; + } + uint32_t GetEnd() const { return mEnd; } + bool SetEnd(uint32_t value) { + uint32_t newValue = std::min(value, *mMaxLength); + bool changed = mEnd != newValue; + mEnd = newValue; + mIsDirty |= changed; + return changed; + } + 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: + 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 + nsITextControlFrame::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, + nsITextControlFrame::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 |