1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_TextControlState_h
8 #define mozilla_TextControlState_h
9 
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/TextControlElement.h"
15 #include "mozilla/UniquePtr.h"
16 #include "mozilla/WeakPtr.h"
17 #include "mozilla/dom/HTMLInputElementBinding.h"
18 #include "mozilla/dom/Nullable.h"
19 #include "nsCycleCollectionParticipant.h"
20 #include "nsITextControlFrame.h"
21 #include "nsITimer.h"
22 
23 class nsTextControlFrame;
24 class nsISelectionController;
25 class nsFrameSelection;
26 class nsFrame;
27 
28 namespace mozilla {
29 
30 class AutoTextControlHandlingState;
31 class ErrorResult;
32 class TextEditor;
33 class TextInputListener;
34 class TextInputSelectionController;
35 
36 namespace dom {
37 class Element;
38 class HTMLInputElement;
39 }  // namespace dom
40 
41 /**
42  * PasswordMaskData stores making information and necessary timer for
43  * `TextEditor` instances.
44  */
45 struct PasswordMaskData final {
46   // Timer to mask unmasked characters automatically.  Used only when it's
47   // a password field.
48   nsCOMPtr<nsITimer> mTimer;
49 
50   // Unmasked character range.  Used only when it's a password field.
51   // If mUnmaskedLength is 0, it means there is no unmasked characters.
52   uint32_t mUnmaskedStart = UINT32_MAX;
53   uint32_t mUnmaskedLength = 0;
54 
55   // Set to true if all characters are masked or waiting notification from
56   // `mTimer`.  Otherwise, i.e., part of or all of password is unmasked
57   // without setting `mTimer`, set to false.
58   bool mIsMaskingPassword = true;
59 
60   // Set to true if a manager of the instance wants to disable echoing
61   // password temporarily.
62   bool mEchoingPasswordPrevented = false;
63 
IsAllMaskedfinal64   MOZ_ALWAYS_INLINE bool IsAllMasked() const {
65     return mUnmaskedStart == UINT32_MAX && mUnmaskedLength == 0;
66   }
UnmaskedEndfinal67   MOZ_ALWAYS_INLINE uint32_t UnmaskedEnd() const {
68     return mUnmaskedStart + mUnmaskedLength;
69   }
MaskAllfinal70   MOZ_ALWAYS_INLINE void MaskAll() {
71     mUnmaskedStart = UINT32_MAX;
72     mUnmaskedLength = 0;
73   }
Resetfinal74   MOZ_ALWAYS_INLINE void Reset() {
75     MaskAll();
76     mIsMaskingPassword = true;
77   }
78   enum class ReleaseTimer { No, Yes };
CancelTimerfinal79   MOZ_ALWAYS_INLINE void CancelTimer(ReleaseTimer aReleaseTimer) {
80     if (mTimer) {
81       mTimer->Cancel();
82       if (aReleaseTimer == ReleaseTimer::Yes) {
83         mTimer = nullptr;
84       }
85     }
86     if (mIsMaskingPassword) {
87       MaskAll();
88     }
89   }
90 };
91 
92 /**
93  * TextControlState is a class which is responsible for managing the state of
94  * plaintext controls.  This currently includes the following HTML elements:
95  *   <input type=text>
96  *   <input type=search>
97  *   <input type=url>
98  *   <input type=telephone>
99  *   <input type=email>
100  *   <input type=password>
101  *   <textarea>
102  *
103  * This class is held as a member of HTMLInputElement and HTMLTextAreaElement.
104  * The public functions in this class include the public APIs which dom/
105  * uses. Layout code uses the TextControlElement interface to invoke
106  * functions on this class.
107  *
108  * The design motivation behind this class is maintaining all of the things
109  * which collectively are considered the "state" of the text control in a single
110  * location. This state includes several things:
111  *
112  *  * The control's value.  This value is stored in the mValue member, and is
113  * only used when there is no frame for the control, or when the editor object
114  * has not been initialized yet.
115  *
116  *  * The control's associated frame.  This value is stored in the mBoundFrame
117  * member. A text control might never have an associated frame during its life
118  * cycle, or might have several different ones, but at any given moment in time
119  * there is a maximum of 1 bound frame to each text control.
120  *
121  *  * The control's associated editor.  This value is stored in the mTextEditor
122  * member. An editor is initialized for the control only when necessary (that
123  * is, when either the user is about to interact with the text control, or when
124  * some other code needs to access the editor object.  Without a frame bound to
125  * the control, an editor is never initialized.  Once initialized, the editor
126  * might outlive the frame, in which case the same editor will be used if a new
127  * frame gets bound to the text control.
128  *
129  *  * The anonymous content associated with the text control's frame, including
130  * the value div (the DIV element responsible for holding the value of the text
131  * control) and the placeholder div (the DIV element responsible for holding the
132  * placeholder value of the text control.)  These values are stored in the
133  * mRootNode and mPlaceholderDiv members, respectively.  They will be created
134  * when a frame is bound to the text control.  They will be destroyed when the
135  * frame is unbound from the object.  We could try and hold on to the anonymous
136  * content between different frames, but unfortunately that is not currently
137  * possible because they are not unbound from the document in time.
138  *
139  *  * The frame selection controller.  This value is stored in the mSelCon
140  * member. The frame selection controller is responsible for maintaining the
141  * selection state on a frame.  It is created when a frame is bound to the text
142  * control element, and will be destroy when the frame is being unbound from the
143  * text control element. It is created alongside with the frame selection object
144  * which is stored in the mFrameSel member.
145  *
146  *  * The editor text listener.  This value is stored in the mTextListener
147  * member. Its job is to listen to selection and keyboard events, and act
148  * accordingly. It is created when an a frame is first bound to the control, and
149  * will be destroyed when the frame is unbound from the text control element.
150  *
151  *  * The editor's cached value.  This value is stored in the mCachedValue
152  * member. It is used to improve the performance of append operations to the
153  * text control.  A mutation observer stored in the mMutationObserver has the
154  * job of invalidating this cache when the anonymous contect containing the
155  * value is changed.
156  *
157  *  * The editor's cached selection properties.  These vales are stored in the
158  *    mSelectionProperties member, and include the selection's start, end and
159  *    direction. They are only used when there is no frame available for the
160  *    text field.
161  *
162  *
163  * As a general rule, TextControlState objects own the value of the text
164  * control, and any attempt to retrieve or set the value must be made through
165  * those objects.  Internally, the value can be represented in several different
166  * ways, based on the state the control is in.
167  *
168  *   * When the control is first initialized, its value is equal to the default
169  * value of the DOM node.  For <input> text controls, this default value is the
170  * value of the value attribute.  For <textarea> elements, this default value is
171  * the value of the text node children of the element.
172  *
173  *   * If the value has been changed through the DOM node (before the editor for
174  * the object is initialized), the value is stored as a simple string inside the
175  * mValue member of the TextControlState object.
176  *
177  *   * If an editor has been initialized for the control, the value is set and
178  * retrievd via the nsIEditor interface, and is internally managed by the
179  * editor as the native anonymous content tree attached to the control's frame.
180  *
181  *   * If the text control state object is unbound from the control's frame, the
182  * value is transferred to the mValue member variable, and will be managed there
183  * until a new frame is bound to the text editor state object.
184  */
185 
186 class RestoreSelectionState;
187 
188 class TextControlState final : public SupportsWeakPtr {
189  public:
190   typedef dom::Element Element;
191   typedef dom::HTMLInputElement HTMLInputElement;
192 
193   static TextControlState* Construct(TextControlElement* aOwningElement);
194 
195   // Note that this does not run script actually because of `sHasShutDown`
196   // is set to true before calling `DeleteOrCacheForReuse()`.
197   MOZ_CAN_RUN_SCRIPT_BOUNDARY static void Shutdown();
198 
199   /**
200    * Destroy() deletes the instance immediately or later.
201    */
202   MOZ_CAN_RUN_SCRIPT void Destroy();
203 
204   TextControlState() = delete;
205   explicit TextControlState(const TextControlState&) = delete;
206   TextControlState(TextControlState&&) = delete;
207 
208   void operator=(const TextControlState&) = delete;
209   void operator=(TextControlState&&) = delete;
210 
211   void Traverse(nsCycleCollectionTraversalCallback& cb);
212   MOZ_CAN_RUN_SCRIPT_BOUNDARY void Unlink();
213 
IsBusy()214   bool IsBusy() const { return !!mHandlingState || mValueTransferInProgress; }
215 
216   MOZ_CAN_RUN_SCRIPT TextEditor* GetTextEditor();
217   TextEditor* GetTextEditorWithoutCreation();
218   nsISelectionController* GetSelectionController() const;
219   nsFrameSelection* GetConstFrameSelection();
220   nsresult BindToFrame(nsTextControlFrame* aFrame);
221   MOZ_CAN_RUN_SCRIPT void UnbindFromFrame(nsTextControlFrame* aFrame);
222   MOZ_CAN_RUN_SCRIPT nsresult PrepareEditor(const nsAString* aValue = nullptr);
223   void InitializeKeyboardEventListeners();
224 
225   /**
226    * OnEditActionHandled() is called when mTextEditor handles something
227    * and immediately before dispatching "input" event.
228    */
229   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled();
230 
231   enum class ValueSetterOption {
232     // The call is for setting value to initial one, computed one, etc.
233     ByInternalAPI,
234     // The value is changed by a call of setUserInput() API from chrome.
235     BySetUserInputAPI,
236     // The value is changed by changing value attribute of the element or
237     // something like setRangeText().
238     ByContentAPI,
239     // The value is changed by setRangeText(). Intended to prevent silent
240     // selection range change.
241     BySetRangeTextAPI,
242     // Whether SetValueChanged should be called as a result of this value
243     // change.
244     SetValueChanged,
245     // Whether to move the cursor to end of the value (in the case when we have
246     // cached selection offsets), in the case when the value has changed.  If
247     // this is not set and MoveCursorToBeginSetSelectionDirectionForward
248     // is not set, the cached selection offsets will simply be clamped to
249     // be within the length of the new value. In either case, if the value has
250     // not changed the cursor won't move.
251     // TODO(mbrodesser): update comment and enumerator identifier to reflect
252     // that also the direction is set to forward.
253     MoveCursorToEndIfValueChanged,
254 
255     // The value change should preserve undo history.
256     PreserveUndoHistory,
257 
258     // Whether it should be tried to move the cursor to the beginning of the
259     // text control and set the selection direction to "forward".
260     // TODO(mbrodesser): As soon as "none" is supported
261     // (https://bugzilla.mozilla.org/show_bug.cgi?id=1541454), it should be set
262     // to "none" and only fall back to "forward" if the platform doesn't support
263     // it.
264     MoveCursorToBeginSetSelectionDirectionForward,
265   };
266   using ValueSetterOptions = EnumSet<ValueSetterOption, uint32_t>;
267 
268   /**
269    * SetValue() sets the value to aValue with replacing \r\n and \r with \n.
270    *
271    * @param aValue      The new value.  Can contain \r.
272    * @param aOldValue   Optional.  If you have already know current value,
273    *                    set this to it.  However, this must not contain \r
274    *                    for the performance.
275    * @param aOptions    See ValueSetterOption.
276    */
277   [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValue(
278       const nsAString& aValue, const nsAString* aOldValue,
279       const ValueSetterOptions& aOptions);
SetValue(const nsAString & aValue,const ValueSetterOptions & aOptions)280   [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValue(
281       const nsAString& aValue, const ValueSetterOptions& aOptions) {
282     return SetValue(aValue, nullptr, aOptions);
283   }
284 
285   /**
286    * GetValue() returns current value either with or without TextEditor.
287    * The result never includes \r.
288    */
289   void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
290   /**
291    * ValueEquals() is designed for internal use so that aValue shouldn't
292    * include \r character.  It should be handled before calling this with
293    * nsContentUtils::PlatformToDOMLineBreaks().
294    */
295   bool ValueEquals(const nsAString& aValue) const;
296   bool HasNonEmptyValue();
297   // The following methods are for textarea element to use whether default
298   // value or not.
299   // XXX We might have to add assertion when it is into editable,
300   // or reconsider fixing bug 597525 to remove these.
EmptyValue()301   void EmptyValue() {
302     if (mValue) {
303       mValue->Truncate();
304     }
305   }
IsEmpty()306   bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
307 
308   Element* GetRootNode();
309   Element* GetPreviewNode();
310 
IsSingleLineTextControl()311   bool IsSingleLineTextControl() const {
312     return mTextCtrlElement->IsSingleLineTextControl();
313   }
IsTextArea()314   bool IsTextArea() const { return mTextCtrlElement->IsTextArea(); }
IsPasswordTextControl()315   bool IsPasswordTextControl() const {
316     return mTextCtrlElement->IsPasswordTextControl();
317   }
GetCols()318   int32_t GetCols() { return mTextCtrlElement->GetCols(); }
GetWrapCols()319   int32_t GetWrapCols() {
320     int32_t wrapCols = mTextCtrlElement->GetWrapCols();
321     MOZ_ASSERT(wrapCols >= 0);
322     return wrapCols;
323   }
GetRows()324   int32_t GetRows() { return mTextCtrlElement->GetRows(); }
325 
326   // preview methods
327   void SetPreviewText(const nsAString& aValue, bool aNotify);
328   void GetPreviewText(nsAString& aValue);
329 
330   struct SelectionProperties {
331    public:
IsDefaultSelectionProperties332     bool IsDefault() const {
333       return mStart == 0 && mEnd == 0 &&
334              mDirection == nsITextControlFrame::eForward;
335     }
GetStartSelectionProperties336     uint32_t GetStart() const { return mStart; }
SetStartSelectionProperties337     bool SetStart(uint32_t value) {
338       uint32_t newValue = std::min(value, *mMaxLength);
339       bool changed = mStart != newValue;
340       mStart = newValue;
341       mIsDirty |= changed;
342       return changed;
343     }
GetEndSelectionProperties344     uint32_t GetEnd() const { return mEnd; }
SetEndSelectionProperties345     bool SetEnd(uint32_t value) {
346       uint32_t newValue = std::min(value, *mMaxLength);
347       bool changed = mEnd != newValue;
348       mEnd = newValue;
349       mIsDirty |= changed;
350       return changed;
351     }
GetDirectionSelectionProperties352     nsITextControlFrame::SelectionDirection GetDirection() const {
353       return mDirection;
354     }
SetDirectionSelectionProperties355     bool SetDirection(nsITextControlFrame::SelectionDirection value) {
356       bool changed = mDirection != value;
357       mDirection = value;
358       mIsDirty |= changed;
359       return changed;
360     }
SetMaxLengthSelectionProperties361     void SetMaxLength(uint32_t aMax) {
362       mMaxLength = Some(aMax);
363       // recompute against the new max length
364       SetStart(GetStart());
365       SetEnd(GetEnd());
366     }
HasMaxLengthSelectionProperties367     bool HasMaxLength() { return mMaxLength.isSome(); }
368 
369     // return true only if mStart, mEnd, or mDirection have been modified,
370     // or if SetIsDirty() was explicitly called.
IsDirtySelectionProperties371     bool IsDirty() const { return mIsDirty; }
SetIsDirtySelectionProperties372     void SetIsDirty() { mIsDirty = true; }
373 
374    private:
375     uint32_t mStart = 0;
376     uint32_t mEnd = 0;
377     Maybe<uint32_t> mMaxLength;
378     bool mIsDirty = false;
379     nsITextControlFrame::SelectionDirection mDirection =
380         nsITextControlFrame::eForward;
381   };
382 
IsSelectionCached()383   bool IsSelectionCached() const { return mSelectionCached; }
GetSelectionProperties()384   SelectionProperties& GetSelectionProperties() { return mSelectionProperties; }
385   MOZ_CAN_RUN_SCRIPT void SetSelectionProperties(SelectionProperties& aProps);
HasNeverInitializedBefore()386   bool HasNeverInitializedBefore() const { return !mEverInited; }
387   // Sync up our selection properties with our editor prior to being destroyed.
388   // This will invoke UnbindFromFrame() to ensure that we grab whatever
389   // selection state may be at the moment.
390   MOZ_CAN_RUN_SCRIPT void SyncUpSelectionPropertiesBeforeDestruction();
391 
392   // Get the selection range start and end points in our text.
393   void GetSelectionRange(uint32_t* aSelectionStart, uint32_t* aSelectionEnd,
394                          ErrorResult& aRv);
395 
396   // Get the selection direction
397   nsITextControlFrame::SelectionDirection GetSelectionDirection(
398       ErrorResult& aRv);
399 
400   enum class ScrollAfterSelection { No, Yes };
401 
402   // Set the selection range (start, end, direction).  aEnd is allowed to be
403   // smaller than aStart; in that case aStart will be reset to the same value as
404   // aEnd.  This basically implements
405   // https://html.spec.whatwg.org/multipage/forms.html#set-the-selection-range
406   // but with the start/end already coerced to zero if null (and without the
407   // special infinity value), and the direction already converted to a
408   // SelectionDirection.
409   //
410   // If we have a frame, this method will scroll the selection into view.
411   MOZ_CAN_RUN_SCRIPT void SetSelectionRange(
412       uint32_t aStart, uint32_t aEnd,
413       nsITextControlFrame::SelectionDirection aDirection, ErrorResult& aRv,
414       ScrollAfterSelection aScroll = ScrollAfterSelection::Yes);
415 
416   // Set the selection range, but with an optional string for the direction.
417   // This will convert aDirection to an nsITextControlFrame::SelectionDirection
418   // and then call our other SetSelectionRange overload.
419   MOZ_CAN_RUN_SCRIPT void SetSelectionRange(
420       uint32_t aSelectionStart, uint32_t aSelectionEnd,
421       const dom::Optional<nsAString>& aDirection, ErrorResult& aRv,
422       ScrollAfterSelection aScroll = ScrollAfterSelection::Yes);
423 
424   // Set the selection start.  This basically implements the
425   // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionstart
426   // setter.
427   MOZ_CAN_RUN_SCRIPT void SetSelectionStart(
428       const dom::Nullable<uint32_t>& aStart, ErrorResult& aRv);
429 
430   // Set the selection end.  This basically implements the
431   // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionend
432   // setter.
433   MOZ_CAN_RUN_SCRIPT void SetSelectionEnd(const dom::Nullable<uint32_t>& aEnd,
434                                           ErrorResult& aRv);
435 
436   // Get the selection direction as a string.  This implements the
437   // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
438   // getter.
439   void GetSelectionDirectionString(nsAString& aDirection, ErrorResult& aRv);
440 
441   // Set the selection direction.  This basically implements the
442   // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
443   // setter.
444   MOZ_CAN_RUN_SCRIPT void SetSelectionDirection(const nsAString& aDirection,
445                                                 ErrorResult& aRv);
446 
447   // Set the range text.  This basically implements
448   // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-setrangetext
449   MOZ_CAN_RUN_SCRIPT void SetRangeText(const nsAString& aReplacement,
450                                        ErrorResult& aRv);
451   // The last two arguments are -1 if we don't know our selection range;
452   // otherwise they're the start and end of our selection range.
453   MOZ_CAN_RUN_SCRIPT void SetRangeText(
454       const nsAString& aReplacement, uint32_t aStart, uint32_t aEnd,
455       dom::SelectionMode aSelectMode, ErrorResult& aRv,
456       const Maybe<uint32_t>& aSelectionStart = Nothing(),
457       const Maybe<uint32_t>& aSelectionEnd = Nothing());
458 
459  private:
460   explicit TextControlState(TextControlElement* aOwningElement);
461   MOZ_CAN_RUN_SCRIPT ~TextControlState();
462 
463   /**
464    * Delete the instance or cache to reuse it if possible.
465    */
466   MOZ_CAN_RUN_SCRIPT void DeleteOrCacheForReuse();
467 
468   MOZ_CAN_RUN_SCRIPT void UnlinkInternal();
469 
470   MOZ_CAN_RUN_SCRIPT void DestroyEditor();
471   MOZ_CAN_RUN_SCRIPT void Clear();
472 
473   nsresult InitializeRootNode();
474 
475   void FinishedRestoringSelection();
476 
477   bool EditorHasComposition();
478 
479   /**
480    * SetValueWithTextEditor() modifies the editor value with mTextEditor.
481    * This may cause destroying mTextEditor, mBoundFrame, the TextControlState
482    * itself.  Must be called when both mTextEditor and mBoundFrame are not
483    * nullptr.
484    *
485    * @param aHandlingState      Must be inner-most handling state for SetValue.
486    * @return                    false if fallible allocation failed.  Otherwise,
487    *                            true.
488    */
489   MOZ_CAN_RUN_SCRIPT bool SetValueWithTextEditor(
490       AutoTextControlHandlingState& aHandlingState);
491 
492   /**
493    * SetValueWithoutTextEditor() modifies the value without editor.  I.e.,
494    * modifying the value in this instance and mBoundFrame.  Must be called
495    * when at least mTextEditor or mBoundFrame is nullptr.
496    *
497    * @param aHandlingState      Must be inner-most handling state for SetValue.
498    * @return                    false if fallible allocation failed.  Otherwise,
499    *                            true.
500    */
501   MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditor(
502       AutoTextControlHandlingState& aHandlingState);
503 
504   // When this class handles something which may run script, this should be
505   // set to non-nullptr.  If so, this class claims that it's busy and that
506   // prevents destroying TextControlState instance.
507   AutoTextControlHandlingState* mHandlingState = nullptr;
508 
509   // The text control element owns this object, and ensures that this object
510   // has a smaller lifetime except the owner releases the instance while it
511   // does something with this.
512   TextControlElement* MOZ_NON_OWNING_REF mTextCtrlElement;
513   RefPtr<TextInputSelectionController> mSelCon;
514   RefPtr<RestoreSelectionState> mRestoringSelection;
515   RefPtr<TextEditor> mTextEditor;
516   nsTextControlFrame* mBoundFrame;
517   RefPtr<TextInputListener> mTextListener;
518   UniquePtr<PasswordMaskData> mPasswordMaskData;
519   Maybe<nsString> mValue;
520   SelectionProperties mSelectionProperties;
521   bool mEverInited;  // Have we ever been initialized?
522   bool mEditorInitialized;
523   bool mValueTransferInProgress;  // Whether a value is being transferred to the
524                                   // frame
525   bool mSelectionCached;          // Whether mSelectionProperties is valid
526 
527   /**
528    * For avoiding allocation cost of the instance, we should reuse instances
529    * as far as possible.
530    *
531    * FYI: `25` is just a magic number considered without enough investigation,
532    *      but at least, this value must not make damage for footprint.
533    *      Feel free to change it if you find better number.
534    */
535   static const size_t kMaxCountOfCacheToReuse = 25;
536   static AutoTArray<TextControlState*, kMaxCountOfCacheToReuse>*
537       sReleasedInstances;
538   static bool sHasShutDown;
539 
540   friend class AutoTextControlHandlingState;
541   friend class PrepareEditorEvent;
542   friend class RestoreSelectionState;
543 };
544 
545 }  // namespace mozilla
546 
547 #endif  // #ifndef mozilla_TextControlState_h
548