/src/mozilla-central/dom/html/nsTextEditorState.h
Line | Count | Source (jump to first uncovered line) |
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 nsTextEditorState_h__ |
8 | | #define nsTextEditorState_h__ |
9 | | |
10 | | #include "nsString.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" |
21 | | |
22 | | class nsTextControlFrame; |
23 | | class nsTextInputSelectionImpl; |
24 | | class nsAnonDivObserver; |
25 | | class nsISelectionController; |
26 | | class nsFrameSelection; |
27 | | class nsITextControlElement; |
28 | | class nsFrame; |
29 | | |
30 | | namespace mozilla { |
31 | | |
32 | | class ErrorResult; |
33 | | class TextInputListener; |
34 | | |
35 | | namespace dom { |
36 | | class HTMLInputElement; |
37 | | } // namespace dom |
38 | | } // namespace mozilla |
39 | | |
40 | | /** |
41 | | * nsTextEditorState is a class which is responsible for managing the state of |
42 | | * plaintext controls. This currently includes the following HTML elements: |
43 | | * <input type=text> |
44 | | * <input type=password> |
45 | | * <textarea> |
46 | | * and also XUL controls such as <textbox> which use one of these elements behind |
47 | | * the scenes. |
48 | | * |
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 |
52 | | * class. |
53 | | * |
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: |
57 | | * |
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. |
61 | | * |
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. |
66 | | * |
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 |
73 | | * text control. |
74 | | * |
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. |
84 | | * |
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 |
90 | | * mFrameSel member. |
91 | | * |
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. |
96 | | * |
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 |
101 | | * changed. |
102 | | * |
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 |
106 | | * text field. |
107 | | * |
108 | | * |
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 |
112 | | * in. |
113 | | * |
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. |
118 | | * |
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. |
122 | | * |
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. |
126 | | * |
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. |
130 | | */ |
131 | | |
132 | | class RestoreSelectionState; |
133 | | |
134 | | class nsTextEditorState : public mozilla::SupportsWeakPtr<nsTextEditorState> { |
135 | | public: |
136 | | MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsTextEditorState) |
137 | | explicit nsTextEditorState(nsITextControlElement* aOwningElement); |
138 | | static nsTextEditorState* |
139 | | Construct(nsITextControlElement* aOwningElement, |
140 | | nsTextEditorState** aReusedState); |
141 | | ~nsTextEditorState(); |
142 | | |
143 | | void Traverse(nsCycleCollectionTraversalCallback& cb); |
144 | | void Unlink(); |
145 | | |
146 | | void PrepareForReuse() |
147 | 0 | { |
148 | 0 | Unlink(); |
149 | 0 | mValue.reset(); |
150 | 0 | mValueBeingSet.Truncate(); |
151 | 0 | mTextCtrlElement = nullptr; |
152 | 0 | } |
153 | | |
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(); |
161 | | |
162 | | enum SetValueFlags |
163 | | { |
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 |
181 | | // undo history. |
182 | | eSetValue_ForXUL = 1 << 4, |
183 | | }; |
184 | | MOZ_MUST_USE bool SetValue(const nsAString& aValue, |
185 | | const nsAString* aOldValue, |
186 | | uint32_t aFlags); |
187 | | MOZ_MUST_USE bool SetValue(const nsAString& aValue, |
188 | | uint32_t aFlags) |
189 | 0 | { |
190 | 0 | return SetValue(aValue, nullptr, aFlags); |
191 | 0 | } |
192 | | void GetValue(nsAString& aValue, bool aIgnoreWrap) const; |
193 | | bool HasNonEmptyValue(); |
194 | | // The following methods are for textarea element to use whether default |
195 | | // value or not. |
196 | | // XXX We might have to add assertion when it is into editable, |
197 | | // or reconsider fixing bug 597525 to remove these. |
198 | 0 | void EmptyValue() { if (mValue) mValue->Truncate(); } |
199 | 0 | bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; } |
200 | | |
201 | | mozilla::dom::Element* GetRootNode(); |
202 | | mozilla::dom::Element* GetPreviewNode(); |
203 | | |
204 | 0 | bool IsSingleLineTextControl() const { |
205 | 0 | return mTextCtrlElement->IsSingleLineTextControl(); |
206 | 0 | } |
207 | | bool IsTextArea() const { |
208 | | return mTextCtrlElement->IsTextArea(); |
209 | | } |
210 | 0 | bool IsPasswordTextControl() const { |
211 | 0 | return mTextCtrlElement->IsPasswordTextControl(); |
212 | 0 | } |
213 | | int32_t GetCols() { |
214 | | return mTextCtrlElement->GetCols(); |
215 | | } |
216 | 0 | int32_t GetWrapCols() { |
217 | 0 | return mTextCtrlElement->GetWrapCols(); |
218 | 0 | } |
219 | | int32_t GetRows() { |
220 | | return mTextCtrlElement->GetRows(); |
221 | | } |
222 | | |
223 | | void UpdateOverlayTextVisibility(bool aNotify); |
224 | | |
225 | | // placeholder methods |
226 | 0 | bool GetPlaceholderVisibility() { |
227 | 0 | return mPlaceholderVisibility; |
228 | 0 | } |
229 | | |
230 | | // preview methods |
231 | | void SetPreviewText(const nsAString& aValue, bool aNotify); |
232 | | void GetPreviewText(nsAString& aValue); |
233 | 0 | bool GetPreviewVisibility() { |
234 | 0 | return mPreviewVisibility; |
235 | 0 | } |
236 | | |
237 | | /** |
238 | | * Get the maxlength attribute |
239 | | * @param aMaxLength the value of the max length attr |
240 | | * @returns false if attr not defined |
241 | | */ |
242 | | int32_t GetMaxLength(); |
243 | | |
244 | | void HideSelectionIfBlurred(); |
245 | | |
246 | | struct SelectionProperties { |
247 | | public: |
248 | | SelectionProperties() : mStart(0), mEnd(0), |
249 | 0 | mDirection(nsITextControlFrame::eForward) {} |
250 | | bool IsDefault() const |
251 | 0 | { |
252 | 0 | return mStart == 0 && mEnd == 0 && |
253 | 0 | mDirection == nsITextControlFrame::eForward; |
254 | 0 | } |
255 | | uint32_t GetStart() const |
256 | 0 | { |
257 | 0 | return mStart; |
258 | 0 | } |
259 | | void SetStart(uint32_t value) |
260 | 0 | { |
261 | 0 | mIsDirty = true; |
262 | 0 | mStart = value; |
263 | 0 | } |
264 | | uint32_t GetEnd() const |
265 | 0 | { |
266 | 0 | return mEnd; |
267 | 0 | } |
268 | | void SetEnd(uint32_t value) |
269 | 0 | { |
270 | 0 | mIsDirty = true; |
271 | 0 | mEnd = value; |
272 | 0 | } |
273 | | nsITextControlFrame::SelectionDirection GetDirection() const |
274 | 0 | { |
275 | 0 | return mDirection; |
276 | 0 | } |
277 | | void SetDirection(nsITextControlFrame::SelectionDirection value) |
278 | 0 | { |
279 | 0 | mIsDirty = true; |
280 | 0 | mDirection = value; |
281 | 0 | } |
282 | | // return true only if mStart, mEnd, or mDirection have been modified, |
283 | | // or if SetIsDirty() was explicitly called. |
284 | | bool IsDirty() const |
285 | 0 | { |
286 | 0 | return mIsDirty; |
287 | 0 | } |
288 | | void SetIsDirty() |
289 | 0 | { |
290 | 0 | mIsDirty = true; |
291 | 0 | } |
292 | | private: |
293 | | uint32_t mStart, mEnd; |
294 | | bool mIsDirty = false; |
295 | | nsITextControlFrame::SelectionDirection mDirection; |
296 | | }; |
297 | | |
298 | | bool IsSelectionCached() const; |
299 | | SelectionProperties& GetSelectionProperties(); |
300 | | void SetSelectionProperties(SelectionProperties& aProps); |
301 | 0 | void WillInitEagerly() { mSelectionRestoreEagerInit = true; } |
302 | 0 | 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(); |
307 | | |
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); |
311 | | |
312 | | // Get the selection direction |
313 | | nsITextControlFrame::SelectionDirection |
314 | | GetSelectionDirection(mozilla::ErrorResult& aRv); |
315 | | |
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. |
323 | | // |
324 | | // If we have a frame, this method will scroll the selection into view. |
325 | | // |
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); |
331 | | |
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); |
339 | | |
340 | | // Set the selection start. This basically implements the |
341 | | // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionstart |
342 | | // setter. |
343 | | void SetSelectionStart(const mozilla::dom::Nullable<uint32_t>& aStart, |
344 | | mozilla::ErrorResult& aRv); |
345 | | |
346 | | // Set the selection end. This basically implements the |
347 | | // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionend |
348 | | // setter. |
349 | | void SetSelectionEnd(const mozilla::dom::Nullable<uint32_t>& aEnd, |
350 | | mozilla::ErrorResult& aRv); |
351 | | |
352 | | // Get the selection direction as a string. This implements the |
353 | | // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection |
354 | | // getter. |
355 | | void GetSelectionDirectionString(nsAString& aDirection, |
356 | | mozilla::ErrorResult& aRv); |
357 | | |
358 | | // Set the selection direction. This basically implements the |
359 | | // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection |
360 | | // setter. |
361 | | void SetSelectionDirection(const nsAString& aDirection, |
362 | | mozilla::ErrorResult& aRv); |
363 | | |
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 = |
373 | | mozilla::Nothing(), |
374 | | const mozilla::Maybe<uint32_t>& aSelectionEnd = |
375 | | mozilla::Nothing()); |
376 | | |
377 | | void UpdateEditableState(bool aNotify) { |
378 | | if (auto* root = GetRootNode()) { |
379 | | root->UpdateEditableState(aNotify); |
380 | | } |
381 | | } |
382 | | |
383 | | private: |
384 | | friend class RestoreSelectionState; |
385 | | |
386 | | // not copy constructible |
387 | | nsTextEditorState(const nsTextEditorState&); |
388 | | // not assignable |
389 | | void operator= (const nsTextEditorState&); |
390 | | |
391 | | void ValueWasChanged(bool aNotify); |
392 | | |
393 | | void DestroyEditor(); |
394 | | void Clear(); |
395 | | |
396 | | nsresult InitializeRootNode(); |
397 | | |
398 | | void FinishedRestoringSelection(); |
399 | | |
400 | | mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const; |
401 | | |
402 | | bool EditorHasComposition(); |
403 | | |
404 | | class InitializationGuard { |
405 | | public: |
406 | | explicit InitializationGuard(nsTextEditorState& aState) : |
407 | | mState(aState), |
408 | | mGuardSet(false) |
409 | 0 | { |
410 | 0 | if (!mState.mInitializing) { |
411 | 0 | mGuardSet = true; |
412 | 0 | mState.mInitializing = true; |
413 | 0 | } |
414 | 0 | } |
415 | 0 | ~InitializationGuard() { |
416 | 0 | if (mGuardSet) { |
417 | 0 | mState.mInitializing = false; |
418 | 0 | } |
419 | 0 | } |
420 | 0 | bool IsInitializingRecursively() const { |
421 | 0 | return !mGuardSet; |
422 | 0 | } |
423 | | private: |
424 | | nsTextEditorState& mState; |
425 | | bool mGuardSet; |
426 | | }; |
427 | | friend class InitializationGuard; |
428 | | friend class PrepareEditorEvent; |
429 | | |
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; |
454 | | }; |
455 | | |
456 | | inline void |
457 | | ImplCycleCollectionUnlink(nsTextEditorState& aField) |
458 | 0 | { |
459 | 0 | aField.Unlink(); |
460 | 0 | } |
461 | | |
462 | | inline void |
463 | | ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, |
464 | | nsTextEditorState& aField, |
465 | | const char* aName, |
466 | | uint32_t aFlags = 0) |
467 | 0 | { |
468 | 0 | aField.Traverse(aCallback); |
469 | 0 | } |
470 | | |
471 | | #endif |