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 #include "TextControlState.h"
8 #include "mozilla/TextInputListener.h"
9
10 #include "nsCOMPtr.h"
11 #include "nsView.h"
12 #include "nsCaret.h"
13 #include "nsLayoutCID.h"
14 #include "nsITextControlFrame.h"
15 #include "nsContentCreatorFunctions.h"
16 #include "nsTextControlFrame.h"
17 #include "nsIControllers.h"
18 #include "nsIControllerContext.h"
19 #include "nsAttrValue.h"
20 #include "nsAttrValueInlines.h"
21 #include "nsGenericHTMLElement.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsIWidget.h"
24 #include "nsIDocumentEncoder.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsServiceManagerUtils.h"
27 #include "mozilla/dom/Selection.h"
28 #include "mozilla/EventListenerManager.h"
29 #include "nsContentUtils.h"
30 #include "mozilla/Preferences.h"
31 #include "nsTextNode.h"
32 #include "nsIController.h"
33 #include "nsIScrollableFrame.h"
34 #include "mozilla/AutoRestore.h"
35 #include "mozilla/InputEventOptions.h"
36 #include "mozilla/PresShell.h"
37 #include "mozilla/TextEvents.h"
38 #include "mozilla/dom/Event.h"
39 #include "mozilla/dom/ScriptSettings.h"
40 #include "mozilla/dom/HTMLInputElement.h"
41 #include "mozilla/dom/HTMLTextAreaElement.h"
42 #include "mozilla/dom/Text.h"
43 #include "mozilla/StaticPrefs_dom.h"
44 #include "nsFrameSelection.h"
45 #include "mozilla/ErrorResult.h"
46 #include "mozilla/Telemetry.h"
47 #include "mozilla/ShortcutKeys.h"
48 #include "mozilla/KeyEventHandler.h"
49 #include "mozilla/dom/KeyboardEvent.h"
50 #include "mozilla/ScrollTypes.h"
51
52 namespace mozilla {
53
54 using namespace dom;
55 using ValueSetterOption = TextControlState::ValueSetterOption;
56 using ValueSetterOptions = TextControlState::ValueSetterOptions;
57
58 /*****************************************************************************
59 * TextControlElement
60 *****************************************************************************/
61
62 NS_IMPL_CYCLE_COLLECTION_CLASS(TextControlElement)
63
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextControlElement,nsGenericHTMLFormElementWithState)64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
65 TextControlElement, nsGenericHTMLFormElementWithState)
66 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
67
68 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
69 TextControlElement, nsGenericHTMLFormElementWithState)
70 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
71
72 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
73 TextControlElement, nsGenericHTMLFormElementWithState)
74
75 /*static*/
76 bool TextControlElement::GetWrapPropertyEnum(
77 nsIContent* aContent, TextControlElement::nsHTMLTextWrap& aWrapProp) {
78 // soft is the default; "physical" defaults to soft as well because all other
79 // browsers treat it that way and there is no real reason to maintain physical
80 // and virtual as separate entities if no one else does. Only hard and off
81 // do anything different.
82 aWrapProp = eHTMLTextWrap_Soft; // the default
83
84 if (!aContent->IsHTMLElement()) {
85 return false;
86 }
87
88 static mozilla::dom::Element::AttrValuesArray strings[] = {
89 nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
90 switch (aContent->AsElement()->FindAttrValueIn(
91 kNameSpaceID_None, nsGkAtoms::wrap, strings, eIgnoreCase)) {
92 case 0:
93 aWrapProp = eHTMLTextWrap_Hard;
94 break;
95 case 1:
96 aWrapProp = eHTMLTextWrap_Off;
97 break;
98 }
99
100 return true;
101 }
102
103 /*static*/
104 already_AddRefed<TextControlElement>
GetTextControlElementFromEditingHost(nsIContent * aHost)105 TextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
106 if (!aHost) {
107 return nullptr;
108 }
109
110 RefPtr<TextControlElement> parent =
111 TextControlElement::FromNodeOrNull(aHost->GetParent());
112 return parent.forget();
113 }
114
115 using ValueChangeKind = TextControlElement::ValueChangeKind;
116
SetEditorFlagsIfNecessary(EditorBase & aEditorBase,uint32_t aFlags)117 MOZ_CAN_RUN_SCRIPT inline nsresult SetEditorFlagsIfNecessary(
118 EditorBase& aEditorBase, uint32_t aFlags) {
119 if (aEditorBase.Flags() == aFlags) {
120 return NS_OK;
121 }
122 return aEditorBase.SetFlags(aFlags);
123 }
124
125 /*****************************************************************************
126 * mozilla::AutoInputEventSuppresser
127 *****************************************************************************/
128
129 class MOZ_STACK_CLASS AutoInputEventSuppresser final {
130 public:
AutoInputEventSuppresser(TextEditor * aTextEditor)131 explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
132 : mTextEditor(aTextEditor),
133 // To protect against a reentrant call to SetValue, we check whether
134 // another SetValue is already happening for this editor. If it is,
135 // we must wait until we unwind to re-enable oninput events.
136 mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent()) {
137 MOZ_ASSERT(mTextEditor);
138 mTextEditor->SuppressDispatchingInputEvent(true);
139 }
~AutoInputEventSuppresser()140 ~AutoInputEventSuppresser() {
141 mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
142 }
143
144 private:
145 RefPtr<TextEditor> mTextEditor;
146 bool mOuterTransaction;
147 };
148
149 /*****************************************************************************
150 * mozilla::RestoreSelectionState
151 *****************************************************************************/
152
153 class RestoreSelectionState : public Runnable {
154 public:
RestoreSelectionState(TextControlState * aState,nsTextControlFrame * aFrame)155 RestoreSelectionState(TextControlState* aState, nsTextControlFrame* aFrame)
156 : Runnable("RestoreSelectionState"),
157 mFrame(aFrame),
158 mTextControlState(aState) {}
159
Run()160 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
161 if (!mTextControlState) {
162 return NS_OK;
163 }
164
165 AutoHideSelectionChanges hideSelectionChanges(
166 mFrame->GetConstFrameSelection());
167
168 if (mFrame) {
169 // SetSelectionRange leads to
170 // Selection::AddRangeAndSelectFramesAndNotifyListeners which flushes
171 // Layout - need to block script to avoid nested PrepareEditor calls (bug
172 // 642800).
173 nsAutoScriptBlocker scriptBlocker;
174 TextControlState::SelectionProperties& properties =
175 mTextControlState->GetSelectionProperties();
176 if (properties.IsDirty()) {
177 mFrame->SetSelectionRange(properties.GetStart(), properties.GetEnd(),
178 properties.GetDirection());
179 }
180 }
181
182 if (mTextControlState) {
183 mTextControlState->FinishedRestoringSelection();
184 }
185 return NS_OK;
186 }
187
188 // Let the text editor tell us we're no longer relevant - avoids use of
189 // AutoWeakFrame
Revoke()190 void Revoke() {
191 mFrame = nullptr;
192 mTextControlState = nullptr;
193 }
194
195 private:
196 nsTextControlFrame* mFrame;
197 TextControlState* mTextControlState;
198 };
199
200 /*****************************************************************************
201 * mozilla::AutoRestoreEditorState
202 *****************************************************************************/
203
204 class MOZ_RAII AutoRestoreEditorState final {
205 public:
AutoRestoreEditorState(TextEditor * aTextEditor)206 MOZ_CAN_RUN_SCRIPT explicit AutoRestoreEditorState(TextEditor* aTextEditor)
207 : mTextEditor(aTextEditor),
208 mSavedFlags(mTextEditor->Flags()),
209 mSavedMaxLength(mTextEditor->MaxTextLength()),
210 mSavedEchoingPasswordPrevented(
211 mTextEditor->EchoingPasswordPrevented()) {
212 MOZ_ASSERT(mTextEditor);
213
214 // EditorBase::SetFlags() is a virtual method. Even though it does nothing
215 // if new flags and current flags are same, the calling cost causes
216 // appearing the method in profile. So, this class should check if it's
217 // necessary to call.
218 uint32_t flags = mSavedFlags;
219 flags &= ~nsIEditor::eEditorReadonlyMask;
220 if (mSavedFlags != flags) {
221 // It's aTextEditor and whose lifetime must be guaranteed by the caller.
222 MOZ_KnownLive(mTextEditor)->SetFlags(flags);
223 }
224 mTextEditor->PreventToEchoPassword();
225 mTextEditor->SetMaxTextLength(-1);
226 }
227
~AutoRestoreEditorState()228 MOZ_CAN_RUN_SCRIPT ~AutoRestoreEditorState() {
229 if (!mSavedEchoingPasswordPrevented) {
230 mTextEditor->AllowToEchoPassword();
231 }
232 mTextEditor->SetMaxTextLength(mSavedMaxLength);
233 // mTextEditor's lifetime must be guaranteed by owner of the instance
234 // since the constructor is marked as `MOZ_CAN_RUN_SCRIPT` and this is
235 // a stack only class.
236 SetEditorFlagsIfNecessary(MOZ_KnownLive(*mTextEditor), mSavedFlags);
237 }
238
239 private:
240 TextEditor* mTextEditor;
241 uint32_t mSavedFlags;
242 int32_t mSavedMaxLength;
243 bool mSavedEchoingPasswordPrevented;
244 };
245
246 /*****************************************************************************
247 * mozilla::AutoDisableUndo
248 *****************************************************************************/
249
250 class MOZ_RAII AutoDisableUndo final {
251 public:
AutoDisableUndo(TextEditor * aTextEditor)252 explicit AutoDisableUndo(TextEditor* aTextEditor)
253 : mTextEditor(aTextEditor), mNumberOfMaximumTransactions(0) {
254 MOZ_ASSERT(mTextEditor);
255
256 mNumberOfMaximumTransactions =
257 mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
258 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
259 NS_WARNING_ASSERTION(disabledUndoRedo,
260 "Failed to disable undo/redo transactions");
261 }
262
~AutoDisableUndo()263 ~AutoDisableUndo() {
264 // Don't change enable/disable of undo/redo if it's enabled after
265 // it's disabled by the constructor because we shouldn't change
266 // the maximum undo/redo count to the old value.
267 if (mTextEditor->IsUndoRedoEnabled()) {
268 return;
269 }
270 // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
271 // than 0. Only when it's 0, it was disabled.
272 if (mNumberOfMaximumTransactions) {
273 DebugOnly<bool> enabledUndoRedo =
274 mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
275 NS_WARNING_ASSERTION(enabledUndoRedo,
276 "Failed to enable undo/redo transactions");
277 } else {
278 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
279 NS_WARNING_ASSERTION(disabledUndoRedo,
280 "Failed to disable undo/redo transactions");
281 }
282 }
283
284 private:
285 TextEditor* mTextEditor;
286 int32_t mNumberOfMaximumTransactions;
287 };
288
SuppressEventHandlers(nsPresContext * aPresContext)289 static bool SuppressEventHandlers(nsPresContext* aPresContext) {
290 bool suppressHandlers = false;
291
292 if (aPresContext) {
293 // Right now we only suppress event handlers and controller manipulation
294 // when in a print preview or print context!
295
296 // In the current implementation, we only paginate when
297 // printing or in print preview.
298
299 suppressHandlers = aPresContext->IsPaginated();
300 }
301
302 return suppressHandlers;
303 }
304
305 /*****************************************************************************
306 * mozilla::TextInputSelectionController
307 *****************************************************************************/
308
309 class TextInputSelectionController final : public nsSupportsWeakReference,
310 public nsISelectionController {
311 ~TextInputSelectionController() = default;
312
313 public:
314 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
315 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputSelectionController,
316 nsISelectionController)
317
318 TextInputSelectionController(PresShell* aPresShell, nsIContent* aLimiter);
319
320 void SetScrollableFrame(nsIScrollableFrame* aScrollableFrame);
GetConstFrameSelection()321 nsFrameSelection* GetConstFrameSelection() { return mFrameSelection; }
322 // Will return null if !mFrameSelection.
323 Selection* GetSelection(SelectionType aSelectionType);
324
325 // NSISELECTIONCONTROLLER INTERFACES
326 NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
327 NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
328 NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
329 NS_IMETHOD GetSelectionFlags(int16_t* aOutEnable) override;
330 NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
331 Selection** aSelection) override;
332 Selection* GetSelection(RawSelectionType aRawSelectionType) override;
333 NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
334 int16_t aRegion, int16_t aFlags) override;
335 NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
336 nsresult RepaintSelection(nsPresContext* aPresContext,
337 SelectionType aSelectionType);
338 NS_IMETHOD SetCaretEnabled(bool enabled) override;
339 NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
340 NS_IMETHOD GetCaretEnabled(bool* _retval) override;
341 NS_IMETHOD GetCaretVisible(bool* _retval) override;
342 NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
343 NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount,
344 bool aExtend) override;
345 NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
346 NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
347 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD LineMove(bool aForward,
348 bool aExtend) override;
349 NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
350 MOZ_CAN_RUN_SCRIPT
351 NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
352 NS_IMETHOD CompleteScroll(bool aForward) override;
353 MOZ_CAN_RUN_SCRIPT NS_IMETHOD CompleteMove(bool aForward,
354 bool aExtend) override;
355 NS_IMETHOD ScrollPage(bool aForward) override;
356 NS_IMETHOD ScrollLine(bool aForward) override;
357 NS_IMETHOD ScrollCharacter(bool aRight) override;
358 NS_IMETHOD CheckVisibility(nsINode* node, int16_t startOffset,
359 int16_t EndOffset, bool* _retval) override;
360 virtual nsresult CheckVisibilityContent(nsIContent* aNode,
361 int16_t aStartOffset,
362 int16_t aEndOffset,
363 bool* aRetval) override;
364 void SelectionWillTakeFocus() override;
365 void SelectionWillLoseFocus() override;
366
367 private:
368 RefPtr<nsFrameSelection> mFrameSelection;
369 nsIScrollableFrame* mScrollFrame;
370 nsWeakPtr mPresShellWeak;
371 };
372
373 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputSelectionController)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputSelectionController)374 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputSelectionController)
375 NS_INTERFACE_TABLE_HEAD(TextInputSelectionController)
376 NS_INTERFACE_TABLE(TextInputSelectionController, nsISelectionController,
377 nsISelectionDisplay, nsISupportsWeakReference)
378 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TextInputSelectionController)
379 NS_INTERFACE_MAP_END
380
381 NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController, mFrameSelection)
382
383 TextInputSelectionController::TextInputSelectionController(
384 PresShell* aPresShell, nsIContent* aLimiter)
385 : mScrollFrame(nullptr) {
386 if (aPresShell) {
387 bool accessibleCaretEnabled =
388 PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
389 mFrameSelection =
390 new nsFrameSelection(aPresShell, aLimiter, accessibleCaretEnabled);
391 mPresShellWeak = do_GetWeakReference(aPresShell);
392 }
393 }
394
SetScrollableFrame(nsIScrollableFrame * aScrollableFrame)395 void TextInputSelectionController::SetScrollableFrame(
396 nsIScrollableFrame* aScrollableFrame) {
397 mScrollFrame = aScrollableFrame;
398 if (!mScrollFrame && mFrameSelection) {
399 mFrameSelection->DisconnectFromPresShell();
400 mFrameSelection = nullptr;
401 }
402 }
403
GetSelection(SelectionType aSelectionType)404 Selection* TextInputSelectionController::GetSelection(
405 SelectionType aSelectionType) {
406 if (!mFrameSelection) {
407 return nullptr;
408 }
409
410 return mFrameSelection->GetSelection(aSelectionType);
411 }
412
413 NS_IMETHODIMP
SetDisplaySelection(int16_t aToggle)414 TextInputSelectionController::SetDisplaySelection(int16_t aToggle) {
415 if (!mFrameSelection) {
416 return NS_ERROR_NULL_POINTER;
417 }
418 mFrameSelection->SetDisplaySelection(aToggle);
419 return NS_OK;
420 }
421
422 NS_IMETHODIMP
GetDisplaySelection(int16_t * aToggle)423 TextInputSelectionController::GetDisplaySelection(int16_t* aToggle) {
424 if (!mFrameSelection) {
425 return NS_ERROR_NULL_POINTER;
426 }
427 *aToggle = mFrameSelection->GetDisplaySelection();
428 return NS_OK;
429 }
430
431 NS_IMETHODIMP
SetSelectionFlags(int16_t aToggle)432 TextInputSelectionController::SetSelectionFlags(int16_t aToggle) {
433 return NS_OK; // stub this out. not used in input
434 }
435
436 NS_IMETHODIMP
GetSelectionFlags(int16_t * aOutEnable)437 TextInputSelectionController::GetSelectionFlags(int16_t* aOutEnable) {
438 *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
439 return NS_OK;
440 }
441
442 NS_IMETHODIMP
GetSelectionFromScript(RawSelectionType aRawSelectionType,Selection ** aSelection)443 TextInputSelectionController::GetSelectionFromScript(
444 RawSelectionType aRawSelectionType, Selection** aSelection) {
445 if (!mFrameSelection) {
446 return NS_ERROR_NULL_POINTER;
447 }
448
449 *aSelection =
450 mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
451
452 // GetSelection() fails only when aRawSelectionType is invalid value.
453 if (!(*aSelection)) {
454 return NS_ERROR_INVALID_ARG;
455 }
456
457 NS_ADDREF(*aSelection);
458 return NS_OK;
459 }
460
GetSelection(RawSelectionType aRawSelectionType)461 Selection* TextInputSelectionController::GetSelection(
462 RawSelectionType aRawSelectionType) {
463 return GetSelection(ToSelectionType(aRawSelectionType));
464 }
465
466 NS_IMETHODIMP
ScrollSelectionIntoView(RawSelectionType aRawSelectionType,int16_t aRegion,int16_t aFlags)467 TextInputSelectionController::ScrollSelectionIntoView(
468 RawSelectionType aRawSelectionType, int16_t aRegion, int16_t aFlags) {
469 if (!mFrameSelection) {
470 return NS_ERROR_NULL_POINTER;
471 }
472 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
473 return frameSelection->ScrollSelectionIntoView(
474 ToSelectionType(aRawSelectionType), aRegion, aFlags);
475 }
476
477 NS_IMETHODIMP
RepaintSelection(RawSelectionType aRawSelectionType)478 TextInputSelectionController::RepaintSelection(
479 RawSelectionType aRawSelectionType) {
480 if (!mFrameSelection) {
481 return NS_ERROR_NULL_POINTER;
482 }
483 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
484 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
485 }
486
RepaintSelection(nsPresContext * aPresContext,SelectionType aSelectionType)487 nsresult TextInputSelectionController::RepaintSelection(
488 nsPresContext* aPresContext, SelectionType aSelectionType) {
489 if (!mFrameSelection) {
490 return NS_ERROR_NULL_POINTER;
491 }
492 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
493 return frameSelection->RepaintSelection(aSelectionType);
494 }
495
496 NS_IMETHODIMP
SetCaretEnabled(bool enabled)497 TextInputSelectionController::SetCaretEnabled(bool enabled) {
498 if (!mPresShellWeak) {
499 return NS_ERROR_NOT_INITIALIZED;
500 }
501 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak);
502 if (!presShell) {
503 return NS_ERROR_FAILURE;
504 }
505
506 // tell the pres shell to enable the caret, rather than settings its
507 // visibility directly. this way the presShell's idea of caret visibility is
508 // maintained.
509 presShell->SetCaretEnabled(enabled);
510
511 return NS_OK;
512 }
513
514 NS_IMETHODIMP
SetCaretReadOnly(bool aReadOnly)515 TextInputSelectionController::SetCaretReadOnly(bool aReadOnly) {
516 if (!mPresShellWeak) {
517 return NS_ERROR_NOT_INITIALIZED;
518 }
519 nsresult rv;
520 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
521 if (!presShell) {
522 return NS_ERROR_FAILURE;
523 }
524 RefPtr<nsCaret> caret = presShell->GetCaret();
525 if (!caret) {
526 return NS_ERROR_FAILURE;
527 }
528
529 if (!mFrameSelection) {
530 return NS_ERROR_FAILURE;
531 }
532
533 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
534 if (selection) {
535 caret->SetCaretReadOnly(aReadOnly);
536 }
537 return NS_OK;
538 }
539
540 NS_IMETHODIMP
GetCaretEnabled(bool * _retval)541 TextInputSelectionController::GetCaretEnabled(bool* _retval) {
542 return GetCaretVisible(_retval);
543 }
544
545 NS_IMETHODIMP
GetCaretVisible(bool * _retval)546 TextInputSelectionController::GetCaretVisible(bool* _retval) {
547 if (!mPresShellWeak) {
548 return NS_ERROR_NOT_INITIALIZED;
549 }
550 nsresult rv;
551 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
552 if (!presShell) {
553 return NS_ERROR_FAILURE;
554 }
555 RefPtr<nsCaret> caret = presShell->GetCaret();
556 if (!caret) {
557 return NS_ERROR_FAILURE;
558 }
559 *_retval = caret->IsVisible();
560 return NS_OK;
561 }
562
563 NS_IMETHODIMP
SetCaretVisibilityDuringSelection(bool aVisibility)564 TextInputSelectionController::SetCaretVisibilityDuringSelection(
565 bool aVisibility) {
566 if (!mPresShellWeak) {
567 return NS_ERROR_NOT_INITIALIZED;
568 }
569 nsresult rv;
570 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
571 if (!presShell) {
572 return NS_ERROR_FAILURE;
573 }
574 RefPtr<nsCaret> caret = presShell->GetCaret();
575 if (!caret) {
576 return NS_ERROR_FAILURE;
577 }
578 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
579 if (selection) {
580 caret->SetVisibilityDuringSelection(aVisibility);
581 }
582 return NS_OK;
583 }
584
585 NS_IMETHODIMP
PhysicalMove(int16_t aDirection,int16_t aAmount,bool aExtend)586 TextInputSelectionController::PhysicalMove(int16_t aDirection, int16_t aAmount,
587 bool aExtend) {
588 if (!mFrameSelection) {
589 return NS_ERROR_NULL_POINTER;
590 }
591 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
592 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
593 }
594
595 NS_IMETHODIMP
CharacterMove(bool aForward,bool aExtend)596 TextInputSelectionController::CharacterMove(bool aForward, bool aExtend) {
597 if (!mFrameSelection) {
598 return NS_ERROR_NULL_POINTER;
599 }
600 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
601 return frameSelection->CharacterMove(aForward, aExtend);
602 }
603
604 NS_IMETHODIMP
WordMove(bool aForward,bool aExtend)605 TextInputSelectionController::WordMove(bool aForward, bool aExtend) {
606 if (!mFrameSelection) {
607 return NS_ERROR_NULL_POINTER;
608 }
609 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
610 return frameSelection->WordMove(aForward, aExtend);
611 }
612
613 NS_IMETHODIMP
LineMove(bool aForward,bool aExtend)614 TextInputSelectionController::LineMove(bool aForward, bool aExtend) {
615 if (!mFrameSelection) {
616 return NS_ERROR_NULL_POINTER;
617 }
618 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
619 nsresult result = frameSelection->LineMove(aForward, aExtend);
620 if (NS_FAILED(result)) {
621 result = CompleteMove(aForward, aExtend);
622 }
623 return result;
624 }
625
626 NS_IMETHODIMP
IntraLineMove(bool aForward,bool aExtend)627 TextInputSelectionController::IntraLineMove(bool aForward, bool aExtend) {
628 if (!mFrameSelection) {
629 return NS_ERROR_NULL_POINTER;
630 }
631 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
632 return frameSelection->IntraLineMove(aForward, aExtend);
633 }
634
635 NS_IMETHODIMP
PageMove(bool aForward,bool aExtend)636 TextInputSelectionController::PageMove(bool aForward, bool aExtend) {
637 // expected behavior for PageMove is to scroll AND move the caret
638 // and to remain relative position of the caret in view. see Bug 4302.
639 if (mScrollFrame) {
640 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
641 nsIFrame* scrollFrame = do_QueryFrame(mScrollFrame);
642 // We won't scroll parent scrollable element of mScrollFrame. Therefore,
643 // this may be handled when mScrollFrame is completely outside of the view.
644 // In such case, user may be confused since they might have wanted to
645 // scroll a parent scrollable element. For making clearer which element
646 // handles PageDown/PageUp, we should move selection into view even if
647 // selection is not changed.
648 return frameSelection->PageMove(aForward, aExtend, scrollFrame,
649 nsFrameSelection::SelectionIntoView::Yes);
650 }
651 // Similarly, if there is no scrollable frame, we should move the editor
652 // frame into the view for making it clearer which element handles
653 // PageDown/PageUp.
654 return ScrollSelectionIntoView(
655 nsISelectionController::SELECTION_NORMAL,
656 nsISelectionController::SELECTION_FOCUS_REGION,
657 nsISelectionController::SCROLL_SYNCHRONOUS |
658 nsISelectionController::SCROLL_FOR_CARET_MOVE);
659 }
660
661 NS_IMETHODIMP
CompleteScroll(bool aForward)662 TextInputSelectionController::CompleteScroll(bool aForward) {
663 if (!mScrollFrame) {
664 return NS_ERROR_NOT_INITIALIZED;
665 }
666
667 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE,
668 ScrollMode::Instant);
669 return NS_OK;
670 }
671
672 NS_IMETHODIMP
CompleteMove(bool aForward,bool aExtend)673 TextInputSelectionController::CompleteMove(bool aForward, bool aExtend) {
674 if (NS_WARN_IF(!mFrameSelection)) {
675 return NS_ERROR_NULL_POINTER;
676 }
677 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
678
679 // grab the parent / root DIV for this text widget
680 nsIContent* parentDIV = frameSelection->GetLimiter();
681 if (!parentDIV) {
682 return NS_ERROR_UNEXPECTED;
683 }
684
685 // make the caret be either at the very beginning (0) or the very end
686 int32_t offset = 0;
687 CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE;
688 if (aForward) {
689 offset = parentDIV->GetChildCount();
690
691 // Prevent the caret from being placed after the last
692 // BR node in the content tree!
693
694 if (offset > 0) {
695 nsIContent* child = parentDIV->GetLastChild();
696
697 if (child->IsHTMLElement(nsGkAtoms::br)) {
698 --offset;
699 hint = CARET_ASSOCIATE_AFTER; // for Bug 106855
700 }
701 }
702 }
703
704 const RefPtr<nsIContent> pinnedParentDIV{parentDIV};
705 const nsFrameSelection::FocusMode focusMode =
706 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
707 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
708 frameSelection->HandleClick(pinnedParentDIV, offset, offset, focusMode, hint);
709
710 // if we got this far, attempt to scroll no matter what the above result is
711 return CompleteScroll(aForward);
712 }
713
714 NS_IMETHODIMP
ScrollPage(bool aForward)715 TextInputSelectionController::ScrollPage(bool aForward) {
716 if (!mScrollFrame) {
717 return NS_ERROR_NOT_INITIALIZED;
718 }
719
720 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
721 ScrollMode::Smooth);
722 return NS_OK;
723 }
724
725 NS_IMETHODIMP
ScrollLine(bool aForward)726 TextInputSelectionController::ScrollLine(bool aForward) {
727 if (!mScrollFrame) {
728 return NS_ERROR_NOT_INITIALIZED;
729 }
730
731 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::LINES,
732 ScrollMode::Smooth);
733 return NS_OK;
734 }
735
736 NS_IMETHODIMP
ScrollCharacter(bool aRight)737 TextInputSelectionController::ScrollCharacter(bool aRight) {
738 if (!mScrollFrame) {
739 return NS_ERROR_NOT_INITIALIZED;
740 }
741
742 mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), ScrollUnit::LINES,
743 ScrollMode::Smooth);
744 return NS_OK;
745 }
746
SelectionWillTakeFocus()747 void TextInputSelectionController::SelectionWillTakeFocus() {
748 if (mFrameSelection) {
749 if (PresShell* shell = mFrameSelection->GetPresShell()) {
750 shell->FrameSelectionWillTakeFocus(*mFrameSelection);
751 }
752 }
753 }
754
SelectionWillLoseFocus()755 void TextInputSelectionController::SelectionWillLoseFocus() {
756 if (mFrameSelection) {
757 if (PresShell* shell = mFrameSelection->GetPresShell()) {
758 shell->FrameSelectionWillLoseFocus(*mFrameSelection);
759 }
760 }
761 }
762
763 NS_IMETHODIMP
CheckVisibility(nsINode * node,int16_t startOffset,int16_t EndOffset,bool * _retval)764 TextInputSelectionController::CheckVisibility(nsINode* node,
765 int16_t startOffset,
766 int16_t EndOffset,
767 bool* _retval) {
768 if (!mPresShellWeak) {
769 return NS_ERROR_NOT_INITIALIZED;
770 }
771 nsresult rv;
772 nsCOMPtr<nsISelectionController> presShell =
773 do_QueryReferent(mPresShellWeak, &rv);
774 if (!presShell) {
775 return NS_ERROR_FAILURE;
776 }
777 return presShell->CheckVisibility(node, startOffset, EndOffset, _retval);
778 }
779
CheckVisibilityContent(nsIContent * aNode,int16_t aStartOffset,int16_t aEndOffset,bool * aRetval)780 nsresult TextInputSelectionController::CheckVisibilityContent(
781 nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset,
782 bool* aRetval) {
783 if (!mPresShellWeak) {
784 return NS_ERROR_NOT_INITIALIZED;
785 }
786 nsCOMPtr<nsISelectionController> presShell = do_QueryReferent(mPresShellWeak);
787 if (NS_WARN_IF(!presShell)) {
788 return NS_ERROR_FAILURE;
789 }
790 return presShell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset,
791 aRetval);
792 }
793
794 /*****************************************************************************
795 * mozilla::TextInputListener
796 *****************************************************************************/
797
TextInputListener(TextControlElement * aTxtCtrlElement)798 TextInputListener::TextInputListener(TextControlElement* aTxtCtrlElement)
799 : mFrame(nullptr),
800 mTxtCtrlElement(aTxtCtrlElement),
801 mTextControlState(aTxtCtrlElement ? aTxtCtrlElement->GetTextControlState()
802 : nullptr),
803 mSelectionWasCollapsed(true),
804 mHadUndoItems(false),
805 mHadRedoItems(false),
806 mSettingValue(false),
807 mSetValueChanged(true),
808 mListeningToSelectionChange(false) {}
809
810 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)811 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
812
813 NS_INTERFACE_MAP_BEGIN(TextInputListener)
814 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
815 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
816 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
817 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
818 NS_INTERFACE_MAP_END
819
820 NS_IMPL_CYCLE_COLLECTION_CLASS(TextInputListener)
821 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TextInputListener)
822 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
823 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
824 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TextInputListener)
825 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
826
827 void TextInputListener::OnSelectionChange(Selection& aSelection,
828 int16_t aReason) {
829 if (!mListeningToSelectionChange) {
830 return;
831 }
832
833 AutoWeakFrame weakFrame = mFrame;
834
835 // Fire the select event
836 // The specs don't exactly say when we should fire the select event.
837 // IE: Whenever you add/remove a character to/from the selection. Also
838 // each time for select all. Also if you get to the end of the text
839 // field you will get new event for each keypress or a continuous
840 // stream of events if you use the mouse. IE will fire select event
841 // when the selection collapses to nothing if you are holding down
842 // the shift or mouse button.
843 // Mozilla: If we have non-empty selection we will fire a new event for each
844 // keypress (or mouseup) if the selection changed. Mozilla will also
845 // create the event each time select all is called, even if
846 // everything was previously selected, becase technically select all
847 // will first collapse and then extend. Mozilla will never create an
848 // event if the selection collapses to nothing.
849 bool collapsed = aSelection.IsCollapsed();
850 if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
851 nsISelectionListener::KEYPRESS_REASON |
852 nsISelectionListener::SELECTALL_REASON))) {
853 if (nsCOMPtr<nsIContent> content = mFrame->GetContent()) {
854 if (nsCOMPtr<Document> doc = content->GetComposedDoc()) {
855 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
856 nsEventStatus status = nsEventStatus_eIgnore;
857 WidgetEvent event(true, eFormSelect);
858
859 presShell->HandleEventWithTarget(&event, mFrame, content, &status);
860 }
861 }
862 }
863 }
864
865 // if the collapsed state did not change, don't fire notifications
866 if (collapsed == mSelectionWasCollapsed) {
867 return;
868 }
869
870 mSelectionWasCollapsed = collapsed;
871
872 if (!weakFrame.IsAlive() || !mFrame ||
873 !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
874 return;
875 }
876
877 UpdateTextInputCommands(u"select"_ns, &aSelection, aReason);
878 }
879
880 MOZ_CAN_RUN_SCRIPT
DoCommandCallback(Command aCommand,void * aData)881 static void DoCommandCallback(Command aCommand, void* aData) {
882 nsTextControlFrame* frame = static_cast<nsTextControlFrame*>(aData);
883 nsIContent* content = frame->GetContent();
884
885 nsCOMPtr<nsIControllers> controllers;
886 HTMLInputElement* input = HTMLInputElement::FromNode(content);
887 if (input) {
888 input->GetControllers(getter_AddRefs(controllers));
889 } else {
890 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(content);
891
892 if (textArea) {
893 textArea->GetControllers(getter_AddRefs(controllers));
894 }
895 }
896
897 if (!controllers) {
898 NS_WARNING("Could not get controllers");
899 return;
900 }
901
902 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
903
904 nsCOMPtr<nsIController> controller;
905 controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
906 if (!controller) {
907 return;
908 }
909
910 bool commandEnabled;
911 if (NS_WARN_IF(NS_FAILED(
912 controller->IsCommandEnabled(commandStr, &commandEnabled)))) {
913 return;
914 }
915 if (commandEnabled) {
916 controller->DoCommand(commandStr);
917 }
918 }
919
920 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
HandleEvent(Event * aEvent)921 TextInputListener::HandleEvent(Event* aEvent) {
922 if (aEvent->DefaultPrevented()) {
923 return NS_OK;
924 }
925
926 if (!aEvent->IsTrusted()) {
927 return NS_OK;
928 }
929
930 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
931 if (!keyEvent) {
932 return NS_ERROR_UNEXPECTED;
933 }
934
935 WidgetKeyboardEvent* widgetKeyEvent =
936 aEvent->WidgetEventPtr()->AsKeyboardEvent();
937 if (!widgetKeyEvent) {
938 return NS_ERROR_UNEXPECTED;
939 }
940
941 {
942 auto* input = HTMLInputElement::FromNode(mTxtCtrlElement);
943 if (input && input->StepsInputValue(*widgetKeyEvent)) {
944 // As an special case, don't handle key events that would step the value
945 // of our <input type=number>.
946 return NS_OK;
947 }
948 }
949
950 KeyEventHandler* keyHandlers = ShortcutKeys::GetHandlers(
951 mTxtCtrlElement->IsTextArea() ? HandlerType::eTextArea
952 : HandlerType::eInput);
953
954 RefPtr<nsAtom> eventTypeAtom =
955 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent);
956 for (KeyEventHandler* handler = keyHandlers; handler;
957 handler = handler->GetNextHandler()) {
958 if (!handler->EventTypeEquals(eventTypeAtom)) {
959 continue;
960 }
961
962 if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) {
963 continue;
964 }
965
966 // XXX Do we execute only one handler even if the handler neither stops
967 // propagation nor prevents default of the event?
968 RefPtr<TextControlElement> textControlElement(mTxtCtrlElement);
969 nsresult rv = handler->ExecuteHandler(textControlElement, aEvent);
970 if (NS_SUCCEEDED(rv)) {
971 return rv;
972 }
973 }
974
975 if (widgetKeyEvent->mMessage != eKeyPress) {
976 return NS_OK;
977 }
978
979 nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
980 mTxtCtrlElement->IsTextArea()
981 ? nsIWidget::NativeKeyBindingsForMultiLineEditor
982 : nsIWidget::NativeKeyBindingsForSingleLineEditor;
983
984 nsIWidget* widget = widgetKeyEvent->mWidget;
985 // If the event is created by chrome script, the widget is nullptr.
986 if (!widget) {
987 widget = mFrame->GetNearestWidget();
988 if (NS_WARN_IF(!widget)) {
989 return NS_OK;
990 }
991 }
992
993 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
994 // If the event is created by chrome script, it is nullptr but we need to
995 // execute native key bindings. Therefore, we need to set widget to
996 // WidgetEvent::mWidget temporarily.
997 AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(widgetKeyEvent->mWidget);
998 widgetKeyEvent->mWidget = widget;
999 if (widgetKeyEvent->ExecuteEditCommands(nativeKeyBindingsType,
1000 DoCommandCallback, mFrame)) {
1001 aEvent->PreventDefault();
1002 }
1003 return NS_OK;
1004 }
1005
OnEditActionHandled(TextEditor & aTextEditor)1006 nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
1007 if (mFrame) {
1008 // XXX Do we still need this or can we just remove the mFrame and
1009 // frame.IsAlive() conditions below?
1010 AutoWeakFrame weakFrame = mFrame;
1011
1012 // Update the undo / redo menus
1013 //
1014 size_t numUndoItems = aTextEditor.NumberOfUndoItems();
1015 size_t numRedoItems = aTextEditor.NumberOfRedoItems();
1016 if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
1017 (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
1018 // Modify the menu if undo or redo items are different
1019 UpdateTextInputCommands(u"undo"_ns);
1020
1021 mHadUndoItems = numUndoItems != 0;
1022 mHadRedoItems = numRedoItems != 0;
1023 }
1024
1025 if (weakFrame.IsAlive()) {
1026 HandleValueChanged();
1027 }
1028 }
1029
1030 return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
1031 }
1032
HandleValueChanged()1033 void TextInputListener::HandleValueChanged() {
1034 // Make sure we know we were changed (do NOT set this to false if there are
1035 // no undo items; JS could change the value and we'd still need to save it)
1036 if (mSetValueChanged) {
1037 mTxtCtrlElement->SetValueChanged(true);
1038 }
1039
1040 if (!mSettingValue) {
1041 mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction);
1042 }
1043 }
1044
UpdateTextInputCommands(const nsAString & aCommandsToUpdate,Selection * aSelection,int16_t aReason)1045 nsresult TextInputListener::UpdateTextInputCommands(
1046 const nsAString& aCommandsToUpdate, Selection* aSelection,
1047 int16_t aReason) {
1048 nsIContent* content = mFrame->GetContent();
1049 if (NS_WARN_IF(!content)) {
1050 return NS_ERROR_FAILURE;
1051 }
1052 nsCOMPtr<Document> doc = content->GetComposedDoc();
1053 if (NS_WARN_IF(!doc)) {
1054 return NS_ERROR_FAILURE;
1055 }
1056 nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1057 if (NS_WARN_IF(!domWindow)) {
1058 return NS_ERROR_FAILURE;
1059 }
1060 domWindow->UpdateCommands(aCommandsToUpdate, aSelection, aReason);
1061 return NS_OK;
1062 }
1063
1064 /*****************************************************************************
1065 * mozilla::AutoTextControlHandlingState
1066 *
1067 * This class is temporarily created in the stack and can manage nested
1068 * handling state of TextControlState. While this instance exists, lifetime of
1069 * TextControlState which created the instance is guaranteed. In other words,
1070 * you can use this class as "kungFuDeathGrip" for TextControlState.
1071 *****************************************************************************/
1072
1073 enum class TextControlAction {
1074 CacheForReuse,
1075 CommitComposition,
1076 Destructor,
1077 PrepareEditor,
1078 SetRangeText,
1079 SetSelectionRange,
1080 SetValue,
1081 UnbindFromFrame,
1082 Unlink,
1083 };
1084
1085 class MOZ_STACK_CLASS AutoTextControlHandlingState {
1086 public:
1087 AutoTextControlHandlingState() = delete;
1088 explicit AutoTextControlHandlingState(const AutoTextControlHandlingState&) =
1089 delete;
1090 AutoTextControlHandlingState(AutoTextControlHandlingState&&) = delete;
1091 void operator=(AutoTextControlHandlingState&) = delete;
1092 void operator=(const AutoTextControlHandlingState&) = delete;
1093
1094 /**
1095 * Generic constructor. If TextControlAction does not require additional
1096 * data, must use this constructor.
1097 */
AutoTextControlHandlingState(TextControlState & aTextControlState,TextControlAction aTextControlAction)1098 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1099 TextControlState& aTextControlState, TextControlAction aTextControlAction)
1100 : mParent(aTextControlState.mHandlingState),
1101 mTextControlState(aTextControlState),
1102 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1103 mTextInputListener(aTextControlState.mTextListener),
1104 mTextControlAction(aTextControlAction) {
1105 MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue,
1106 "Use specific constructor");
1107 mTextControlState.mHandlingState = this;
1108 if (Is(TextControlAction::CommitComposition)) {
1109 MOZ_ASSERT(mParent);
1110 MOZ_ASSERT(mParent->Is(TextControlAction::SetValue));
1111 // If we're trying to commit composition before handling SetValue,
1112 // the parent old values will be outdated so that we need to clear
1113 // them.
1114 mParent->InvalidateOldValue();
1115 }
1116 }
1117
1118 /**
1119 * TextControlAction::SetValue specific constructor. Current setting value
1120 * must be specified and the creator should check whether we succeeded to
1121 * allocate memory for line breaker conversion.
1122 */
AutoTextControlHandlingState(TextControlState & aTextControlState,TextControlAction aTextControlAction,const nsAString & aSettingValue,const nsAString * aOldValue,const ValueSetterOptions & aOptions,ErrorResult & aRv)1123 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1124 TextControlState& aTextControlState, TextControlAction aTextControlAction,
1125 const nsAString& aSettingValue, const nsAString* aOldValue,
1126 const ValueSetterOptions& aOptions, ErrorResult& aRv)
1127 : mParent(aTextControlState.mHandlingState),
1128 mTextControlState(aTextControlState),
1129 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1130 mTextInputListener(aTextControlState.mTextListener),
1131 mSettingValue(aSettingValue),
1132 mOldValue(aOldValue),
1133 mValueSetterOptions(aOptions),
1134 mTextControlAction(aTextControlAction) {
1135 MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue,
1136 "Use generic constructor");
1137 mTextControlState.mHandlingState = this;
1138 if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue, fallible)) {
1139 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1140 return;
1141 }
1142 // Update all setting value's new value because older value shouldn't
1143 // overwrite newer value.
1144 if (mParent) {
1145 // If SetValue is nested, parents cannot trust their old value anymore.
1146 // So, we need to clear them.
1147 mParent->UpdateSettingValueAndInvalidateOldValue(mSettingValue);
1148 }
1149 }
1150
~AutoTextControlHandlingState()1151 MOZ_CAN_RUN_SCRIPT ~AutoTextControlHandlingState() {
1152 mTextControlState.mHandlingState = mParent;
1153 if (!mParent && mTextControlStateDestroyed) {
1154 mTextControlState.DeleteOrCacheForReuse();
1155 }
1156 if (!mTextControlStateDestroyed && mPreareEditorLater) {
1157 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
1158 mTextControlState.PrepareEditor();
1159 }
1160 }
1161
OnDestroyTextControlState()1162 void OnDestroyTextControlState() {
1163 if (IsHandling(TextControlAction::Destructor) ||
1164 IsHandling(TextControlAction::CacheForReuse)) {
1165 // Do nothing since mTextContrlState.DeleteOrCacheForReuse() has
1166 // already been called.
1167 return;
1168 }
1169 mTextControlStateDestroyed = true;
1170 if (mParent) {
1171 mParent->OnDestroyTextControlState();
1172 }
1173 }
1174
PrepareEditorLater()1175 void PrepareEditorLater() {
1176 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1177 MOZ_ASSERT(!IsHandling(TextControlAction::PrepareEditor));
1178 // Look for the top most SetValue.
1179 AutoTextControlHandlingState* settingValue = nullptr;
1180 for (AutoTextControlHandlingState* handlingSomething = this;
1181 handlingSomething; handlingSomething = handlingSomething->mParent) {
1182 if (handlingSomething->Is(TextControlAction::SetValue)) {
1183 settingValue = handlingSomething;
1184 }
1185 }
1186 settingValue->mPreareEditorLater = true;
1187 }
1188
1189 /**
1190 * WillSetValueWithTextEditor() is called when TextControlState sets
1191 * value with its mTextEditor.
1192 */
WillSetValueWithTextEditor()1193 void WillSetValueWithTextEditor() {
1194 MOZ_ASSERT(Is(TextControlAction::SetValue));
1195 MOZ_ASSERT(mTextControlState.mBoundFrame);
1196 mTextControlFrame = mTextControlState.mBoundFrame;
1197 // If we'reemulating user input, we don't need to manage mTextInputListener
1198 // by ourselves since everything should be handled by TextEditor as normal
1199 // user input.
1200 if (mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1201 return;
1202 }
1203 // Otherwise, if we're setting the value programatically, we need to manage
1204 // mTextInputListener by ourselves since TextEditor users special path
1205 // for the performance.
1206 mTextInputListener->SettingValue(true);
1207 mTextInputListener->SetValueChanged(
1208 mValueSetterOptions.contains(ValueSetterOption::SetValueChanged));
1209 mEditActionHandled = false;
1210 // Even if falling back to `TextControlState::SetValueWithoutTextEditor()`
1211 // due to editor destruction, it shouldn't dispatch "beforeinput" event
1212 // anymore. Therefore, we should mark that we've already dispatched
1213 // "beforeinput" event.
1214 WillDispatchBeforeInputEvent();
1215 }
1216
1217 /**
1218 * WillDispatchBeforeInputEvent() is called immediately before dispatching
1219 * "beforeinput" event in `TextControlState`.
1220 */
WillDispatchBeforeInputEvent()1221 void WillDispatchBeforeInputEvent() {
1222 mBeforeInputEventHasBeenDispatched = true;
1223 }
1224
1225 /**
1226 * OnEditActionHandled() is called when the TextEditor handles something
1227 * and immediately before dispatching "input" event.
1228 */
OnEditActionHandled()1229 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled() {
1230 MOZ_ASSERT(!mEditActionHandled);
1231 mEditActionHandled = true;
1232 if (!Is(TextControlAction::SetValue)) {
1233 return NS_OK;
1234 }
1235 if (!mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1236 mTextInputListener->SetValueChanged(true);
1237 mTextInputListener->SettingValue(
1238 mParent && mParent->IsHandling(TextControlAction::SetValue));
1239 }
1240 if (!IsOriginalTextControlFrameAlive()) {
1241 return SetValueWithoutTextEditorAgain() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
1242 }
1243 // The new value never includes line breaks caused by hard-wrap.
1244 // So, mCachedValue can always cache the new value.
1245 nsITextControlFrame* textControlFrame =
1246 do_QueryFrame(mTextControlFrame.GetFrame());
1247 return static_cast<nsTextControlFrame*>(textControlFrame)
1248 ->CacheValue(mSettingValue, fallible)
1249 ? NS_OK
1250 : NS_ERROR_OUT_OF_MEMORY;
1251 }
1252
1253 /**
1254 * SetValueWithoutTextEditorAgain() should be called if the frame for
1255 * mTextControlState was destroyed during setting value.
1256 */
SetValueWithoutTextEditorAgain()1257 [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditorAgain() {
1258 MOZ_ASSERT(!IsOriginalTextControlFrameAlive());
1259 // If the frame was destroyed because of a flush somewhere inside
1260 // TextEditor, mBoundFrame here will be nullptr. But it's also
1261 // possible for the frame to go away because of another reason (such
1262 // as deleting the existing selection -- see bug 574558), in which
1263 // case we don't need to reset the value here.
1264 if (mTextControlState.mBoundFrame) {
1265 return true;
1266 }
1267 // XXX It's odd to drop flags except
1268 // ValueSetterOption::SetValueChanged.
1269 // Probably, this intended to drop ValueSetterOption::BySetUserInputAPI
1270 // and ValueSetterOption::ByContentAPI, but other flags are added later.
1271 ErrorResult error;
1272 AutoTextControlHandlingState handlingSetValueWithoutEditor(
1273 mTextControlState, TextControlAction::SetValue, mSettingValue,
1274 mOldValue, mValueSetterOptions & ValueSetterOption::SetValueChanged,
1275 error);
1276 if (error.Failed()) {
1277 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
1278 error.SuppressException();
1279 return false;
1280 }
1281 return mTextControlState.SetValueWithoutTextEditor(
1282 handlingSetValueWithoutEditor);
1283 }
1284
IsTextControlStateDestroyed() const1285 bool IsTextControlStateDestroyed() const {
1286 return mTextControlStateDestroyed;
1287 }
IsOriginalTextControlFrameAlive() const1288 bool IsOriginalTextControlFrameAlive() const {
1289 return const_cast<AutoTextControlHandlingState*>(this)
1290 ->mTextControlFrame.IsAlive();
1291 }
HasEditActionHandled() const1292 bool HasEditActionHandled() const { return mEditActionHandled; }
HasBeforeInputEventDispatched() const1293 bool HasBeforeInputEventDispatched() const {
1294 return mBeforeInputEventHasBeenDispatched;
1295 }
Is(TextControlAction aTextControlAction) const1296 bool Is(TextControlAction aTextControlAction) const {
1297 return mTextControlAction == aTextControlAction;
1298 }
IsHandling(TextControlAction aTextControlAction) const1299 bool IsHandling(TextControlAction aTextControlAction) const {
1300 if (mTextControlAction == aTextControlAction) {
1301 return true;
1302 }
1303 return mParent ? mParent->IsHandling(aTextControlAction) : false;
1304 }
GetTextControlElement() const1305 TextControlElement* GetTextControlElement() const { return mTextCtrlElement; }
GetTextInputListener() const1306 TextInputListener* GetTextInputListener() const { return mTextInputListener; }
ValueSetterOptionsRef() const1307 const ValueSetterOptions& ValueSetterOptionsRef() const {
1308 MOZ_ASSERT(Is(TextControlAction::SetValue));
1309 return mValueSetterOptions;
1310 }
GetOldValue() const1311 const nsAString* GetOldValue() const {
1312 MOZ_ASSERT(Is(TextControlAction::SetValue));
1313 return mOldValue;
1314 }
GetSettingValue() const1315 const nsString& GetSettingValue() const {
1316 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1317 if (mTextControlAction == TextControlAction::SetValue) {
1318 return mSettingValue;
1319 }
1320 return mParent->GetSettingValue();
1321 }
1322
1323 private:
UpdateSettingValueAndInvalidateOldValue(const nsString & aSettingValue)1324 void UpdateSettingValueAndInvalidateOldValue(const nsString& aSettingValue) {
1325 if (mTextControlAction == TextControlAction::SetValue) {
1326 mSettingValue = aSettingValue;
1327 }
1328 mOldValue = nullptr;
1329 if (mParent) {
1330 mParent->UpdateSettingValueAndInvalidateOldValue(aSettingValue);
1331 }
1332 }
InvalidateOldValue()1333 void InvalidateOldValue() {
1334 mOldValue = nullptr;
1335 if (mParent) {
1336 mParent->InvalidateOldValue();
1337 }
1338 }
1339
1340 AutoTextControlHandlingState* const mParent;
1341 TextControlState& mTextControlState;
1342 // mTextControlFrame should be set immediately before calling methods
1343 // which may destroy the frame. Then, you can check whether the frame
1344 // was destroyed/replaced.
1345 AutoWeakFrame mTextControlFrame;
1346 // mTextCtrlElement grabs TextControlState::mTextCtrlElement since
1347 // if the text control element releases mTextControlState, only this
1348 // can guarantee the instance of the text control element.
1349 RefPtr<TextControlElement> const mTextCtrlElement;
1350 // mTextInputListener grabs TextControlState::mTextListener because if
1351 // TextControlState is unbind from the frame, it's released.
1352 RefPtr<TextInputListener> const mTextInputListener;
1353 nsString mSettingValue;
1354 const nsAString* mOldValue = nullptr;
1355 ValueSetterOptions mValueSetterOptions;
1356 TextControlAction const mTextControlAction;
1357 bool mTextControlStateDestroyed = false;
1358 bool mEditActionHandled = false;
1359 bool mPreareEditorLater = false;
1360 bool mBeforeInputEventHasBeenDispatched = false;
1361 };
1362
1363 /*****************************************************************************
1364 * mozilla::TextControlState
1365 *****************************************************************************/
1366
1367 AutoTArray<TextControlState*, TextControlState::kMaxCountOfCacheToReuse>*
1368 TextControlState::sReleasedInstances = nullptr;
1369 bool TextControlState::sHasShutDown = false;
1370
TextControlState(TextControlElement * aOwningElement)1371 TextControlState::TextControlState(TextControlElement* aOwningElement)
1372 : mTextCtrlElement(aOwningElement),
1373 mBoundFrame(nullptr),
1374 mEverInited(false),
1375 mEditorInitialized(false),
1376 mValueTransferInProgress(false),
1377 mSelectionCached(true)
1378 // When adding more member variable initializations here, add the same
1379 // also to ::Construct.
1380 {
1381 MOZ_COUNT_CTOR(TextControlState);
1382 static_assert(sizeof(*this) <= 128,
1383 "Please keep small TextControlState as far as possible");
1384 }
1385
Construct(TextControlElement * aOwningElement)1386 TextControlState* TextControlState::Construct(
1387 TextControlElement* aOwningElement) {
1388 if (sReleasedInstances && !sReleasedInstances->IsEmpty()) {
1389 TextControlState* state = sReleasedInstances->PopLastElement();
1390 state->mTextCtrlElement = aOwningElement;
1391 state->mBoundFrame = nullptr;
1392 state->mSelectionProperties = SelectionProperties();
1393 state->mEverInited = false;
1394 state->mEditorInitialized = false;
1395 state->mValueTransferInProgress = false;
1396 state->mSelectionCached = true;
1397 // When adding more member variable initializations here, add the same
1398 // also to the constructor.
1399 return state;
1400 }
1401
1402 return new TextControlState(aOwningElement);
1403 }
1404
~TextControlState()1405 TextControlState::~TextControlState() {
1406 MOZ_ASSERT(!mHandlingState);
1407 MOZ_COUNT_DTOR(TextControlState);
1408 AutoTextControlHandlingState handlingDesctructor(
1409 *this, TextControlAction::Destructor);
1410 Clear();
1411 }
1412
Shutdown()1413 void TextControlState::Shutdown() {
1414 sHasShutDown = true;
1415 if (sReleasedInstances) {
1416 for (TextControlState* textControlState : *sReleasedInstances) {
1417 textControlState->DeleteOrCacheForReuse();
1418 }
1419 delete sReleasedInstances;
1420 }
1421 }
1422
Destroy()1423 void TextControlState::Destroy() {
1424 // If we're handling something, we should be deleted later.
1425 if (mHandlingState) {
1426 mHandlingState->OnDestroyTextControlState();
1427 return;
1428 }
1429 DeleteOrCacheForReuse();
1430 // Note that this instance may have already been deleted here. Don't touch
1431 // any members.
1432 }
1433
DeleteOrCacheForReuse()1434 void TextControlState::DeleteOrCacheForReuse() {
1435 MOZ_ASSERT(!IsBusy());
1436
1437 // If we can cache this instance, we should do it instead of deleting it.
1438 if (!sHasShutDown && (!sReleasedInstances || sReleasedInstances->Length() <
1439 kMaxCountOfCacheToReuse)) {
1440 AutoTextControlHandlingState handlingCacheForReuse(
1441 *this, TextControlAction::CacheForReuse);
1442
1443 // Prepare for reuse, unlink and release any refcountable objects.
1444 UnlinkInternal();
1445 mValue.reset();
1446 mTextCtrlElement = nullptr;
1447
1448 // Put this instance to the cache. Note that now, the array may be full,
1449 // but it's not problem to cache more instances than kMaxCountOfCacheToReuse
1450 // because it just requires reallocation cost of the array buffer.
1451 if (!sReleasedInstances) {
1452 sReleasedInstances =
1453 new AutoTArray<TextControlState*, kMaxCountOfCacheToReuse>;
1454 }
1455 sReleasedInstances->AppendElement(this);
1456
1457 return;
1458 }
1459 delete this;
1460 }
1461
OnEditActionHandled()1462 nsresult TextControlState::OnEditActionHandled() {
1463 return mHandlingState ? mHandlingState->OnEditActionHandled() : NS_OK;
1464 }
1465
GetRootNode()1466 Element* TextControlState::GetRootNode() {
1467 return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1468 }
1469
GetPreviewNode()1470 Element* TextControlState::GetPreviewNode() {
1471 return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1472 }
1473
Clear()1474 void TextControlState::Clear() {
1475 MOZ_ASSERT(mHandlingState);
1476 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Destructor) ||
1477 mHandlingState->Is(TextControlAction::CacheForReuse) ||
1478 mHandlingState->Is(TextControlAction::Unlink));
1479 if (mTextEditor) {
1480 mTextEditor->SetTextInputListener(nullptr);
1481 }
1482
1483 if (mBoundFrame) {
1484 // Oops, we still have a frame!
1485 // This should happen when the type of a text input control is being changed
1486 // to something which is not a text control. In this case, we should
1487 // pretend that a frame is being destroyed, and clean up after ourselves
1488 // properly.
1489 UnbindFromFrame(mBoundFrame);
1490 mTextEditor = nullptr;
1491 } else {
1492 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1493 // for us.
1494 DestroyEditor();
1495 }
1496 mTextListener = nullptr;
1497 }
1498
Unlink()1499 void TextControlState::Unlink() {
1500 AutoTextControlHandlingState handlingUnlink(*this, TextControlAction::Unlink);
1501 UnlinkInternal();
1502 }
1503
UnlinkInternal()1504 void TextControlState::UnlinkInternal() {
1505 MOZ_ASSERT(mHandlingState);
1506 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Unlink) ||
1507 mHandlingState->Is(TextControlAction::CacheForReuse));
1508 TextControlState* tmp = this;
1509 tmp->Clear();
1510 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
1511 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
1512 }
1513
Traverse(nsCycleCollectionTraversalCallback & cb)1514 void TextControlState::Traverse(nsCycleCollectionTraversalCallback& cb) {
1515 TextControlState* tmp = this;
1516 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
1517 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
1518 }
1519
GetConstFrameSelection()1520 nsFrameSelection* TextControlState::GetConstFrameSelection() {
1521 return mSelCon ? mSelCon->GetConstFrameSelection() : nullptr;
1522 }
1523
GetTextEditor()1524 TextEditor* TextControlState::GetTextEditor() {
1525 // Note that if the instance is destroyed in PrepareEditor(), it returns
1526 // NS_ERROR_NOT_INITIALIZED so that we don't need to create kungFuDeathGrip
1527 // in this hot path.
1528 if (!mTextEditor && NS_WARN_IF(NS_FAILED(PrepareEditor()))) {
1529 return nullptr;
1530 }
1531 return mTextEditor;
1532 }
1533
GetTextEditorWithoutCreation()1534 TextEditor* TextControlState::GetTextEditorWithoutCreation() {
1535 return mTextEditor;
1536 }
1537
GetSelectionController() const1538 nsISelectionController* TextControlState::GetSelectionController() const {
1539 return mSelCon;
1540 }
1541
1542 // Helper class, used below in BindToFrame().
1543 class PrepareEditorEvent : public Runnable {
1544 public:
PrepareEditorEvent(TextControlState & aState,nsIContent * aOwnerContent,const nsAString & aCurrentValue)1545 PrepareEditorEvent(TextControlState& aState, nsIContent* aOwnerContent,
1546 const nsAString& aCurrentValue)
1547 : Runnable("PrepareEditorEvent"),
1548 mState(&aState),
1549 mOwnerContent(aOwnerContent),
1550 mCurrentValue(aCurrentValue) {
1551 aState.mValueTransferInProgress = true;
1552 }
1553
Run()1554 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
1555 if (NS_WARN_IF(!mState)) {
1556 return NS_ERROR_NULL_POINTER;
1557 }
1558
1559 // Transfer the saved value to the editor if we have one
1560 const nsAString* value = nullptr;
1561 if (!mCurrentValue.IsEmpty()) {
1562 value = &mCurrentValue;
1563 }
1564
1565 nsAutoScriptBlocker scriptBlocker;
1566
1567 mState->PrepareEditor(value);
1568
1569 mState->mValueTransferInProgress = false;
1570
1571 return NS_OK;
1572 }
1573
1574 private:
1575 WeakPtr<TextControlState> mState;
1576 nsCOMPtr<nsIContent> mOwnerContent; // strong reference
1577 nsAutoString mCurrentValue;
1578 };
1579
BindToFrame(nsTextControlFrame * aFrame)1580 nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
1581 MOZ_ASSERT(
1582 !nsContentUtils::IsSafeToRunScript(),
1583 "TextControlState::BindToFrame() has to be called with script blocker");
1584 NS_ASSERTION(aFrame, "The frame to bind to should be valid");
1585 if (!aFrame) {
1586 return NS_ERROR_INVALID_ARG;
1587 }
1588
1589 NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
1590 if (mBoundFrame) {
1591 return NS_ERROR_FAILURE;
1592 }
1593
1594 // If we'll need to transfer our current value to the editor, save it before
1595 // binding to the frame.
1596 nsAutoString currentValue;
1597 if (mTextEditor) {
1598 GetValue(currentValue, true);
1599 }
1600
1601 mBoundFrame = aFrame;
1602
1603 Element* rootNode = aFrame->GetRootNode();
1604 MOZ_ASSERT(rootNode);
1605
1606 PresShell* presShell = aFrame->PresContext()->GetPresShell();
1607 MOZ_ASSERT(presShell);
1608
1609 // Create a SelectionController
1610 mSelCon = new TextInputSelectionController(presShell, rootNode);
1611 MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
1612 mTextListener = new TextInputListener(mTextCtrlElement);
1613
1614 mTextListener->SetFrame(mBoundFrame);
1615
1616 // Editor will override this as needed from InitializeSelection.
1617 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1618
1619 // Get the caret and make it a selection listener.
1620 // FYI: It's safe to use raw pointer for calling
1621 // Selection::AddSelectionListner() because it only appends the listener
1622 // to its internal array.
1623 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
1624 if (selection) {
1625 RefPtr<nsCaret> caret = presShell->GetCaret();
1626 if (caret) {
1627 selection->AddSelectionListener(caret);
1628 }
1629 mTextListener->StartToListenToSelectionChange();
1630 }
1631
1632 // If an editor exists from before, prepare it for usage
1633 if (mTextEditor) {
1634 if (NS_WARN_IF(!mTextCtrlElement)) {
1635 return NS_ERROR_FAILURE;
1636 }
1637
1638 // Set the correct direction on the newly created root node
1639 if (mTextEditor->IsRightToLeft()) {
1640 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, false);
1641 } else if (mTextEditor->IsLeftToRight()) {
1642 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"ltr"_ns, false);
1643 } else {
1644 // otherwise, inherit the content node's direction
1645 }
1646
1647 nsContentUtils::AddScriptRunner(
1648 new PrepareEditorEvent(*this, mTextCtrlElement, currentValue));
1649 }
1650
1651 return NS_OK;
1652 }
1653
1654 struct MOZ_STACK_CLASS PreDestroyer {
Initmozilla::PreDestroyer1655 void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
~PreDestroyermozilla::PreDestroyer1656 ~PreDestroyer() {
1657 if (mTextEditor) {
1658 // In this case, we don't need to restore the unmasked range of password
1659 // editor.
1660 UniquePtr<PasswordMaskData> passwordMaskData = mTextEditor->PreDestroy();
1661 }
1662 }
Swapmozilla::PreDestroyer1663 void Swap(RefPtr<TextEditor>& aTextEditor) {
1664 return mTextEditor.swap(aTextEditor);
1665 }
1666
1667 private:
1668 RefPtr<TextEditor> mTextEditor;
1669 };
1670
PrepareEditor(const nsAString * aValue)1671 nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
1672 if (!mBoundFrame) {
1673 // Cannot create an editor without a bound frame.
1674 // Don't return a failure code, because js callers can't handle that.
1675 return NS_OK;
1676 }
1677
1678 if (mEditorInitialized) {
1679 // Do not initialize the editor multiple times.
1680 return NS_OK;
1681 }
1682
1683 AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
1684
1685 if (mHandlingState) {
1686 // Don't attempt to initialize recursively!
1687 if (mHandlingState->IsHandling(TextControlAction::PrepareEditor)) {
1688 return NS_ERROR_NOT_INITIALIZED;
1689 }
1690 // Reschedule creating editor later if we're setting value.
1691 if (mHandlingState->IsHandling(TextControlAction::SetValue)) {
1692 mHandlingState->PrepareEditorLater();
1693 return NS_ERROR_NOT_INITIALIZED;
1694 }
1695 }
1696
1697 MOZ_ASSERT(mTextCtrlElement);
1698
1699 AutoTextControlHandlingState preparingEditor(
1700 *this, TextControlAction::PrepareEditor);
1701
1702 // Note that we don't check mTextEditor here, because we might already have
1703 // one around, in which case we don't create a new one, and we'll just tie
1704 // the required machinery to it.
1705
1706 nsPresContext* presContext = mBoundFrame->PresContext();
1707 PresShell* presShell = presContext->GetPresShell();
1708
1709 // Setup the editor flags
1710 uint32_t editorFlags = nsIEditor::eEditorPlaintextMask;
1711 if (IsSingleLineTextControl()) {
1712 editorFlags |= nsIEditor::eEditorSingleLineMask;
1713 }
1714 if (IsPasswordTextControl()) {
1715 editorFlags |= nsIEditor::eEditorPasswordMask;
1716 }
1717
1718 // Spell check is diabled at creation time. It is enabled once
1719 // the editor comes into focus.
1720 editorFlags |= nsIEditor::eEditorSkipSpellCheck;
1721
1722 bool shouldInitializeEditor = false;
1723 RefPtr<TextEditor> newTextEditor; // the editor that we might create
1724 PreDestroyer preDestroyer;
1725 if (!mTextEditor) {
1726 shouldInitializeEditor = true;
1727
1728 // Create an editor
1729 newTextEditor = new TextEditor();
1730 preDestroyer.Init(newTextEditor);
1731
1732 // Make sure we clear out the non-breaking space before we initialize the
1733 // editor
1734 nsresult rv = mBoundFrame->UpdateValueDisplay(true, true);
1735 if (NS_FAILED(rv)) {
1736 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1737 return rv;
1738 }
1739 } else {
1740 if (aValue || !mEditorInitialized) {
1741 // Set the correct value in the root node
1742 nsresult rv =
1743 mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
1744 if (NS_FAILED(rv)) {
1745 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1746 return rv;
1747 }
1748 }
1749
1750 newTextEditor = mTextEditor; // just pretend that we have a new editor!
1751
1752 // Don't lose application flags in the process.
1753 if (newTextEditor->IsMailEditor()) {
1754 editorFlags |= nsIEditor::eEditorMailMask;
1755 }
1756 }
1757
1758 // Get the current value of the textfield from the content.
1759 // Note that if we've created a new editor, mTextEditor is null at this stage,
1760 // so we will get the real value from the content.
1761 nsAutoString defaultValue;
1762 if (aValue) {
1763 defaultValue = *aValue;
1764 } else {
1765 GetValue(defaultValue, true);
1766 }
1767
1768 if (!mEditorInitialized) {
1769 // Now initialize the editor.
1770 //
1771 // NOTE: Conversion of '\n' to <BR> happens inside the
1772 // editor's Init() call.
1773
1774 // Get the DOM document
1775 nsCOMPtr<Document> doc = presShell->GetDocument();
1776 if (NS_WARN_IF(!doc)) {
1777 return NS_ERROR_FAILURE;
1778 }
1779
1780 // What follows is a bit of a hack. The editor uses the public DOM APIs
1781 // for its content manipulations, and it causes it to fail some security
1782 // checks deep inside when initializing. So we explictly make it clear that
1783 // we're native code.
1784 // Note that any script that's directly trying to access our value
1785 // has to be going through some scriptable object to do that and that
1786 // already does the relevant security checks.
1787 AutoNoJSAPI nojsapi;
1788
1789 RefPtr<Element> anonymousDivElement = GetRootNode();
1790 if (NS_WARN_IF(!anonymousDivElement) || NS_WARN_IF(!mSelCon)) {
1791 return NS_ERROR_FAILURE;
1792 }
1793 OwningNonNull<TextInputSelectionController> selectionController(*mSelCon);
1794 UniquePtr<PasswordMaskData> passwordMaskData;
1795 if (editorFlags & nsIEditor::eEditorPasswordMask) {
1796 if (mPasswordMaskData) {
1797 passwordMaskData = std::move(mPasswordMaskData);
1798 } else {
1799 passwordMaskData = MakeUnique<PasswordMaskData>();
1800 }
1801 } else {
1802 mPasswordMaskData = nullptr;
1803 }
1804 nsresult rv =
1805 newTextEditor->Init(*doc, *anonymousDivElement, selectionController,
1806 editorFlags, std::move(passwordMaskData));
1807 if (NS_FAILED(rv)) {
1808 NS_WARNING("TextEditor::Init() failed");
1809 return rv;
1810 }
1811 }
1812
1813 // Initialize the controller for the editor
1814
1815 nsresult rv = NS_OK;
1816 if (!SuppressEventHandlers(presContext)) {
1817 nsCOMPtr<nsIControllers> controllers;
1818 if (HTMLInputElement* inputElement =
1819 HTMLInputElement::FromNodeOrNull(mTextCtrlElement)) {
1820 nsresult rv = inputElement->GetControllers(getter_AddRefs(controllers));
1821 if (NS_WARN_IF(NS_FAILED(rv))) {
1822 return rv;
1823 }
1824 } else {
1825 HTMLTextAreaElement* textAreaElement =
1826 HTMLTextAreaElement::FromNodeOrNull(mTextCtrlElement);
1827 if (!textAreaElement) {
1828 return NS_ERROR_FAILURE;
1829 }
1830
1831 nsresult rv =
1832 textAreaElement->GetControllers(getter_AddRefs(controllers));
1833 if (NS_WARN_IF(NS_FAILED(rv))) {
1834 return rv;
1835 }
1836 }
1837
1838 if (controllers) {
1839 // XXX Oddly, nsresult value is overwritten in the following loop, and
1840 // only the last result or `found` decides the value.
1841 uint32_t numControllers;
1842 bool found = false;
1843 rv = controllers->GetControllerCount(&numControllers);
1844 for (uint32_t i = 0; i < numControllers; i++) {
1845 nsCOMPtr<nsIController> controller;
1846 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1847 if (NS_SUCCEEDED(rv) && controller) {
1848 nsCOMPtr<nsIControllerContext> editController =
1849 do_QueryInterface(controller);
1850 if (editController) {
1851 editController->SetCommandContext(
1852 static_cast<nsIEditor*>(newTextEditor));
1853 found = true;
1854 }
1855 }
1856 }
1857 if (!found) {
1858 rv = NS_ERROR_FAILURE;
1859 }
1860 }
1861 }
1862
1863 // Initialize the plaintext editor
1864 if (shouldInitializeEditor) {
1865 const int32_t wrapCols = GetWrapCols();
1866 MOZ_ASSERT(wrapCols >= 0);
1867 newTextEditor->SetWrapColumn(wrapCols);
1868 }
1869
1870 // Set max text field length
1871 newTextEditor->SetMaxTextLength(mTextCtrlElement->UsedMaxLength());
1872
1873 editorFlags = newTextEditor->Flags();
1874
1875 // Check if the readonly attribute is set.
1876 //
1877 // TODO: Should probably call IsDisabled(), as it is cheaper.
1878 if (mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
1879 mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
1880 editorFlags |= nsIEditor::eEditorReadonlyMask;
1881 }
1882
1883 SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1884
1885 if (shouldInitializeEditor) {
1886 // Hold on to the newly created editor
1887 preDestroyer.Swap(mTextEditor);
1888 }
1889
1890 // If we have a default value, insert it under the div we created
1891 // above, but be sure to use the editor so that '*' characters get
1892 // displayed for password fields, etc. SetValue() will call the
1893 // editor for us.
1894
1895 if (!defaultValue.IsEmpty()) {
1896 // XXX rv may store error code which indicates there is no controller.
1897 // However, we overwrite it only in this case.
1898 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1899 if (NS_WARN_IF(NS_FAILED(rv))) {
1900 return rv;
1901 }
1902
1903 // Now call SetValue() which will make the necessary editor calls to set
1904 // the default value. Make sure to turn off undo before setting the default
1905 // value, and turn it back on afterwards. This will make sure we can't undo
1906 // past the default value.
1907 // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
1908 // undo.
1909
1910 if (NS_WARN_IF(!SetValue(defaultValue, ValueSetterOption::ByInternalAPI))) {
1911 return NS_ERROR_OUT_OF_MEMORY;
1912 }
1913
1914 // Now restore the original editor flags.
1915 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1916 if (NS_WARN_IF(NS_FAILED(rv))) {
1917 return rv;
1918 }
1919 }
1920
1921 if (IsPasswordTextControl()) {
1922 // Disable undo for <input type="password">. Note that we want to do this
1923 // at the very end of InitEditor(), so the calls to EnableUndoRedo() when
1924 // setting the default value don't screw us up. Since changing the
1925 // control type does a reframe, we don't have to worry about dynamic type
1926 // changes here.
1927 DebugOnly<bool> disabledUndoRedo = newTextEditor->DisableUndoRedo();
1928 NS_WARNING_ASSERTION(disabledUndoRedo,
1929 "Failed to disable undo/redo transaction");
1930 } else {
1931 DebugOnly<bool> enabledUndoRedo =
1932 newTextEditor->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP);
1933 NS_WARNING_ASSERTION(enabledUndoRedo,
1934 "Failed to enable undo/redo transaction");
1935 }
1936
1937 if (!mEditorInitialized) {
1938 newTextEditor->PostCreate();
1939 mEverInited = true;
1940 mEditorInitialized = true;
1941 }
1942
1943 if (mTextListener) {
1944 newTextEditor->SetTextInputListener(mTextListener);
1945 }
1946
1947 // Restore our selection after being bound to a new frame
1948 if (mSelectionCached) {
1949 if (mRestoringSelection) { // paranoia
1950 mRestoringSelection->Revoke();
1951 }
1952 mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1953 if (mRestoringSelection) {
1954 nsContentUtils::AddScriptRunner(mRestoringSelection);
1955 }
1956 }
1957
1958 // The selection cache is no longer going to be valid.
1959 //
1960 // XXXbz Shouldn't we do this at the point when we're actually about to
1961 // restore the properties or something? As things stand, if UnbindFromFrame
1962 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1963 // selection info, because we will think we don't have it cached and try to
1964 // read it from the selection controller, which will not have it yet.
1965 mSelectionCached = false;
1966
1967 return preparingEditor.IsTextControlStateDestroyed()
1968 ? NS_ERROR_NOT_INITIALIZED
1969 : rv;
1970 }
1971
FinishedRestoringSelection()1972 void TextControlState::FinishedRestoringSelection() {
1973 mRestoringSelection = nullptr;
1974 }
1975
SyncUpSelectionPropertiesBeforeDestruction()1976 void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
1977 if (mBoundFrame) {
1978 UnbindFromFrame(mBoundFrame);
1979 }
1980 }
1981
SetSelectionProperties(TextControlState::SelectionProperties & aProps)1982 void TextControlState::SetSelectionProperties(
1983 TextControlState::SelectionProperties& aProps) {
1984 if (mBoundFrame) {
1985 mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
1986 aProps.GetDirection());
1987 // The instance may have already been deleted here.
1988 } else {
1989 mSelectionProperties = aProps;
1990 }
1991 }
1992
GetSelectionRange(uint32_t * aSelectionStart,uint32_t * aSelectionEnd,ErrorResult & aRv)1993 void TextControlState::GetSelectionRange(uint32_t* aSelectionStart,
1994 uint32_t* aSelectionEnd,
1995 ErrorResult& aRv) {
1996 MOZ_ASSERT(aSelectionStart);
1997 MOZ_ASSERT(aSelectionEnd);
1998 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1999 "How can we not have a cached selection if we have no selection "
2000 "controller?");
2001
2002 // Note that we may have both IsSelectionCached() _and_
2003 // GetSelectionController() if we haven't initialized our editor yet.
2004 if (IsSelectionCached()) {
2005 const SelectionProperties& props = GetSelectionProperties();
2006 *aSelectionStart = props.GetStart();
2007 *aSelectionEnd = props.GetEnd();
2008 return;
2009 }
2010
2011 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2012 if (NS_WARN_IF(!sel)) {
2013 aRv.Throw(NS_ERROR_FAILURE);
2014 return;
2015 }
2016
2017 Element* root = GetRootNode();
2018 if (NS_WARN_IF(!root)) {
2019 aRv.Throw(NS_ERROR_UNEXPECTED);
2020 return;
2021 }
2022 nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
2023 *aSelectionEnd);
2024 }
2025
GetSelectionDirection(ErrorResult & aRv)2026 nsITextControlFrame::SelectionDirection TextControlState::GetSelectionDirection(
2027 ErrorResult& aRv) {
2028 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2029 "How can we not have a cached selection if we have no selection "
2030 "controller?");
2031
2032 // Note that we may have both IsSelectionCached() _and_
2033 // GetSelectionController() if we haven't initialized our editor yet.
2034 if (IsSelectionCached()) {
2035 return GetSelectionProperties().GetDirection();
2036 }
2037
2038 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2039 if (NS_WARN_IF(!sel)) {
2040 aRv.Throw(NS_ERROR_FAILURE);
2041 return nsITextControlFrame::eForward; // Doesn't really matter
2042 }
2043
2044 nsDirection direction = sel->GetDirection();
2045 if (direction == eDirNext) {
2046 return nsITextControlFrame::eForward;
2047 }
2048
2049 MOZ_ASSERT(direction == eDirPrevious);
2050 return nsITextControlFrame::eBackward;
2051 }
2052
SetSelectionRange(uint32_t aStart,uint32_t aEnd,nsITextControlFrame::SelectionDirection aDirection,ErrorResult & aRv,ScrollAfterSelection aScroll)2053 void TextControlState::SetSelectionRange(
2054 uint32_t aStart, uint32_t aEnd,
2055 nsITextControlFrame::SelectionDirection aDirection, ErrorResult& aRv,
2056 ScrollAfterSelection aScroll) {
2057 MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
2058 "How can we have a non-cached selection but no frame?");
2059
2060 AutoTextControlHandlingState handlingSetSelectionRange(
2061 *this, TextControlAction::SetSelectionRange);
2062
2063 if (aStart > aEnd) {
2064 aStart = aEnd;
2065 }
2066
2067 if (!IsSelectionCached()) {
2068 MOZ_ASSERT(mBoundFrame, "Our frame should still be valid");
2069 aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
2070 if (aRv.Failed() ||
2071 handlingSetSelectionRange.IsTextControlStateDestroyed()) {
2072 return;
2073 }
2074 if (aScroll == ScrollAfterSelection::Yes && mBoundFrame) {
2075 // mBoundFrame could be gone if selection listeners flushed layout for
2076 // example.
2077 mBoundFrame->ScrollSelectionIntoViewAsync();
2078 }
2079 return;
2080 }
2081
2082 SelectionProperties& props = GetSelectionProperties();
2083 if (!props.HasMaxLength()) {
2084 // A clone without a dirty value flag may not have a max length yet
2085 nsAutoString value;
2086 GetValue(value, false);
2087 props.SetMaxLength(value.Length());
2088 }
2089
2090 bool changed = props.SetStart(aStart);
2091 changed |= props.SetEnd(aEnd);
2092 changed |= props.SetDirection(aDirection);
2093
2094 if (!changed) {
2095 return;
2096 }
2097
2098 // It sure would be nice if we had an existing Element* or so to work with.
2099 RefPtr<AsyncEventDispatcher> asyncDispatcher =
2100 new AsyncEventDispatcher(mTextCtrlElement, eFormSelect, CanBubble::eYes);
2101 asyncDispatcher->PostDOMEvent();
2102
2103 // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
2104 // XXX(krosylight): Shouldn't it fire before select event?
2105 // Currently Gecko and Blink both fire selectionchange after select.
2106 if (IsSelectionCached() &&
2107 StaticPrefs::dom_select_events_textcontrols_enabled()) {
2108 asyncDispatcher = new AsyncEventDispatcher(
2109 mTextCtrlElement, eSelectionChange, CanBubble::eNo);
2110 asyncDispatcher->PostDOMEvent();
2111 }
2112 }
2113
SetSelectionStart(const Nullable<uint32_t> & aStart,ErrorResult & aRv)2114 void TextControlState::SetSelectionStart(const Nullable<uint32_t>& aStart,
2115 ErrorResult& aRv) {
2116 uint32_t start = 0;
2117 if (!aStart.IsNull()) {
2118 start = aStart.Value();
2119 }
2120
2121 uint32_t ignored, end;
2122 GetSelectionRange(&ignored, &end, aRv);
2123 if (aRv.Failed()) {
2124 return;
2125 }
2126
2127 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
2128 if (aRv.Failed()) {
2129 return;
2130 }
2131
2132 if (end < start) {
2133 end = start;
2134 }
2135
2136 SetSelectionRange(start, end, dir, aRv);
2137 // The instance may have already been deleted here.
2138 }
2139
SetSelectionEnd(const Nullable<uint32_t> & aEnd,ErrorResult & aRv)2140 void TextControlState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
2141 ErrorResult& aRv) {
2142 uint32_t end = 0;
2143 if (!aEnd.IsNull()) {
2144 end = aEnd.Value();
2145 }
2146
2147 uint32_t start, ignored;
2148 GetSelectionRange(&start, &ignored, aRv);
2149 if (aRv.Failed()) {
2150 return;
2151 }
2152
2153 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
2154 if (aRv.Failed()) {
2155 return;
2156 }
2157
2158 SetSelectionRange(start, end, dir, aRv);
2159 // The instance may have already been deleted here.
2160 }
2161
DirectionToName(nsITextControlFrame::SelectionDirection dir,nsAString & aDirection)2162 static void DirectionToName(nsITextControlFrame::SelectionDirection dir,
2163 nsAString& aDirection) {
2164 switch (dir) {
2165 case nsITextControlFrame::eNone:
2166 // TODO(mbrodesser): this should be supported, see
2167 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
2168 NS_WARNING("We don't actually support this... how did we get it?");
2169 aDirection.AssignLiteral("none");
2170 break;
2171 case nsITextControlFrame::eForward:
2172 aDirection.AssignLiteral("forward");
2173 break;
2174 case nsITextControlFrame::eBackward:
2175 aDirection.AssignLiteral("backward");
2176 break;
2177 default:
2178 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
2179 }
2180 }
2181
GetSelectionDirectionString(nsAString & aDirection,ErrorResult & aRv)2182 void TextControlState::GetSelectionDirectionString(nsAString& aDirection,
2183 ErrorResult& aRv) {
2184 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
2185 if (aRv.Failed()) {
2186 return;
2187 }
2188 DirectionToName(dir, aDirection);
2189 }
2190
2191 static nsITextControlFrame::SelectionDirection
DirectionStringToSelectionDirection(const nsAString & aDirection)2192 DirectionStringToSelectionDirection(const nsAString& aDirection) {
2193 if (aDirection.EqualsLiteral("backward")) {
2194 return nsITextControlFrame::eBackward;
2195 }
2196
2197 // We don't support directionless selections, see bug 1541454.
2198 return nsITextControlFrame::eForward;
2199 }
2200
SetSelectionDirection(const nsAString & aDirection,ErrorResult & aRv)2201 void TextControlState::SetSelectionDirection(const nsAString& aDirection,
2202 ErrorResult& aRv) {
2203 nsITextControlFrame::SelectionDirection dir =
2204 DirectionStringToSelectionDirection(aDirection);
2205
2206 uint32_t start, end;
2207 GetSelectionRange(&start, &end, aRv);
2208 if (aRv.Failed()) {
2209 return;
2210 }
2211
2212 SetSelectionRange(start, end, dir, aRv);
2213 // The instance may have already been deleted here.
2214 }
2215
2216 static nsITextControlFrame::SelectionDirection
DirectionStringToSelectionDirection(const Optional<nsAString> & aDirection)2217 DirectionStringToSelectionDirection(const Optional<nsAString>& aDirection) {
2218 if (!aDirection.WasPassed()) {
2219 // We don't support directionless selections.
2220 return nsITextControlFrame::eForward;
2221 }
2222
2223 return DirectionStringToSelectionDirection(aDirection.Value());
2224 }
2225
SetSelectionRange(uint32_t aSelectionStart,uint32_t aSelectionEnd,const Optional<nsAString> & aDirection,ErrorResult & aRv,ScrollAfterSelection aScroll)2226 void TextControlState::SetSelectionRange(uint32_t aSelectionStart,
2227 uint32_t aSelectionEnd,
2228 const Optional<nsAString>& aDirection,
2229 ErrorResult& aRv,
2230 ScrollAfterSelection aScroll) {
2231 nsITextControlFrame::SelectionDirection dir =
2232 DirectionStringToSelectionDirection(aDirection);
2233
2234 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv, aScroll);
2235 // The instance may have already been deleted here.
2236 }
2237
SetRangeText(const nsAString & aReplacement,ErrorResult & aRv)2238 void TextControlState::SetRangeText(const nsAString& aReplacement,
2239 ErrorResult& aRv) {
2240 uint32_t start, end;
2241 GetSelectionRange(&start, &end, aRv);
2242 if (aRv.Failed()) {
2243 return;
2244 }
2245
2246 SetRangeText(aReplacement, start, end, SelectionMode::Preserve, aRv,
2247 Some(start), Some(end));
2248 // The instance may have already been deleted here.
2249 }
2250
SetRangeText(const nsAString & aReplacement,uint32_t aStart,uint32_t aEnd,SelectionMode aSelectMode,ErrorResult & aRv,const Maybe<uint32_t> & aSelectionStart,const Maybe<uint32_t> & aSelectionEnd)2251 void TextControlState::SetRangeText(const nsAString& aReplacement,
2252 uint32_t aStart, uint32_t aEnd,
2253 SelectionMode aSelectMode, ErrorResult& aRv,
2254 const Maybe<uint32_t>& aSelectionStart,
2255 const Maybe<uint32_t>& aSelectionEnd) {
2256 if (aStart > aEnd) {
2257 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2258 return;
2259 }
2260
2261 AutoTextControlHandlingState handlingSetRangeText(
2262 *this, TextControlAction::SetRangeText);
2263
2264 nsAutoString value;
2265 mTextCtrlElement->GetValueFromSetRangeText(value);
2266 uint32_t inputValueLength = value.Length();
2267
2268 if (aStart > inputValueLength) {
2269 aStart = inputValueLength;
2270 }
2271
2272 if (aEnd > inputValueLength) {
2273 aEnd = inputValueLength;
2274 }
2275
2276 uint32_t selectionStart, selectionEnd;
2277 if (!aSelectionStart) {
2278 MOZ_ASSERT(!aSelectionEnd);
2279 GetSelectionRange(&selectionStart, &selectionEnd, aRv);
2280 if (aRv.Failed()) {
2281 return;
2282 }
2283 } else {
2284 MOZ_ASSERT(aSelectionEnd);
2285 selectionStart = *aSelectionStart;
2286 selectionEnd = *aSelectionEnd;
2287 }
2288
2289 // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
2290 Selection* selection =
2291 mSelCon ? mSelCon->GetSelection(SelectionType::eNormal) : nullptr;
2292 SelectionBatcher selectionBatcher(
2293 selection, nsISelectionListener::JS_REASON); // no-op if nullptr
2294
2295 MOZ_ASSERT(aStart <= aEnd);
2296 value.Replace(aStart, aEnd - aStart, aReplacement);
2297 nsresult rv =
2298 MOZ_KnownLive(mTextCtrlElement)->SetValueFromSetRangeText(value);
2299 if (NS_FAILED(rv)) {
2300 aRv.Throw(rv);
2301 return;
2302 }
2303
2304 uint32_t newEnd = aStart + aReplacement.Length();
2305 int32_t delta = aReplacement.Length() - (aEnd - aStart);
2306
2307 switch (aSelectMode) {
2308 case SelectionMode::Select:
2309 selectionStart = aStart;
2310 selectionEnd = newEnd;
2311 break;
2312 case SelectionMode::Start:
2313 selectionStart = selectionEnd = aStart;
2314 break;
2315 case SelectionMode::End:
2316 selectionStart = selectionEnd = newEnd;
2317 break;
2318 case SelectionMode::Preserve:
2319 if (selectionStart > aEnd) {
2320 selectionStart += delta;
2321 } else if (selectionStart > aStart) {
2322 selectionStart = aStart;
2323 }
2324
2325 if (selectionEnd > aEnd) {
2326 selectionEnd += delta;
2327 } else if (selectionEnd > aStart) {
2328 selectionEnd = newEnd;
2329 }
2330 break;
2331 default:
2332 MOZ_ASSERT_UNREACHABLE("Unknown mode!");
2333 }
2334
2335 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
2336 if (IsSelectionCached()) {
2337 // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
2338 GetSelectionProperties().SetMaxLength(value.Length());
2339 }
2340 }
2341
DestroyEditor()2342 void TextControlState::DestroyEditor() {
2343 // notify the editor that we are going away
2344 if (mEditorInitialized) {
2345 // FYI: TextEditor checks whether it's destroyed or not immediately after
2346 // changes the DOM tree or selection so that it's safe to call
2347 // PreDestroy() here even while we're handling actions with
2348 // mTextEditor.
2349 MOZ_ASSERT(!mPasswordMaskData);
2350 RefPtr<TextEditor> textEditor = mTextEditor;
2351 mPasswordMaskData = textEditor->PreDestroy();
2352 MOZ_ASSERT_IF(mPasswordMaskData, !mPasswordMaskData->mTimer);
2353 mEditorInitialized = false;
2354 }
2355 }
2356
UnbindFromFrame(nsTextControlFrame * aFrame)2357 void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
2358 if (NS_WARN_IF(!mBoundFrame)) {
2359 return;
2360 }
2361
2362 // If it was, however, it should be unbounded from the same frame.
2363 MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2364 if (aFrame && aFrame != mBoundFrame) {
2365 return;
2366 }
2367
2368 AutoTextControlHandlingState handlingUnbindFromFrame(
2369 *this, TextControlAction::UnbindFromFrame);
2370
2371 if (mSelCon) {
2372 mSelCon->SelectionWillLoseFocus();
2373 }
2374
2375 // We need to start storing the value outside of the editor if we're not
2376 // going to use it anymore, so retrieve it for now.
2377 nsAutoString value;
2378 GetValue(value, true);
2379
2380 if (mRestoringSelection) {
2381 mRestoringSelection->Revoke();
2382 mRestoringSelection = nullptr;
2383 }
2384
2385 // Save our selection state if needed.
2386 // Note that GetSelectionRange will attempt to work with our selection
2387 // controller, so we should make sure we do it before we start doing things
2388 // like destroying our editor (if we have one), tearing down the selection
2389 // controller, and so forth.
2390 if (!IsSelectionCached()) {
2391 // Go ahead and cache it now.
2392 uint32_t start = 0, end = 0;
2393 GetSelectionRange(&start, &end, IgnoreErrors());
2394
2395 nsITextControlFrame::SelectionDirection direction =
2396 GetSelectionDirection(IgnoreErrors());
2397
2398 SelectionProperties& props = GetSelectionProperties();
2399 props.SetMaxLength(value.Length());
2400 props.SetStart(start);
2401 props.SetEnd(end);
2402 props.SetDirection(direction);
2403 mSelectionCached = true;
2404 }
2405
2406 // Destroy our editor
2407 DestroyEditor();
2408
2409 // Clean up the controller
2410 if (!SuppressEventHandlers(mBoundFrame->PresContext())) {
2411 nsCOMPtr<nsIControllers> controllers;
2412 if (HTMLInputElement* inputElement =
2413 HTMLInputElement::FromNodeOrNull(mTextCtrlElement)) {
2414 inputElement->GetControllers(getter_AddRefs(controllers));
2415 } else {
2416 HTMLTextAreaElement* textAreaElement =
2417 HTMLTextAreaElement::FromNodeOrNull(mTextCtrlElement);
2418 if (textAreaElement) {
2419 textAreaElement->GetControllers(getter_AddRefs(controllers));
2420 }
2421 }
2422
2423 if (controllers) {
2424 uint32_t numControllers;
2425 nsresult rv = controllers->GetControllerCount(&numControllers);
2426 NS_ASSERTION((NS_SUCCEEDED(rv)),
2427 "bad result in gfx text control destructor");
2428 for (uint32_t i = 0; i < numControllers; i++) {
2429 nsCOMPtr<nsIController> controller;
2430 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2431 if (NS_SUCCEEDED(rv) && controller) {
2432 nsCOMPtr<nsIControllerContext> editController =
2433 do_QueryInterface(controller);
2434 if (editController) {
2435 editController->SetCommandContext(nullptr);
2436 }
2437 }
2438 }
2439 }
2440 }
2441
2442 if (mSelCon) {
2443 if (mTextListener) {
2444 mTextListener->EndListeningToSelectionChange();
2445 }
2446
2447 mSelCon->SetScrollableFrame(nullptr);
2448 mSelCon = nullptr;
2449 }
2450
2451 if (mTextListener) {
2452 mTextListener->SetFrame(nullptr);
2453
2454 EventListenerManager* manager =
2455 mTextCtrlElement->GetExistingListenerManager();
2456 if (manager) {
2457 manager->RemoveEventListenerByType(mTextListener, u"keydown"_ns,
2458 TrustedEventsAtSystemGroupBubble());
2459 manager->RemoveEventListenerByType(mTextListener, u"keypress"_ns,
2460 TrustedEventsAtSystemGroupBubble());
2461 manager->RemoveEventListenerByType(mTextListener, u"keyup"_ns,
2462 TrustedEventsAtSystemGroupBubble());
2463 }
2464
2465 mTextListener = nullptr;
2466 }
2467
2468 mBoundFrame = nullptr;
2469
2470 // Now that we don't have a frame any more, store the value in the text
2471 // buffer. The only case where we don't do this is if a value transfer is in
2472 // progress.
2473 if (!mValueTransferInProgress) {
2474 DebugOnly<bool> ok = SetValue(value, ValueSetterOption::ByInternalAPI);
2475 // TODO Find something better to do if this fails...
2476 NS_WARNING_ASSERTION(ok, "SetValue() couldn't allocate memory");
2477 }
2478 }
2479
GetValue(nsAString & aValue,bool aIgnoreWrap) const2480 void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap) const {
2481 // While SetValue() is being called and requesting to commit composition to
2482 // IME, GetValue() may be called for appending text or something. Then, we
2483 // need to return the latest aValue of SetValue() since the value hasn't
2484 // been set to the editor yet.
2485 // XXX After implementing "beforeinput" event, this becomes wrong. The
2486 // value should be modified immediately after "beforeinput" event for
2487 // "insertReplacementText".
2488 if (mHandlingState &&
2489 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2490 aValue = mHandlingState->GetSettingValue();
2491 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2492 return;
2493 }
2494
2495 if (mTextEditor && mBoundFrame &&
2496 (mEditorInitialized || !IsSingleLineTextControl())) {
2497 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2498 aValue = mBoundFrame->CachedValue();
2499 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2500 return;
2501 }
2502
2503 aValue.Truncate(); // initialize out param
2504
2505 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2506 nsIDocumentEncoder::OutputPreformatted |
2507 nsIDocumentEncoder::OutputPersistNBSP |
2508 nsIDocumentEncoder::OutputBodyOnly);
2509 if (!aIgnoreWrap) {
2510 TextControlElement::nsHTMLTextWrap wrapProp;
2511 if (mTextCtrlElement &&
2512 TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
2513 wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
2514 flags |= nsIDocumentEncoder::OutputWrap;
2515 }
2516 }
2517
2518 // What follows is a bit of a hack. The problem is that we could be in
2519 // this method because we're being destroyed for whatever reason while
2520 // script is executing. If that happens, editor will run with the
2521 // privileges of the executing script, which means it may not be able to
2522 // access its own DOM nodes! Let's try to deal with that by pushing a null
2523 // JSContext on the JSContext stack to make it clear that we're native
2524 // code. Note that any script that's directly trying to access our value
2525 // has to be going through some scriptable object to do that and that
2526 // already does the relevant security checks.
2527 // XXXbz if we could just get the textContent of our anonymous content (eg
2528 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2529 // this.
2530 { /* Scope for AutoNoJSAPI. */
2531 AutoNoJSAPI nojsapi;
2532
2533 DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2534 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2535 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2536 }
2537 // Only when the result doesn't include line breaks caused by hard-wrap,
2538 // mCacheValue should cache the value.
2539 if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2540 mBoundFrame->CacheValue(aValue);
2541 } else {
2542 mBoundFrame->ClearCachedValue();
2543 }
2544 } else {
2545 if (!mTextCtrlElement->ValueChanged() || !mValue) {
2546 // Use nsString to avoid copying string buffer at setting aValue.
2547 nsString value;
2548 mTextCtrlElement->GetDefaultValueFromContent(value);
2549 // TODO: We should make default value not include \r.
2550 nsContentUtils::PlatformToDOMLineBreaks(value);
2551 aValue = value;
2552 } else {
2553 aValue = *mValue;
2554 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2555 }
2556 }
2557 }
2558
ValueEquals(const nsAString & aValue) const2559 bool TextControlState::ValueEquals(const nsAString& aValue) const {
2560 // We can avoid copying string buffer in many cases. Therefore, we should
2561 // use nsString rather than nsAutoString here.
2562 nsString value;
2563 GetValue(value, true);
2564 return aValue.Equals(value);
2565 }
2566
2567 #ifdef DEBUG
2568 // @param aOptions TextControlState::ValueSetterOptions
AreFlagsNotDemandingContradictingMovements(const ValueSetterOptions & aOptions)2569 bool AreFlagsNotDemandingContradictingMovements(
2570 const ValueSetterOptions& aOptions) {
2571 return !aOptions.contains(
2572 {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward,
2573 ValueSetterOption::MoveCursorToEndIfValueChanged});
2574 }
2575 #endif // DEBUG
2576
SetValue(const nsAString & aValue,const nsAString * aOldValue,const ValueSetterOptions & aOptions)2577 bool TextControlState::SetValue(const nsAString& aValue,
2578 const nsAString* aOldValue,
2579 const ValueSetterOptions& aOptions) {
2580 if (mHandlingState &&
2581 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2582 // GetValue doesn't return current text frame's content during committing.
2583 // So we cannot trust this old value
2584 aOldValue = nullptr;
2585 }
2586
2587 if (mPasswordMaskData) {
2588 if (mHandlingState &&
2589 mHandlingState->Is(TextControlAction::UnbindFromFrame)) {
2590 // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
2591 } else {
2592 // Otherwise, we should mask the new password, even if it's same value
2593 // since the same value may be one for different web app's.
2594 mPasswordMaskData->Reset();
2595 }
2596 }
2597
2598 const bool wasHandlingSetValue =
2599 mHandlingState && mHandlingState->IsHandling(TextControlAction::SetValue);
2600
2601 ErrorResult error;
2602 AutoTextControlHandlingState handlingSetValue(
2603 *this, TextControlAction::SetValue, aValue, aOldValue, aOptions, error);
2604 if (error.Failed()) {
2605 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
2606 error.SuppressException();
2607 return false;
2608 }
2609
2610 // Note that if this may be called during reframe of the editor. In such
2611 // case, we shouldn't commit composition. Therefore, when this is called
2612 // for internal processing, we shouldn't commit the composition.
2613 // TODO: In strictly speaking, we should move committing composition into
2614 // editor because if "beforeinput" for this setting value is canceled,
2615 // we shouldn't commit composition. However, in Firefox, we never
2616 // call this via `setUserInput` during composition. Therefore, the
2617 // bug must not be reproducible actually.
2618 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
2619 aOptions.contains(ValueSetterOption::ByContentAPI)) {
2620 if (EditorHasComposition()) {
2621 // When this is called recursively, there shouldn't be composition.
2622 if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
2623 // Don't request to commit composition again. But if it occurs,
2624 // we should skip to set the new value to the editor here. It should
2625 // be set later with the newest value.
2626 return true;
2627 }
2628 if (NS_WARN_IF(!mBoundFrame)) {
2629 // We're not sure if this case is possible.
2630 } else {
2631 // If setting value won't change current value, we shouldn't commit
2632 // composition for compatibility with the other browsers.
2633 MOZ_ASSERT(!aOldValue || mBoundFrame->TextEquals(*aOldValue));
2634 bool isSameAsCurrentValue =
2635 aOldValue
2636 ? aOldValue->Equals(handlingSetValue.GetSettingValue())
2637 : mBoundFrame->TextEquals(handlingSetValue.GetSettingValue());
2638 if (isSameAsCurrentValue) {
2639 // Note that in this case, we shouldn't fire any events with setting
2640 // value because event handlers may try to set value recursively but
2641 // we cannot commit composition at that time due to unsafe to run
2642 // script (see below).
2643 return true;
2644 }
2645 }
2646 // If there is composition, need to commit composition first because
2647 // other browsers do that.
2648 // NOTE: We don't need to block nested calls of this because input nor
2649 // other events won't be fired by setting values and script blocker
2650 // is used during setting the value to the editor. IE also allows
2651 // to set the editor value on the input event which is caused by
2652 // forcibly committing composition.
2653 AutoTextControlHandlingState handlingCommitComposition(
2654 *this, TextControlAction::CommitComposition);
2655 if (nsContentUtils::IsSafeToRunScript()) {
2656 // WARNING: During this call, compositionupdate, compositionend, input
2657 // events will be fired. Therefore, everything can occur. E.g., the
2658 // document may be unloaded.
2659 RefPtr<TextEditor> textEditor = mTextEditor;
2660 nsresult rv = textEditor->CommitComposition();
2661 if (handlingCommitComposition.IsTextControlStateDestroyed()) {
2662 return true;
2663 }
2664 if (NS_FAILED(rv)) {
2665 NS_WARNING("TextControlState failed to commit composition");
2666 return true;
2667 }
2668 // Note that if a composition event listener sets editor value again,
2669 // we should use the new value here. The new value is stored in
2670 // handlingSetValue right now.
2671 } else {
2672 NS_WARNING(
2673 "SetValue() is called when there is composition but "
2674 "it's not safe to request to commit the composition");
2675 }
2676 }
2677 }
2678
2679 if (mTextEditor && mBoundFrame) {
2680 if (!SetValueWithTextEditor(handlingSetValue)) {
2681 return false;
2682 }
2683 } else if (!SetValueWithoutTextEditor(handlingSetValue)) {
2684 return false;
2685 }
2686
2687 // If we were handling SetValue() before, don't update the DOM state twice,
2688 // just let the outer call do so.
2689 if (!wasHandlingSetValue) {
2690 // TODO(emilio): It seems wrong to pass ValueChangeKind::Script if
2691 // BySetUserInput is in aOptions.
2692 auto changeKind = aOptions.contains(ValueSetterOption::ByInternalAPI)
2693 ? ValueChangeKind::Internal
2694 : ValueChangeKind::Script;
2695 handlingSetValue.GetTextControlElement()->OnValueChanged(changeKind);
2696 }
2697 return true;
2698 }
2699
SetValueWithTextEditor(AutoTextControlHandlingState & aHandlingSetValue)2700 bool TextControlState::SetValueWithTextEditor(
2701 AutoTextControlHandlingState& aHandlingSetValue) {
2702 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2703 MOZ_ASSERT(mTextEditor);
2704 MOZ_ASSERT(mBoundFrame);
2705 NS_WARNING_ASSERTION(!EditorHasComposition(),
2706 "Failed to commit composition before setting value. "
2707 "Investigate the cause!");
2708
2709 #ifdef DEBUG
2710 if (IsSingleLineTextControl()) {
2711 NS_ASSERTION(mEditorInitialized || aHandlingSetValue.IsHandling(
2712 TextControlAction::PrepareEditor),
2713 "We should never try to use the editor if we're not "
2714 "initialized unless we're being initialized");
2715 }
2716 #endif
2717
2718 MOZ_ASSERT(!aHandlingSetValue.GetOldValue() ||
2719 mBoundFrame->TextEquals(*aHandlingSetValue.GetOldValue()));
2720 bool isSameAsCurrentValue =
2721 aHandlingSetValue.GetOldValue()
2722 ? aHandlingSetValue.GetOldValue()->Equals(
2723 aHandlingSetValue.GetSettingValue())
2724 : mBoundFrame->TextEquals(aHandlingSetValue.GetSettingValue());
2725
2726 // this is necessary to avoid infinite recursion
2727 if (isSameAsCurrentValue) {
2728 return true;
2729 }
2730
2731 RefPtr<TextEditor> textEditor = mTextEditor;
2732
2733 nsCOMPtr<Document> document = textEditor->GetDocument();
2734 if (NS_WARN_IF(!document)) {
2735 return true;
2736 }
2737
2738 // Time to mess with our security context... See comments in GetValue()
2739 // for why this is needed. Note that we have to do this up here, because
2740 // otherwise SelectAll() will fail.
2741 AutoNoJSAPI nojsapi;
2742
2743 // FYI: It's safe to use raw pointer for selection here because
2744 // SelectionBatcher will grab it with RefPtr.
2745 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
2746 SelectionBatcher selectionBatcher(selection);
2747
2748 // get the flags, remove readonly, disabled and max-length,
2749 // set the value, restore flags
2750 AutoRestoreEditorState restoreState(textEditor);
2751
2752 aHandlingSetValue.WillSetValueWithTextEditor();
2753
2754 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2755 ValueSetterOption::BySetUserInputAPI)) {
2756 // If the caller inserts text as part of user input, for example,
2757 // autocomplete, we need to replace the text as "insert string"
2758 // because undo should cancel only this operation (i.e., previous
2759 // transactions typed by user shouldn't be merged with this).
2760 // In this case, we need to dispatch "input" event because
2761 // web apps may need to know the user's operation.
2762 // In this case, we need to dispatch "beforeinput" events since
2763 // we're emulating the user's input. Passing nullptr as
2764 // nsIPrincipal means that that may be user's input. So, let's
2765 // do it.
2766 nsresult rv = textEditor->ReplaceTextAsAction(
2767 aHandlingSetValue.GetSettingValue(), nullptr,
2768 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2769 ? TextEditor::AllowBeforeInputEventCancelable::Yes
2770 : TextEditor::AllowBeforeInputEventCancelable::No,
2771 nullptr);
2772 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2773 "EditorBase::ReplaceTextAsAction() failed");
2774 return rv != NS_ERROR_OUT_OF_MEMORY;
2775 }
2776
2777 // Don't dispatch "beforeinput" event nor "input" event for setting value
2778 // by script.
2779 AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
2780
2781 // On <input> or <textarea>, we shouldn't preserve existing undo
2782 // transactions because other browsers do not preserve them too
2783 // and not preserving transactions makes setting value faster.
2784 //
2785 // (Except if chrome opts into this behavior).
2786 Maybe<AutoDisableUndo> disableUndo;
2787 if (!aHandlingSetValue.ValueSetterOptionsRef().contains(
2788 ValueSetterOption::PreserveUndoHistory)) {
2789 disableUndo.emplace(textEditor);
2790 }
2791
2792 if (selection) {
2793 // Since we don't use undo transaction, we don't need to store
2794 // selection state. SetText will set selection to tail.
2795 IgnoredErrorResult ignoredError;
2796 MOZ_KnownLive(selection)->RemoveAllRanges(ignoredError);
2797 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2798 "Selection::RemoveAllRanges() failed, but ignored");
2799 }
2800
2801 // In this case, we makes the editor stop dispatching "input"
2802 // event so that passing nullptr as nsIPrincipal is safe for now.
2803 nsresult rv = textEditor->SetTextAsAction(
2804 aHandlingSetValue.GetSettingValue(),
2805 aHandlingSetValue.ValueSetterOptionsRef().contains(
2806 ValueSetterOption::BySetUserInputAPI) &&
2807 !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2808 ? TextEditor::AllowBeforeInputEventCancelable::No
2809 : TextEditor::AllowBeforeInputEventCancelable::Yes,
2810 nullptr);
2811 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2812 "TextEditor::SetTextAsAction() failed");
2813
2814 // Call the listener's OnEditActionHandled() callback manually if
2815 // OnEditActionHandled() hasn't been called yet since TextEditor don't use
2816 // the transaction manager in this path and it could be that the editor
2817 // would bypass calling the listener for that reason.
2818 if (!aHandlingSetValue.HasEditActionHandled()) {
2819 nsresult rvOnEditActionHandled =
2820 MOZ_KnownLive(aHandlingSetValue.GetTextInputListener())
2821 ->OnEditActionHandled(*textEditor);
2822 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled),
2823 "TextInputListener::OnEditActionHandled() failed");
2824 if (rv != NS_ERROR_OUT_OF_MEMORY) {
2825 rv = rvOnEditActionHandled;
2826 }
2827 }
2828
2829 return rv != NS_ERROR_OUT_OF_MEMORY;
2830 }
2831
SetValueWithoutTextEditor(AutoTextControlHandlingState & aHandlingSetValue)2832 bool TextControlState::SetValueWithoutTextEditor(
2833 AutoTextControlHandlingState& aHandlingSetValue) {
2834 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2835 MOZ_ASSERT(!mTextEditor || !mBoundFrame);
2836 NS_WARNING_ASSERTION(!EditorHasComposition(),
2837 "Failed to commit composition before setting value. "
2838 "Investigate the cause!");
2839
2840 if (!mValue) {
2841 mValue.emplace();
2842 }
2843
2844 // We can't just early-return here, because OnValueChanged below still need to
2845 // be called.
2846 if (!mValue->Equals(aHandlingSetValue.GetSettingValue()) ||
2847 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2848 bool handleSettingValue = true;
2849 // If `SetValue()` call is nested, `GetSettingValue()` result will be
2850 // modified. So, we need to store input event data value before
2851 // dispatching beforeinput event.
2852 nsString inputEventData(aHandlingSetValue.GetSettingValue());
2853 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2854 ValueSetterOption::BySetUserInputAPI) &&
2855 StaticPrefs::dom_input_events_beforeinput_enabled() &&
2856 !aHandlingSetValue.HasBeforeInputEventDispatched()) {
2857 // This probably occurs when session restorer sets the old value with
2858 // `setUserInput`. If so, we need to dispatch "beforeinput" event of
2859 // "insertReplacementText" for conforming to the spec. However, the
2860 // spec does NOT treat the session restoring case. Therefore, if this
2861 // breaks session restorere in a lot of web apps, we should probably
2862 // stop dispatching it or make it non-cancelable.
2863 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2864 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2865 aHandlingSetValue.WillDispatchBeforeInputEvent();
2866 nsEventStatus status = nsEventStatus_eIgnore;
2867 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2868 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2869 eEditorBeforeInput, EditorInputType::eInsertReplacementText, nullptr,
2870 InputEventOptions(
2871 inputEventData,
2872 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2873 ? InputEventOptions::NeverCancelable::No
2874 : InputEventOptions::NeverCancelable::Yes),
2875 &status);
2876 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2877 "Failed to dispatch beforeinput event");
2878 if (status == nsEventStatus_eConsumeNoDefault) {
2879 return true; // "beforeinput" event was canceled.
2880 }
2881 // If we were destroyed by "beforeinput" event listeners, probably, we
2882 // don't need to keep handling it.
2883 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2884 return true;
2885 }
2886 // Even if "beforeinput" event was not canceled, its listeners may do
2887 // something. If it causes creating `TextEditor` and bind this to a
2888 // frame, we need to use the path, but `TextEditor` shouldn't fire
2889 // "beforeinput" event again. Therefore, we need to prevent editor
2890 // to dispatch it.
2891 if (mTextEditor && mBoundFrame) {
2892 AutoInputEventSuppresser suppressInputEvent(mTextEditor);
2893 if (!SetValueWithTextEditor(aHandlingSetValue)) {
2894 return false;
2895 }
2896 // If we were destroyed by "beforeinput" event listeners, probably, we
2897 // don't need to keep handling it.
2898 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2899 return true;
2900 }
2901 handleSettingValue = false;
2902 }
2903 }
2904
2905 if (handleSettingValue) {
2906 if (!mValue->Assign(aHandlingSetValue.GetSettingValue(), fallible)) {
2907 return false;
2908 }
2909
2910 // Since we have no editor we presumably have cached selection state.
2911 if (IsSelectionCached()) {
2912 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
2913 aHandlingSetValue.ValueSetterOptionsRef()));
2914
2915 SelectionProperties& props = GetSelectionProperties();
2916 // Setting a max length and thus capping selection range early prevents
2917 // selection change detection in setRangeText. Temporarily disable
2918 // capping here with UINT32_MAX, and set it later in ::SetRangeText().
2919 props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
2920 ValueSetterOption::BySetRangeTextAPI)
2921 ? UINT32_MAX
2922 : aHandlingSetValue.GetSettingValue().Length());
2923 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2924 ValueSetterOption::MoveCursorToEndIfValueChanged)) {
2925 props.SetStart(aHandlingSetValue.GetSettingValue().Length());
2926 props.SetEnd(aHandlingSetValue.GetSettingValue().Length());
2927 props.SetDirection(nsITextControlFrame::eForward);
2928 } else if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2929 ValueSetterOption::
2930 MoveCursorToBeginSetSelectionDirectionForward)) {
2931 props.SetStart(0);
2932 props.SetEnd(0);
2933 props.SetDirection(nsITextControlFrame::eForward);
2934 }
2935 }
2936
2937 // Update the frame display if needed
2938 if (mBoundFrame) {
2939 mBoundFrame->UpdateValueDisplay(true);
2940 }
2941 }
2942
2943 // If this is called as part of user input, we need to dispatch "input"
2944 // event with "insertReplacementText" since web apps may want to know
2945 // the user operation which changes editor value with a built-in function
2946 // like autocomplete, password manager, session restore, etc.
2947 // XXX Should we stop dispatching `input` event if the text control
2948 // element has already removed from the DOM tree by a `beforeinput`
2949 // event listener?
2950 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2951 ValueSetterOption::BySetUserInputAPI)) {
2952 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2953
2954 // Update validity state before dispatching "input" event for its
2955 // listeners like `EditorBase::NotifyEditorObservers()`.
2956 aHandlingSetValue.GetTextControlElement()->OnValueChanged(
2957 ValueChangeKind::UserInteraction);
2958
2959 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2960 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2961 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2962 eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
2963 InputEventOptions(inputEventData,
2964 InputEventOptions::NeverCancelable::No));
2965 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2966 "Failed to dispatch input event");
2967 }
2968 } else {
2969 // Even if our value is not actually changing, apparently we need to mark
2970 // our SelectionProperties dirty to make accessibility tests happy.
2971 // Probably because they depend on the SetSelectionRange() call we make on
2972 // our frame in RestoreSelectionState, but I have no idea why they do.
2973 if (IsSelectionCached()) {
2974 SelectionProperties& props = GetSelectionProperties();
2975 props.SetIsDirty();
2976 }
2977 }
2978
2979 return true;
2980 }
2981
HasNonEmptyValue()2982 bool TextControlState::HasNonEmptyValue() {
2983 // If the frame for editor is alive, we can compute it with mTextEditor.
2984 // Otherwise, we need to check cached value via GetValue().
2985 if (mTextEditor && mBoundFrame && mEditorInitialized &&
2986 !(mHandlingState &&
2987 mHandlingState->IsHandling(TextControlAction::CommitComposition))) {
2988 return !mTextEditor->IsEmpty();
2989 }
2990
2991 nsAutoString value;
2992 GetValue(value, true);
2993 return !value.IsEmpty();
2994 }
2995
InitializeKeyboardEventListeners()2996 void TextControlState::InitializeKeyboardEventListeners() {
2997 // register key listeners
2998 EventListenerManager* manager =
2999 mTextCtrlElement->GetOrCreateListenerManager();
3000 if (manager) {
3001 manager->AddEventListenerByType(mTextListener, u"keydown"_ns,
3002 TrustedEventsAtSystemGroupBubble());
3003 manager->AddEventListenerByType(mTextListener, u"keypress"_ns,
3004 TrustedEventsAtSystemGroupBubble());
3005 manager->AddEventListenerByType(mTextListener, u"keyup"_ns,
3006 TrustedEventsAtSystemGroupBubble());
3007 }
3008
3009 mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
3010 }
3011
SetPreviewText(const nsAString & aValue,bool aNotify)3012 void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
3013 // If we don't have a preview div, there's nothing to do.
3014 Element* previewDiv = GetPreviewNode();
3015 if (!previewDiv) {
3016 return;
3017 }
3018
3019 nsAutoString previewValue(aValue);
3020
3021 nsContentUtils::RemoveNewlines(previewValue);
3022 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3023 previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
3024 }
3025
GetPreviewText(nsAString & aValue)3026 void TextControlState::GetPreviewText(nsAString& aValue) {
3027 // If we don't have a preview div, there's nothing to do.
3028 Element* previewDiv = GetPreviewNode();
3029 if (!previewDiv) {
3030 return;
3031 }
3032
3033 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3034 const nsTextFragment* text = previewDiv->GetFirstChild()->GetText();
3035
3036 aValue.Truncate();
3037 text->AppendTo(aValue);
3038 }
3039
EditorHasComposition()3040 bool TextControlState::EditorHasComposition() {
3041 return mTextEditor && mTextEditor->IsIMEComposing();
3042 }
3043
3044 } // namespace mozilla
3045