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/. */
7 #ifndef nsTextEditorState_h__
8 #define nsTextEditorState_h__
11 #include "nsITextControlElement.h"
12 #include "nsITextControlFrame.h"
13 #include "nsCycleCollectionParticipant.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/Attributes.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/TextEditor.h"
18 #include "mozilla/WeakPtr.h"
19 #include "mozilla/dom/HTMLInputElementBinding.h"
20 #include "mozilla/dom/Nullable.h"
22 class nsTextControlFrame
;
23 class nsTextInputSelectionImpl
;
24 class nsAnonDivObserver
;
25 class nsISelectionController
;
26 class nsFrameSelection
;
27 class nsITextControlElement
;
33 class TextInputListener
;
36 class HTMLInputElement
;
38 } // namespace mozilla
41 * nsTextEditorState is a class which is responsible for managing the state of
42 * plaintext controls. This currently includes the following HTML elements:
44 * <input type=password>
46 * and also XUL controls such as <textbox> which use one of these elements behind
49 * This class is held as a member of HTMLInputElement and nsHTMLTextAreaElement.
50 * The public functions in this class include the public APIs which content/ uses.
51 * Layout code uses the nsITextControlElement interface to invoke functions on this
54 * The design motivation behind this class is maintaining all of the things which
55 * collectively are considered the "state" of the text control in a single location.
56 * This state includes several things:
58 * * The control's value. This value is stored in the mValue member, and is only
59 * used when there is no frame for the control, or when the editor object has
60 * not been initialized yet.
62 * * The control's associated frame. This value is stored in the mBoundFrame member.
63 * A text control might never have an associated frame during its life cycle,
64 * or might have several different ones, but at any given moment in time there is
65 * a maximum of 1 bound frame to each text control.
67 * * The control's associated editor. This value is stored in the mEditor member.
68 * An editor is initilized for the control only when necessary (that is, when either
69 * the user is about to interact with the text control, or when some other code
70 * needs to access the editor object. Without a frame bound to the control, an
71 * editor is never initialzied. Once initialized, the editor might outlive the frame,
72 * in which case the same editor will be used if a new frame gets bound to the
75 * * The anonymous content associated with the text control's frame, including the
76 * value div (the DIV element responsible for holding the value of the text control)
77 * and the placeholder div (the DIV element responsible for holding the placeholder
78 * value of the text control.) These values are stored in the mRootNode and
79 * mPlaceholderDiv members, respectively. They will be created when a
80 * frame is bound to the text control. They will be destroyed when the frame is
81 * unbound from the object. We could try and hold on to the anonymous content
82 * between different frames, but unfortunately that is not currently possible
83 * because they are not unbound from the document in time.
85 * * The frame selection controller. This value is stored in the mSelCon member.
86 * The frame selection controller is responsible for maintaining the selection state
87 * on a frame. It is created when a frame is bound to the text control element,
88 * and will be destroy when the frame is being unbound from the text control element.
89 * It is created alongside with the frame selection object which is stored in the
92 * * The editor text listener. This value is stored in the mTextListener member.
93 * Its job is to listen to selection and keyboard events, and act accordingly.
94 * It is created when an a frame is first bound to the control, and will be destroyed
95 * when the frame is unbound from the text control element.
97 * * The editor's cached value. This value is stored in the mCachedValue member.
98 * It is used to improve the performance of append operations to the text
99 * control. A mutation observer stored in the mMutationObserver has the job of
100 * invalidating this cache when the anonymous contect containing the value is
103 * * The editor's cached selection properties. These vales are stored in the
104 * mSelectionProperties member, and include the selection's start, end and
105 * direction. They are only used when there is no frame available for the
109 * As a general rule, nsTextEditorState objects own the value of the text control, and any
110 * attempt to retrieve or set the value must be made through those objects. Internally,
111 * the value can be represented in several different ways, based on the state the control is
114 * * When the control is first initialized, its value is equal to the default value of
115 * the DOM node. For <input> text controls, this default value is the value of the
116 * value attribute. For <textarea> elements, this default value is the value of the
117 * text node children of the element.
119 * * If the value has been changed through the DOM node (before the editor for the object
120 * is initialized), the value is stored as a simple string inside the mValue member of
121 * the nsTextEditorState object.
123 * * If an editor has been initialized for the control, the value is set and retrievd via
124 * the nsIPlaintextEditor interface, and is internally managed by the editor as the
125 * native anonymous content tree attached to the control's frame.
127 * * If the text editor state object is unbound from the control's frame, the value is
128 * transferred to the mValue member variable, and will be managed there until a new
129 * frame is bound to the text editor state object.
132 class RestoreSelectionState
;
134 class nsTextEditorState
: public mozilla::SupportsWeakPtr
<nsTextEditorState
> {
136 MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsTextEditorState
)
137 explicit nsTextEditorState(nsITextControlElement
* aOwningElement
);
138 static nsTextEditorState
*
139 Construct(nsITextControlElement
* aOwningElement
,
140 nsTextEditorState
** aReusedState
);
141 ~nsTextEditorState();
143 void Traverse(nsCycleCollectionTraversalCallback
& cb
);
146 void PrepareForReuse()
150 mValueBeingSet
.Truncate();
151 mTextCtrlElement
= nullptr;
154 mozilla::TextEditor
* GetTextEditor();
155 nsISelectionController
* GetSelectionController() const;
156 nsFrameSelection
* GetConstFrameSelection();
157 nsresult
BindToFrame(nsTextControlFrame
* aFrame
);
158 void UnbindFromFrame(nsTextControlFrame
* aFrame
);
159 nsresult
PrepareEditor(const nsAString
*aValue
= nullptr);
160 void InitializeKeyboardEventListeners();
164 // The call is for internal processing.
165 eSetValue_Internal
= 0,
166 // The value is changed by a call of setUserInput() from chrome.
167 eSetValue_BySetUserInput
= 1 << 0,
168 // The value is changed by changing value attribute of the element or
169 // something like setRangeText().
170 eSetValue_ByContent
= 1 << 1,
171 // Whether the value change should be notified to the frame/contet nor not.
172 eSetValue_Notify
= 1 << 2,
173 // Whether to move the cursor to end of the value (in the case when we have
174 // cached selection offsets), in the case when the value has changed. If
175 // this is not set, the cached selection offsets will simply be clamped to
176 // be within the length of the new value. In either case, if the value has
177 // not changed the cursor won't move.
178 eSetValue_MoveCursorToEndIfValueChanged
= 1 << 3,
179 // The value is changed for a XUL text control as opposed to for an HTML
180 // text control. Such value changes are different in that they preserve the
182 eSetValue_ForXUL
= 1 << 4,
184 MOZ_MUST_USE
bool SetValue(const nsAString
& aValue
,
185 const nsAString
* aOldValue
,
187 MOZ_MUST_USE
bool SetValue(const nsAString
& aValue
,
190 return SetValue(aValue
, nullptr, aFlags
);
192 void GetValue(nsAString
& aValue
, bool aIgnoreWrap
) const;
193 bool HasNonEmptyValue();
194 // The following methods are for textarea element to use whether default
196 // XXX We might have to add assertion when it is into editable,
197 // or reconsider fixing bug 597525 to remove these.
198 void EmptyValue() { if (mValue
) mValue
->Truncate(); }
199 bool IsEmpty() const { return mValue
? mValue
->IsEmpty() : true; }
201 mozilla::dom::Element
* GetRootNode();
202 mozilla::dom::Element
* GetPreviewNode();
204 bool IsSingleLineTextControl() const {
205 return mTextCtrlElement
->IsSingleLineTextControl();
207 bool IsTextArea() const {
208 return mTextCtrlElement
->IsTextArea();
210 bool IsPasswordTextControl() const {
211 return mTextCtrlElement
->IsPasswordTextControl();
214 return mTextCtrlElement
->GetCols();
216 int32_t GetWrapCols() {
217 return mTextCtrlElement
->GetWrapCols();
220 return mTextCtrlElement
->GetRows();
223 void UpdateOverlayTextVisibility(bool aNotify
);
225 // placeholder methods
226 bool GetPlaceholderVisibility() {
227 return mPlaceholderVisibility
;
231 void SetPreviewText(const nsAString
& aValue
, bool aNotify
);
232 void GetPreviewText(nsAString
& aValue
);
233 bool GetPreviewVisibility() {
234 return mPreviewVisibility
;
238 * Get the maxlength attribute
239 * @param aMaxLength the value of the max length attr
240 * @returns false if attr not defined
242 int32_t GetMaxLength();
244 void HideSelectionIfBlurred();
246 struct SelectionProperties
{
248 SelectionProperties() : mStart(0), mEnd(0),
249 mDirection(nsITextControlFrame::eForward
) {}
250 bool IsDefault() const
252 return mStart
== 0 && mEnd
== 0 &&
253 mDirection
== nsITextControlFrame::eForward
;
255 uint32_t GetStart() const
259 void SetStart(uint32_t value
)
264 uint32_t GetEnd() const
268 void SetEnd(uint32_t value
)
273 nsITextControlFrame::SelectionDirection
GetDirection() const
277 void SetDirection(nsITextControlFrame::SelectionDirection value
)
282 // return true only if mStart, mEnd, or mDirection have been modified,
283 // or if SetIsDirty() was explicitly called.
293 uint32_t mStart
, mEnd
;
294 bool mIsDirty
= false;
295 nsITextControlFrame::SelectionDirection mDirection
;
298 bool IsSelectionCached() const;
299 SelectionProperties
& GetSelectionProperties();
300 void SetSelectionProperties(SelectionProperties
& aProps
);
301 void WillInitEagerly() { mSelectionRestoreEagerInit
= true; }
302 bool HasNeverInitializedBefore() const { return !mEverInited
; }
303 // Sync up our selection properties with our editor prior to being destroyed.
304 // This will invoke UnbindFromFrame() to ensure that we grab whatever
305 // selection state may be at the moment.
306 void SyncUpSelectionPropertiesBeforeDestruction();
308 // Get the selection range start and end points in our text.
309 void GetSelectionRange(uint32_t* aSelectionStart
, uint32_t* aSelectionEnd
,
310 mozilla::ErrorResult
& aRv
);
312 // Get the selection direction
313 nsITextControlFrame::SelectionDirection
314 GetSelectionDirection(mozilla::ErrorResult
& aRv
);
316 // Set the selection range (start, end, direction). aEnd is allowed to be
317 // smaller than aStart; in that case aStart will be reset to the same value as
318 // aEnd. This basically implements
319 // https://html.spec.whatwg.org/multipage/forms.html#set-the-selection-range
320 // but with the start/end already coerced to zero if null (and without the
321 // special infinity value), and the direction already converted to a
322 // SelectionDirection.
324 // If we have a frame, this method will scroll the selection into view.
326 // XXXbz This should really take uint32_t, but none of our guts (either the
327 // frame or our cached selection state) work with uint32_t at the moment...
328 void SetSelectionRange(uint32_t aStart
, uint32_t aEnd
,
329 nsITextControlFrame::SelectionDirection aDirection
,
330 mozilla::ErrorResult
& aRv
);
332 // Set the selection range, but with an optional string for the direction.
333 // This will convert aDirection to an nsITextControlFrame::SelectionDirection
334 // and then call our other SetSelectionRange overload.
335 void SetSelectionRange(uint32_t aSelectionStart
,
336 uint32_t aSelectionEnd
,
337 const mozilla::dom::Optional
<nsAString
>& aDirection
,
338 mozilla::ErrorResult
& aRv
);
340 // Set the selection start. This basically implements the
341 // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionstart
343 void SetSelectionStart(const mozilla::dom::Nullable
<uint32_t>& aStart
,
344 mozilla::ErrorResult
& aRv
);
346 // Set the selection end. This basically implements the
347 // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionend
349 void SetSelectionEnd(const mozilla::dom::Nullable
<uint32_t>& aEnd
,
350 mozilla::ErrorResult
& aRv
);
352 // Get the selection direction as a string. This implements the
353 // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
355 void GetSelectionDirectionString(nsAString
& aDirection
,
356 mozilla::ErrorResult
& aRv
);
358 // Set the selection direction. This basically implements the
359 // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
361 void SetSelectionDirection(const nsAString
& aDirection
,
362 mozilla::ErrorResult
& aRv
);
364 // Set the range text. This basically implements
365 // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-setrangetext
366 void SetRangeText(const nsAString
& aReplacement
, mozilla::ErrorResult
& aRv
);
367 // The last two arguments are -1 if we don't know our selection range;
368 // otherwise they're the start and end of our selection range.
369 void SetRangeText(const nsAString
& aReplacement
, uint32_t aStart
,
370 uint32_t aEnd
, mozilla::dom::SelectionMode aSelectMode
,
371 mozilla::ErrorResult
& aRv
,
372 const mozilla::Maybe
<uint32_t>& aSelectionStart
=
374 const mozilla::Maybe
<uint32_t>& aSelectionEnd
=
377 void UpdateEditableState(bool aNotify
) {
378 if (auto* root
= GetRootNode()) {
379 root
->UpdateEditableState(aNotify
);
384 friend class RestoreSelectionState
;
386 // not copy constructible
387 nsTextEditorState(const nsTextEditorState
&);
389 void operator= (const nsTextEditorState
&);
391 void ValueWasChanged(bool aNotify
);
393 void DestroyEditor();
396 nsresult
InitializeRootNode();
398 void FinishedRestoringSelection();
400 mozilla::dom::HTMLInputElement
* GetParentNumberControl(nsFrame
* aFrame
) const;
402 bool EditorHasComposition();
404 class InitializationGuard
{
406 explicit InitializationGuard(nsTextEditorState
& aState
) :
410 if (!mState
.mInitializing
) {
412 mState
.mInitializing
= true;
415 ~InitializationGuard() {
417 mState
.mInitializing
= false;
420 bool IsInitializingRecursively() const {
424 nsTextEditorState
& mState
;
427 friend class InitializationGuard
;
428 friend class PrepareEditorEvent
;
430 // The text control element owns this object, and ensures that this object
431 // has a smaller lifetime.
432 nsITextControlElement
* MOZ_NON_OWNING_REF mTextCtrlElement
;
433 RefPtr
<nsTextInputSelectionImpl
> mSelCon
;
434 RefPtr
<RestoreSelectionState
> mRestoringSelection
;
435 RefPtr
<mozilla::TextEditor
> mTextEditor
;
436 nsTextControlFrame
* mBoundFrame
;
437 RefPtr
<mozilla::TextInputListener
> mTextListener
;
438 mozilla::Maybe
<nsString
> mValue
;
439 // mValueBeingSet is available only while SetValue() is requesting to commit
440 // composition. I.e., this is valid only while mIsCommittingComposition is
441 // true. While active composition is being committed, GetValue() needs
442 // the latest value which is set by SetValue(). So, this is cache for that.
443 nsString mValueBeingSet
;
444 SelectionProperties mSelectionProperties
;
445 bool mEverInited
; // Have we ever been initialized?
446 bool mEditorInitialized
;
447 bool mInitializing
; // Whether we're in the process of initialization
448 bool mValueTransferInProgress
; // Whether a value is being transferred to the frame
449 bool mSelectionCached
; // Whether mSelectionProperties is valid
450 mutable bool mSelectionRestoreEagerInit
; // Whether we're eager initing because of selection restore
451 bool mPlaceholderVisibility
;
452 bool mPreviewVisibility
;
453 bool mIsCommittingComposition
;
457 ImplCycleCollectionUnlink(nsTextEditorState
& aField
)
463 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
& aCallback
,
464 nsTextEditorState
& aField
,
468 aField
.Traverse(aCallback
);