Line data Source code
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 nsTextInputListener;
23 : class nsTextControlFrame;
24 : class nsTextInputSelectionImpl;
25 : class nsAnonDivObserver;
26 : class nsISelectionController;
27 : class nsFrameSelection;
28 : class nsITextControlElement;
29 : class nsFrame;
30 :
31 : namespace mozilla {
32 :
33 : class ErrorResult;
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 12 : 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 1 : void PrepareForReuse()
147 : {
148 1 : Unlink();
149 1 : mValue.reset();
150 1 : mCachedValue.Truncate();
151 1 : mValueBeingSet.Truncate();
152 1 : mTextCtrlElement = nullptr;
153 1 : MOZ_ASSERT(!mMutationObserver);
154 1 : }
155 :
156 : mozilla::TextEditor* GetTextEditor();
157 : nsISelectionController* GetSelectionController() const;
158 : nsFrameSelection* GetConstFrameSelection();
159 : nsresult BindToFrame(nsTextControlFrame* aFrame);
160 : void UnbindFromFrame(nsTextControlFrame* aFrame);
161 : nsresult PrepareEditor(const nsAString *aValue = nullptr);
162 : void InitializeKeyboardEventListeners();
163 :
164 : enum SetValueFlags
165 : {
166 : // The call is for internal processing.
167 : eSetValue_Internal = 0,
168 : // The value is changed by a call of setUserInput() from chrome.
169 : eSetValue_BySetUserInput = 1 << 0,
170 : // The value is changed by changing value attribute of the element or
171 : // something like setRangeText().
172 : eSetValue_ByContent = 1 << 1,
173 : // Whether the value change should be notified to the frame/contet nor not.
174 : eSetValue_Notify = 1 << 2,
175 : // Whether to move the cursor to end of the value (in the case when we have
176 : // cached selection offsets), in the case when the value has changed. If
177 : // this is not set, the cached selection offsets will simply be clamped to
178 : // be within the length of the new value. In either case, if the value has
179 : // not changed the cursor won't move.
180 : eSetValue_MoveCursorToEndIfValueChanged = 1 << 3,
181 : };
182 : MOZ_MUST_USE bool SetValue(const nsAString& aValue,
183 : const nsAString* aOldValue,
184 : uint32_t aFlags);
185 2 : MOZ_MUST_USE bool SetValue(const nsAString& aValue,
186 : uint32_t aFlags)
187 : {
188 2 : return SetValue(aValue, nullptr, aFlags);
189 : }
190 : void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
191 : bool HasNonEmptyValue();
192 : // The following methods are for textarea element to use whether default
193 : // value or not.
194 : // XXX We might have to add assertion when it is into editable,
195 : // or reconsider fixing bug 597525 to remove these.
196 0 : void EmptyValue() { if (mValue) mValue->Truncate(); }
197 0 : bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
198 :
199 : nsresult CreatePlaceholderNode();
200 : nsresult CreatePreviewNode();
201 : mozilla::dom::Element* CreateEmptyDivNode();
202 :
203 25 : mozilla::dom::Element* GetRootNode() {
204 25 : return mRootNode;
205 : }
206 106 : mozilla::dom::Element* GetPlaceholderNode() {
207 106 : return mPlaceholderDiv;
208 : }
209 85 : mozilla::dom::Element* GetPreviewNode() {
210 85 : return mPreviewDiv;
211 : }
212 :
213 21 : bool IsSingleLineTextControl() const {
214 21 : return mTextCtrlElement->IsSingleLineTextControl();
215 : }
216 : bool IsTextArea() const {
217 : return mTextCtrlElement->IsTextArea();
218 : }
219 6 : bool IsPlainTextControl() const {
220 6 : return mTextCtrlElement->IsPlainTextControl();
221 : }
222 4 : bool IsPasswordTextControl() const {
223 4 : return mTextCtrlElement->IsPasswordTextControl();
224 : }
225 : int32_t GetCols() {
226 : return mTextCtrlElement->GetCols();
227 : }
228 5 : int32_t GetWrapCols() {
229 5 : return mTextCtrlElement->GetWrapCols();
230 : }
231 : int32_t GetRows() {
232 : return mTextCtrlElement->GetRows();
233 : }
234 :
235 : void UpdateOverlayTextVisibility(bool aNotify);
236 :
237 : // placeholder methods
238 48 : bool GetPlaceholderVisibility() {
239 48 : return mPlaceholderVisibility;
240 : }
241 : void UpdatePlaceholderText(bool aNotify);
242 :
243 : // preview methods
244 : void SetPreviewText(const nsAString& aValue, bool aNotify);
245 : void GetPreviewText(nsAString& aValue);
246 0 : bool GetPreviewVisibility() {
247 0 : return mPreviewVisibility;
248 : }
249 :
250 : /**
251 : * Get the maxlength attribute
252 : * @param aMaxLength the value of the max length attr
253 : * @returns false if attr not defined
254 : */
255 : int32_t GetMaxLength();
256 :
257 3 : void ClearValueCache() { mCachedValue.Truncate(); }
258 :
259 : void HideSelectionIfBlurred();
260 :
261 : struct SelectionProperties {
262 : public:
263 17 : SelectionProperties() : mStart(0), mEnd(0),
264 17 : mDirection(nsITextControlFrame::eForward) {}
265 1 : bool IsDefault() const
266 : {
267 2 : return mStart == 0 && mEnd == 0 &&
268 2 : mDirection == nsITextControlFrame::eForward;
269 : }
270 5 : uint32_t GetStart() const
271 : {
272 5 : return mStart;
273 : }
274 1 : void SetStart(uint32_t value)
275 : {
276 1 : mIsDirty = true;
277 1 : mStart = value;
278 1 : }
279 5 : uint32_t GetEnd() const
280 : {
281 5 : return mEnd;
282 : }
283 1 : void SetEnd(uint32_t value)
284 : {
285 1 : mIsDirty = true;
286 1 : mEnd = value;
287 1 : }
288 2 : nsITextControlFrame::SelectionDirection GetDirection() const
289 : {
290 2 : return mDirection;
291 : }
292 1 : void SetDirection(nsITextControlFrame::SelectionDirection value)
293 : {
294 1 : mIsDirty = true;
295 1 : mDirection = value;
296 1 : }
297 : // return true only if mStart, mEnd, or mDirection have been modified,
298 : // or if SetIsDirty() was explicitly called.
299 2 : bool IsDirty() const
300 : {
301 2 : return mIsDirty;
302 : }
303 5 : void SetIsDirty()
304 : {
305 5 : mIsDirty = true;
306 5 : }
307 : private:
308 : uint32_t mStart, mEnd;
309 : bool mIsDirty = false;
310 : nsITextControlFrame::SelectionDirection mDirection;
311 : };
312 :
313 : bool IsSelectionCached() const;
314 : SelectionProperties& GetSelectionProperties();
315 : void SetSelectionProperties(SelectionProperties& aProps);
316 0 : void WillInitEagerly() { mSelectionRestoreEagerInit = true; }
317 4 : bool HasNeverInitializedBefore() const { return !mEverInited; }
318 : // Sync up our selection properties with our editor prior to being destroyed.
319 : // This will invoke UnbindFromFrame() to ensure that we grab whatever
320 : // selection state may be at the moment.
321 : void SyncUpSelectionPropertiesBeforeDestruction();
322 :
323 : // Get the selection range start and end points in our text.
324 : void GetSelectionRange(uint32_t* aSelectionStart, uint32_t* aSelectionEnd,
325 : mozilla::ErrorResult& aRv);
326 :
327 : // Get the selection direction
328 : nsITextControlFrame::SelectionDirection
329 : GetSelectionDirection(mozilla::ErrorResult& aRv);
330 :
331 : // Set the selection range (start, end, direction). aEnd is allowed to be
332 : // smaller than aStart; in that case aStart will be reset to the same value as
333 : // aEnd. This basically implements
334 : // https://html.spec.whatwg.org/multipage/forms.html#set-the-selection-range
335 : // but with the start/end already coerced to zero if null (and without the
336 : // special infinity value), and the direction already converted to a
337 : // SelectionDirection.
338 : //
339 : // If we have a frame, this method will scroll the selection into view.
340 : //
341 : // XXXbz This should really take uint32_t, but none of our guts (either the
342 : // frame or our cached selection state) work with uint32_t at the moment...
343 : void SetSelectionRange(uint32_t aStart, uint32_t aEnd,
344 : nsITextControlFrame::SelectionDirection aDirection,
345 : mozilla::ErrorResult& aRv);
346 :
347 : // Set the selection range, but with an optional string for the direction.
348 : // This will convert aDirection to an nsITextControlFrame::SelectionDirection
349 : // and then call our other SetSelectionRange overload.
350 : void SetSelectionRange(uint32_t aSelectionStart,
351 : uint32_t aSelectionEnd,
352 : const mozilla::dom::Optional<nsAString>& aDirection,
353 : mozilla::ErrorResult& aRv);
354 :
355 : // Set the selection start. This basically implements the
356 : // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionstart
357 : // setter.
358 : void SetSelectionStart(const mozilla::dom::Nullable<uint32_t>& aStart,
359 : mozilla::ErrorResult& aRv);
360 :
361 : // Set the selection end. This basically implements the
362 : // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectionend
363 : // setter.
364 : void SetSelectionEnd(const mozilla::dom::Nullable<uint32_t>& aEnd,
365 : mozilla::ErrorResult& aRv);
366 :
367 : // Get the selection direction as a string. This implements the
368 : // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
369 : // getter.
370 : void GetSelectionDirectionString(nsAString& aDirection,
371 : mozilla::ErrorResult& aRv);
372 :
373 : // Set the selection direction. This basically implements the
374 : // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-selectiondirection
375 : // setter.
376 : void SetSelectionDirection(const nsAString& aDirection,
377 : mozilla::ErrorResult& aRv);
378 :
379 : // Set the range text. This basically implements
380 : // https://html.spec.whatwg.org/multipage/forms.html#dom-textarea/input-setrangetext
381 : void SetRangeText(const nsAString& aReplacement, mozilla::ErrorResult& aRv);
382 : // The last two arguments are -1 if we don't know our selection range;
383 : // otherwise they're the start and end of our selection range.
384 : void SetRangeText(const nsAString& aReplacement, uint32_t aStart,
385 : uint32_t aEnd, mozilla::dom::SelectionMode aSelectMode,
386 : mozilla::ErrorResult& aRv,
387 : const mozilla::Maybe<uint32_t>& aSelectionStart =
388 : mozilla::Nothing(),
389 : const mozilla::Maybe<uint32_t>& aSelectionEnd =
390 : mozilla::Nothing());
391 :
392 : void UpdateEditableState(bool aNotify) {
393 : if (mRootNode) {
394 : mRootNode->UpdateEditableState(aNotify);
395 : }
396 : }
397 :
398 : private:
399 : friend class RestoreSelectionState;
400 :
401 : // not copy constructible
402 : nsTextEditorState(const nsTextEditorState&);
403 : // not assignable
404 : void operator= (const nsTextEditorState&);
405 :
406 : nsresult CreateRootNode();
407 :
408 : void ValueWasChanged(bool aNotify);
409 :
410 : void DestroyEditor();
411 : void Clear();
412 :
413 : nsresult InitializeRootNode();
414 :
415 : void FinishedRestoringSelection();
416 :
417 : mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const;
418 :
419 : bool EditorHasComposition();
420 :
421 : class InitializationGuard {
422 : public:
423 2 : explicit InitializationGuard(nsTextEditorState& aState) :
424 : mState(aState),
425 2 : mGuardSet(false)
426 : {
427 2 : if (!mState.mInitializing) {
428 2 : mGuardSet = true;
429 2 : mState.mInitializing = true;
430 : }
431 2 : }
432 4 : ~InitializationGuard() {
433 2 : if (mGuardSet) {
434 2 : mState.mInitializing = false;
435 : }
436 2 : }
437 2 : bool IsInitializingRecursively() const {
438 2 : return !mGuardSet;
439 : }
440 : private:
441 : nsTextEditorState& mState;
442 : bool mGuardSet;
443 : };
444 : friend class InitializationGuard;
445 : friend class PrepareEditorEvent;
446 :
447 : // The text control element owns this object, and ensures that this object
448 : // has a smaller lifetime.
449 : nsITextControlElement* MOZ_NON_OWNING_REF mTextCtrlElement;
450 : // mSelCon is non-null while we have an mBoundFrame.
451 : RefPtr<nsTextInputSelectionImpl> mSelCon;
452 : RefPtr<RestoreSelectionState> mRestoringSelection;
453 : RefPtr<mozilla::TextEditor> mTextEditor;
454 : nsCOMPtr<mozilla::dom::Element> mRootNode;
455 : nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
456 : nsCOMPtr<mozilla::dom::Element> mPreviewDiv;
457 : nsTextControlFrame* mBoundFrame;
458 : RefPtr<nsTextInputListener> mTextListener;
459 : mozilla::Maybe<nsString> mValue;
460 : RefPtr<nsAnonDivObserver> mMutationObserver;
461 : mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
462 : // mValueBeingSet is available only while SetValue() is requesting to commit
463 : // composition. I.e., this is valid only while mIsCommittingComposition is
464 : // true. While active composition is being committed, GetValue() needs
465 : // the latest value which is set by SetValue(). So, this is cache for that.
466 : nsString mValueBeingSet;
467 : SelectionProperties mSelectionProperties;
468 : bool mEverInited; // Have we ever been initialized?
469 : bool mEditorInitialized;
470 : bool mInitializing; // Whether we're in the process of initialization
471 : bool mValueTransferInProgress; // Whether a value is being transferred to the frame
472 : bool mSelectionCached; // Whether mSelectionProperties is valid
473 : mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
474 : bool mPlaceholderVisibility;
475 : bool mPreviewVisibility;
476 : bool mIsCommittingComposition;
477 : };
478 :
479 : inline void
480 0 : ImplCycleCollectionUnlink(nsTextEditorState& aField)
481 : {
482 0 : aField.Unlink();
483 0 : }
484 :
485 : inline void
486 0 : ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
487 : nsTextEditorState& aField,
488 : const char* aName,
489 : uint32_t aFlags = 0)
490 : {
491 0 : aField.Traverse(aCallback);
492 0 : }
493 :
494 : #endif
|