Backed out changeset 51d87c2129d2 (bug 1865372) for causing RunWatchdog crashes in...
[gecko.git] / dom / html / TextControlState.cpp
bloba4b9043d5456ef2cbeb23082151737e742bbe4a6
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TextControlState.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/CaretAssociationHint.h"
10 #include "mozilla/IMEContentObserver.h"
11 #include "mozilla/IMEStateManager.h"
12 #include "mozilla/TextInputListener.h"
14 #include "nsCOMPtr.h"
15 #include "nsView.h"
16 #include "nsCaret.h"
17 #include "nsLayoutCID.h"
18 #include "nsITextControlFrame.h"
19 #include "nsContentCreatorFunctions.h"
20 #include "nsTextControlFrame.h"
21 #include "nsIControllers.h"
22 #include "nsIControllerContext.h"
23 #include "nsAttrValue.h"
24 #include "nsAttrValueInlines.h"
25 #include "nsGenericHTMLElement.h"
26 #include "nsIDOMEventListener.h"
27 #include "nsIWidget.h"
28 #include "nsIDocumentEncoder.h"
29 #include "nsPIDOMWindow.h"
30 #include "nsServiceManagerUtils.h"
31 #include "mozilla/dom/Selection.h"
32 #include "mozilla/EventListenerManager.h"
33 #include "nsContentUtils.h"
34 #include "mozilla/Preferences.h"
35 #include "nsTextNode.h"
36 #include "nsIController.h"
37 #include "nsIScrollableFrame.h"
38 #include "mozilla/AutoRestore.h"
39 #include "mozilla/InputEventOptions.h"
40 #include "mozilla/NativeKeyBindingsType.h"
41 #include "mozilla/PresShell.h"
42 #include "mozilla/TextEvents.h"
43 #include "mozilla/dom/Event.h"
44 #include "mozilla/dom/ScriptSettings.h"
45 #include "mozilla/dom/HTMLInputElement.h"
46 #include "mozilla/dom/HTMLTextAreaElement.h"
47 #include "mozilla/dom/Text.h"
48 #include "mozilla/StaticPrefs_dom.h"
49 #include "mozilla/StaticPrefs_ui.h"
50 #include "nsFrameSelection.h"
51 #include "mozilla/ErrorResult.h"
52 #include "mozilla/Telemetry.h"
53 #include "mozilla/ShortcutKeys.h"
54 #include "mozilla/KeyEventHandler.h"
55 #include "mozilla/dom/KeyboardEvent.h"
56 #include "mozilla/ScrollTypes.h"
58 namespace mozilla {
60 using namespace dom;
61 using ValueSetterOption = TextControlState::ValueSetterOption;
62 using ValueSetterOptions = TextControlState::ValueSetterOptions;
63 using SelectionDirection = nsITextControlFrame::SelectionDirection;
65 /*****************************************************************************
66 * TextControlElement
67 *****************************************************************************/
69 NS_IMPL_CYCLE_COLLECTION_CLASS(TextControlElement)
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
72 TextControlElement, nsGenericHTMLFormControlElementWithState)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
75 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
76 TextControlElement, nsGenericHTMLFormControlElementWithState)
77 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
79 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
80 TextControlElement, nsGenericHTMLFormControlElementWithState)
82 /*static*/
83 bool TextControlElement::GetWrapPropertyEnum(
84 nsIContent* aContent, TextControlElement::nsHTMLTextWrap& aWrapProp) {
85 // soft is the default; "physical" defaults to soft as well because all other
86 // browsers treat it that way and there is no real reason to maintain physical
87 // and virtual as separate entities if no one else does. Only hard and off
88 // do anything different.
89 aWrapProp = eHTMLTextWrap_Soft; // the default
91 if (!aContent->IsHTMLElement()) {
92 return false;
95 static mozilla::dom::Element::AttrValuesArray strings[] = {
96 nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
97 switch (aContent->AsElement()->FindAttrValueIn(
98 kNameSpaceID_None, nsGkAtoms::wrap, strings, eIgnoreCase)) {
99 case 0:
100 aWrapProp = eHTMLTextWrap_Hard;
101 break;
102 case 1:
103 aWrapProp = eHTMLTextWrap_Off;
104 break;
107 return true;
110 /*static*/
111 already_AddRefed<TextControlElement>
112 TextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
113 if (!aHost) {
114 return nullptr;
117 RefPtr<TextControlElement> parent =
118 TextControlElement::FromNodeOrNull(aHost->GetParent());
119 return parent.forget();
122 TextControlElement::FocusTristate TextControlElement::FocusState() {
123 // We can't be focused if we aren't in a (composed) document
124 Document* doc = GetComposedDoc();
125 if (!doc) {
126 return FocusTristate::eUnfocusable;
129 // first see if we are disabled or not. If disabled then do nothing.
130 if (IsDisabled()) {
131 return FocusTristate::eUnfocusable;
134 return IsInActiveTab(doc) ? FocusTristate::eActiveWindow
135 : FocusTristate::eInactiveWindow;
138 using ValueChangeKind = TextControlElement::ValueChangeKind;
140 MOZ_CAN_RUN_SCRIPT inline nsresult SetEditorFlagsIfNecessary(
141 EditorBase& aEditorBase, uint32_t aFlags) {
142 if (aEditorBase.Flags() == aFlags) {
143 return NS_OK;
145 return aEditorBase.SetFlags(aFlags);
148 /*****************************************************************************
149 * mozilla::AutoInputEventSuppresser
150 *****************************************************************************/
152 class MOZ_STACK_CLASS AutoInputEventSuppresser final {
153 public:
154 explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
155 : mTextEditor(aTextEditor),
156 // To protect against a reentrant call to SetValue, we check whether
157 // another SetValue is already happening for this editor. If it is,
158 // we must wait until we unwind to re-enable oninput events.
159 mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent()) {
160 MOZ_ASSERT(mTextEditor);
161 mTextEditor->SuppressDispatchingInputEvent(true);
163 ~AutoInputEventSuppresser() {
164 mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
167 private:
168 RefPtr<TextEditor> mTextEditor;
169 bool mOuterTransaction;
172 /*****************************************************************************
173 * mozilla::RestoreSelectionState
174 *****************************************************************************/
176 class RestoreSelectionState : public Runnable {
177 public:
178 RestoreSelectionState(TextControlState* aState, nsTextControlFrame* aFrame)
179 : Runnable("RestoreSelectionState"),
180 mFrame(aFrame),
181 mTextControlState(aState) {}
183 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
184 if (!mTextControlState) {
185 return NS_OK;
188 AutoHideSelectionChanges hideSelectionChanges(
189 mFrame->GetConstFrameSelection());
191 if (mFrame) {
192 // EnsureEditorInitialized and SetSelectionRange leads to
193 // Selection::AddRangeAndSelectFramesAndNotifyListeners which flushes
194 // Layout - need to block script to avoid nested PrepareEditor calls (bug
195 // 642800).
196 nsAutoScriptBlocker scriptBlocker;
197 mFrame->EnsureEditorInitialized();
198 TextControlState::SelectionProperties& properties =
199 mTextControlState->GetSelectionProperties();
200 if (properties.IsDirty()) {
201 mFrame->SetSelectionRange(properties.GetStart(), properties.GetEnd(),
202 properties.GetDirection());
206 if (mTextControlState) {
207 mTextControlState->FinishedRestoringSelection();
209 return NS_OK;
212 // Let the text editor tell us we're no longer relevant - avoids use of
213 // AutoWeakFrame
214 void Revoke() {
215 mFrame = nullptr;
216 mTextControlState = nullptr;
219 private:
220 nsTextControlFrame* mFrame;
221 TextControlState* mTextControlState;
224 /*****************************************************************************
225 * mozilla::AutoRestoreEditorState
226 *****************************************************************************/
228 class MOZ_RAII AutoRestoreEditorState final {
229 public:
230 MOZ_CAN_RUN_SCRIPT explicit AutoRestoreEditorState(TextEditor* aTextEditor)
231 : mTextEditor(aTextEditor),
232 mSavedFlags(mTextEditor->Flags()),
233 mSavedMaxLength(mTextEditor->MaxTextLength()),
234 mSavedEchoingPasswordPrevented(
235 mTextEditor->EchoingPasswordPrevented()) {
236 MOZ_ASSERT(mTextEditor);
238 // EditorBase::SetFlags() is a virtual method. Even though it does nothing
239 // if new flags and current flags are same, the calling cost causes
240 // appearing the method in profile. So, this class should check if it's
241 // necessary to call.
242 uint32_t flags = mSavedFlags;
243 flags &= ~nsIEditor::eEditorReadonlyMask;
244 if (mSavedFlags != flags) {
245 // It's aTextEditor and whose lifetime must be guaranteed by the caller.
246 MOZ_KnownLive(mTextEditor)->SetFlags(flags);
248 mTextEditor->PreventToEchoPassword();
249 mTextEditor->SetMaxTextLength(-1);
252 MOZ_CAN_RUN_SCRIPT ~AutoRestoreEditorState() {
253 if (!mSavedEchoingPasswordPrevented) {
254 mTextEditor->AllowToEchoPassword();
256 mTextEditor->SetMaxTextLength(mSavedMaxLength);
257 // mTextEditor's lifetime must be guaranteed by owner of the instance
258 // since the constructor is marked as `MOZ_CAN_RUN_SCRIPT` and this is
259 // a stack only class.
260 SetEditorFlagsIfNecessary(MOZ_KnownLive(*mTextEditor), mSavedFlags);
263 private:
264 TextEditor* mTextEditor;
265 uint32_t mSavedFlags;
266 int32_t mSavedMaxLength;
267 bool mSavedEchoingPasswordPrevented;
270 /*****************************************************************************
271 * mozilla::AutoDisableUndo
272 *****************************************************************************/
274 class MOZ_RAII AutoDisableUndo final {
275 public:
276 explicit AutoDisableUndo(TextEditor* aTextEditor)
277 : mTextEditor(aTextEditor), mNumberOfMaximumTransactions(0) {
278 MOZ_ASSERT(mTextEditor);
280 mNumberOfMaximumTransactions =
281 mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
282 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
283 NS_WARNING_ASSERTION(disabledUndoRedo,
284 "Failed to disable undo/redo transactions");
287 ~AutoDisableUndo() {
288 // Don't change enable/disable of undo/redo if it's enabled after
289 // it's disabled by the constructor because we shouldn't change
290 // the maximum undo/redo count to the old value.
291 if (mTextEditor->IsUndoRedoEnabled()) {
292 return;
294 // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
295 // than 0. Only when it's 0, it was disabled.
296 if (mNumberOfMaximumTransactions) {
297 DebugOnly<bool> enabledUndoRedo =
298 mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
299 NS_WARNING_ASSERTION(enabledUndoRedo,
300 "Failed to enable undo/redo transactions");
301 } else {
302 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
303 NS_WARNING_ASSERTION(disabledUndoRedo,
304 "Failed to disable undo/redo transactions");
308 private:
309 TextEditor* mTextEditor;
310 int32_t mNumberOfMaximumTransactions;
313 static bool SuppressEventHandlers(nsPresContext* aPresContext) {
314 bool suppressHandlers = false;
316 if (aPresContext) {
317 // Right now we only suppress event handlers and controller manipulation
318 // when in a print preview or print context!
320 // In the current implementation, we only paginate when
321 // printing or in print preview.
323 suppressHandlers = aPresContext->IsPaginated();
326 return suppressHandlers;
329 /*****************************************************************************
330 * mozilla::TextInputSelectionController
331 *****************************************************************************/
333 class TextInputSelectionController final : public nsSupportsWeakReference,
334 public nsISelectionController {
335 ~TextInputSelectionController() = default;
337 public:
338 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
339 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputSelectionController,
340 nsISelectionController)
342 TextInputSelectionController(PresShell* aPresShell, nsIContent* aLimiter);
344 void SetScrollableFrame(nsIScrollableFrame* aScrollableFrame);
345 nsFrameSelection* GetConstFrameSelection() { return mFrameSelection; }
346 // Will return null if !mFrameSelection.
347 Selection* GetSelection(SelectionType aSelectionType);
349 // NSISELECTIONCONTROLLER INTERFACES
350 NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
351 NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
352 NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
353 NS_IMETHOD GetSelectionFlags(int16_t* aOutEnable) override;
354 NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
355 Selection** aSelection) override;
356 Selection* GetSelection(RawSelectionType aRawSelectionType) override;
357 NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
358 int16_t aRegion, int16_t aFlags) override;
359 NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
360 nsresult RepaintSelection(nsPresContext* aPresContext,
361 SelectionType aSelectionType);
362 NS_IMETHOD SetCaretEnabled(bool enabled) override;
363 NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
364 NS_IMETHOD GetCaretEnabled(bool* _retval) override;
365 NS_IMETHOD GetCaretVisible(bool* _retval) override;
366 NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
367 NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount,
368 bool aExtend) override;
369 NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
370 NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
371 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD LineMove(bool aForward,
372 bool aExtend) override;
373 NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
374 MOZ_CAN_RUN_SCRIPT
375 NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
376 NS_IMETHOD CompleteScroll(bool aForward) override;
377 MOZ_CAN_RUN_SCRIPT NS_IMETHOD CompleteMove(bool aForward,
378 bool aExtend) override;
379 NS_IMETHOD ScrollPage(bool aForward) override;
380 NS_IMETHOD ScrollLine(bool aForward) override;
381 NS_IMETHOD ScrollCharacter(bool aRight) override;
382 void SelectionWillTakeFocus() override;
383 void SelectionWillLoseFocus() override;
385 private:
386 RefPtr<nsFrameSelection> mFrameSelection;
387 nsIScrollableFrame* mScrollFrame;
388 nsWeakPtr mPresShellWeak;
391 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputSelectionController)
392 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputSelectionController)
393 NS_INTERFACE_TABLE_HEAD(TextInputSelectionController)
394 NS_INTERFACE_TABLE(TextInputSelectionController, nsISelectionController,
395 nsISelectionDisplay, nsISupportsWeakReference)
396 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TextInputSelectionController)
397 NS_INTERFACE_MAP_END
399 NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController, mFrameSelection)
401 TextInputSelectionController::TextInputSelectionController(
402 PresShell* aPresShell, nsIContent* aLimiter)
403 : mScrollFrame(nullptr) {
404 if (aPresShell) {
405 bool accessibleCaretEnabled =
406 PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
407 mFrameSelection =
408 new nsFrameSelection(aPresShell, aLimiter, accessibleCaretEnabled);
409 mPresShellWeak = do_GetWeakReference(aPresShell);
413 void TextInputSelectionController::SetScrollableFrame(
414 nsIScrollableFrame* aScrollableFrame) {
415 mScrollFrame = aScrollableFrame;
416 if (!mScrollFrame && mFrameSelection) {
417 mFrameSelection->DisconnectFromPresShell();
418 mFrameSelection = nullptr;
422 Selection* TextInputSelectionController::GetSelection(
423 SelectionType aSelectionType) {
424 if (!mFrameSelection) {
425 return nullptr;
428 return mFrameSelection->GetSelection(aSelectionType);
431 NS_IMETHODIMP
432 TextInputSelectionController::SetDisplaySelection(int16_t aToggle) {
433 if (!mFrameSelection) {
434 return NS_ERROR_NULL_POINTER;
436 mFrameSelection->SetDisplaySelection(aToggle);
437 return NS_OK;
440 NS_IMETHODIMP
441 TextInputSelectionController::GetDisplaySelection(int16_t* aToggle) {
442 if (!mFrameSelection) {
443 return NS_ERROR_NULL_POINTER;
445 *aToggle = mFrameSelection->GetDisplaySelection();
446 return NS_OK;
449 NS_IMETHODIMP
450 TextInputSelectionController::SetSelectionFlags(int16_t aToggle) {
451 return NS_OK; // stub this out. not used in input
454 NS_IMETHODIMP
455 TextInputSelectionController::GetSelectionFlags(int16_t* aOutEnable) {
456 *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
457 return NS_OK;
460 NS_IMETHODIMP
461 TextInputSelectionController::GetSelectionFromScript(
462 RawSelectionType aRawSelectionType, Selection** aSelection) {
463 if (!mFrameSelection) {
464 return NS_ERROR_NULL_POINTER;
467 *aSelection =
468 mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
470 // GetSelection() fails only when aRawSelectionType is invalid value.
471 if (!(*aSelection)) {
472 return NS_ERROR_INVALID_ARG;
475 NS_ADDREF(*aSelection);
476 return NS_OK;
479 Selection* TextInputSelectionController::GetSelection(
480 RawSelectionType aRawSelectionType) {
481 return GetSelection(ToSelectionType(aRawSelectionType));
484 NS_IMETHODIMP
485 TextInputSelectionController::ScrollSelectionIntoView(
486 RawSelectionType aRawSelectionType, int16_t aRegion, int16_t aFlags) {
487 if (!mFrameSelection) {
488 return NS_ERROR_NULL_POINTER;
490 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
491 return frameSelection->ScrollSelectionIntoView(
492 ToSelectionType(aRawSelectionType), aRegion, aFlags);
495 NS_IMETHODIMP
496 TextInputSelectionController::RepaintSelection(
497 RawSelectionType aRawSelectionType) {
498 if (!mFrameSelection) {
499 return NS_ERROR_NULL_POINTER;
501 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
502 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
505 nsresult TextInputSelectionController::RepaintSelection(
506 nsPresContext* aPresContext, SelectionType aSelectionType) {
507 if (!mFrameSelection) {
508 return NS_ERROR_NULL_POINTER;
510 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
511 return frameSelection->RepaintSelection(aSelectionType);
514 NS_IMETHODIMP
515 TextInputSelectionController::SetCaretEnabled(bool enabled) {
516 if (!mPresShellWeak) {
517 return NS_ERROR_NOT_INITIALIZED;
519 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak);
520 if (!presShell) {
521 return NS_ERROR_FAILURE;
524 // tell the pres shell to enable the caret, rather than settings its
525 // visibility directly. this way the presShell's idea of caret visibility is
526 // maintained.
527 presShell->SetCaretEnabled(enabled);
529 return NS_OK;
532 NS_IMETHODIMP
533 TextInputSelectionController::SetCaretReadOnly(bool aReadOnly) {
534 if (!mPresShellWeak) {
535 return NS_ERROR_NOT_INITIALIZED;
537 nsresult rv;
538 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
539 if (!presShell) {
540 return NS_ERROR_FAILURE;
542 RefPtr<nsCaret> caret = presShell->GetCaret();
543 if (!caret) {
544 return NS_ERROR_FAILURE;
547 if (!mFrameSelection) {
548 return NS_ERROR_FAILURE;
551 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
552 if (selection) {
553 caret->SetCaretReadOnly(aReadOnly);
555 return NS_OK;
558 NS_IMETHODIMP
559 TextInputSelectionController::GetCaretEnabled(bool* _retval) {
560 return GetCaretVisible(_retval);
563 NS_IMETHODIMP
564 TextInputSelectionController::GetCaretVisible(bool* _retval) {
565 if (!mPresShellWeak) {
566 return NS_ERROR_NOT_INITIALIZED;
568 nsresult rv;
569 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
570 if (!presShell) {
571 return NS_ERROR_FAILURE;
573 RefPtr<nsCaret> caret = presShell->GetCaret();
574 if (!caret) {
575 return NS_ERROR_FAILURE;
577 *_retval = caret->IsVisible();
578 return NS_OK;
581 NS_IMETHODIMP
582 TextInputSelectionController::SetCaretVisibilityDuringSelection(
583 bool aVisibility) {
584 if (!mPresShellWeak) {
585 return NS_ERROR_NOT_INITIALIZED;
587 nsresult rv;
588 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
589 if (!presShell) {
590 return NS_ERROR_FAILURE;
592 RefPtr<nsCaret> caret = presShell->GetCaret();
593 if (!caret) {
594 return NS_ERROR_FAILURE;
596 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
597 if (selection) {
598 caret->SetVisibilityDuringSelection(aVisibility);
600 return NS_OK;
603 NS_IMETHODIMP
604 TextInputSelectionController::PhysicalMove(int16_t aDirection, int16_t aAmount,
605 bool aExtend) {
606 if (!mFrameSelection) {
607 return NS_ERROR_NULL_POINTER;
609 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
610 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
613 NS_IMETHODIMP
614 TextInputSelectionController::CharacterMove(bool aForward, bool aExtend) {
615 if (!mFrameSelection) {
616 return NS_ERROR_NULL_POINTER;
618 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
619 return frameSelection->CharacterMove(aForward, aExtend);
622 NS_IMETHODIMP
623 TextInputSelectionController::WordMove(bool aForward, bool aExtend) {
624 if (!mFrameSelection) {
625 return NS_ERROR_NULL_POINTER;
627 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
628 return frameSelection->WordMove(aForward, aExtend);
631 NS_IMETHODIMP
632 TextInputSelectionController::LineMove(bool aForward, bool aExtend) {
633 if (!mFrameSelection) {
634 return NS_ERROR_NULL_POINTER;
636 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
637 nsresult result = frameSelection->LineMove(aForward, aExtend);
638 if (NS_FAILED(result)) {
639 result = CompleteMove(aForward, aExtend);
641 return result;
644 NS_IMETHODIMP
645 TextInputSelectionController::IntraLineMove(bool aForward, bool aExtend) {
646 if (!mFrameSelection) {
647 return NS_ERROR_NULL_POINTER;
649 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
650 return frameSelection->IntraLineMove(aForward, aExtend);
653 NS_IMETHODIMP
654 TextInputSelectionController::PageMove(bool aForward, bool aExtend) {
655 // expected behavior for PageMove is to scroll AND move the caret
656 // and to remain relative position of the caret in view. see Bug 4302.
657 if (mScrollFrame) {
658 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
659 nsIFrame* scrollFrame = do_QueryFrame(mScrollFrame);
660 // We won't scroll parent scrollable element of mScrollFrame. Therefore,
661 // this may be handled when mScrollFrame is completely outside of the view.
662 // In such case, user may be confused since they might have wanted to
663 // scroll a parent scrollable element. For making clearer which element
664 // handles PageDown/PageUp, we should move selection into view even if
665 // selection is not changed.
666 return frameSelection->PageMove(aForward, aExtend, scrollFrame,
667 nsFrameSelection::SelectionIntoView::Yes);
669 // Similarly, if there is no scrollable frame, we should move the editor
670 // frame into the view for making it clearer which element handles
671 // PageDown/PageUp.
672 return ScrollSelectionIntoView(
673 nsISelectionController::SELECTION_NORMAL,
674 nsISelectionController::SELECTION_FOCUS_REGION,
675 nsISelectionController::SCROLL_SYNCHRONOUS |
676 nsISelectionController::SCROLL_FOR_CARET_MOVE);
679 NS_IMETHODIMP
680 TextInputSelectionController::CompleteScroll(bool aForward) {
681 if (!mScrollFrame) {
682 return NS_ERROR_NOT_INITIALIZED;
685 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE,
686 ScrollMode::Instant);
687 return NS_OK;
690 NS_IMETHODIMP
691 TextInputSelectionController::CompleteMove(bool aForward, bool aExtend) {
692 if (NS_WARN_IF(!mFrameSelection)) {
693 return NS_ERROR_NULL_POINTER;
695 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
697 // grab the parent / root DIV for this text widget
698 nsIContent* parentDIV = frameSelection->GetLimiter();
699 if (!parentDIV) {
700 return NS_ERROR_UNEXPECTED;
703 // make the caret be either at the very beginning (0) or the very end
704 int32_t offset = 0;
705 CaretAssociationHint hint = CaretAssociationHint::Before;
706 if (aForward) {
707 offset = parentDIV->GetChildCount();
709 // Prevent the caret from being placed after the last
710 // BR node in the content tree!
712 if (offset > 0) {
713 nsIContent* child = parentDIV->GetLastChild();
715 if (child->IsHTMLElement(nsGkAtoms::br)) {
716 --offset;
717 hint = CaretAssociationHint::After; // for Bug 106855
722 const RefPtr<nsIContent> pinnedParentDIV{parentDIV};
723 const nsFrameSelection::FocusMode focusMode =
724 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
725 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
726 frameSelection->HandleClick(pinnedParentDIV, offset, offset, focusMode, hint);
728 // if we got this far, attempt to scroll no matter what the above result is
729 return CompleteScroll(aForward);
732 NS_IMETHODIMP
733 TextInputSelectionController::ScrollPage(bool aForward) {
734 if (!mScrollFrame) {
735 return NS_ERROR_NOT_INITIALIZED;
738 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
739 ScrollMode::Smooth);
740 return NS_OK;
743 NS_IMETHODIMP
744 TextInputSelectionController::ScrollLine(bool aForward) {
745 if (!mScrollFrame) {
746 return NS_ERROR_NOT_INITIALIZED;
749 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::LINES,
750 ScrollMode::Smooth);
751 return NS_OK;
754 NS_IMETHODIMP
755 TextInputSelectionController::ScrollCharacter(bool aRight) {
756 if (!mScrollFrame) {
757 return NS_ERROR_NOT_INITIALIZED;
760 mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), ScrollUnit::LINES,
761 ScrollMode::Smooth);
762 return NS_OK;
765 void TextInputSelectionController::SelectionWillTakeFocus() {
766 if (mFrameSelection) {
767 if (PresShell* shell = mFrameSelection->GetPresShell()) {
768 shell->FrameSelectionWillTakeFocus(*mFrameSelection);
773 void TextInputSelectionController::SelectionWillLoseFocus() {
774 if (mFrameSelection) {
775 if (PresShell* shell = mFrameSelection->GetPresShell()) {
776 shell->FrameSelectionWillLoseFocus(*mFrameSelection);
781 /*****************************************************************************
782 * mozilla::TextInputListener
783 *****************************************************************************/
785 TextInputListener::TextInputListener(TextControlElement* aTxtCtrlElement)
786 : mFrame(nullptr),
787 mTxtCtrlElement(aTxtCtrlElement),
788 mTextControlState(aTxtCtrlElement ? aTxtCtrlElement->GetTextControlState()
789 : nullptr),
790 mSelectionWasCollapsed(true),
791 mHadUndoItems(false),
792 mHadRedoItems(false),
793 mSettingValue(false),
794 mSetValueChanged(true),
795 mListeningToSelectionChange(false) {}
797 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
798 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
800 NS_INTERFACE_MAP_BEGIN(TextInputListener)
801 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
802 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
803 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
804 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
805 NS_INTERFACE_MAP_END
807 NS_IMPL_CYCLE_COLLECTION_CLASS(TextInputListener)
808 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TextInputListener)
809 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
810 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
811 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TextInputListener)
812 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
814 void TextInputListener::OnSelectionChange(Selection& aSelection,
815 int16_t aReason) {
816 if (!mListeningToSelectionChange) {
817 return;
820 AutoWeakFrame weakFrame = mFrame;
822 // Fire the select event
823 // The specs don't exactly say when we should fire the select event.
824 // IE: Whenever you add/remove a character to/from the selection. Also
825 // each time for select all. Also if you get to the end of the text
826 // field you will get new event for each keypress or a continuous
827 // stream of events if you use the mouse. IE will fire select event
828 // when the selection collapses to nothing if you are holding down
829 // the shift or mouse button.
830 // Mozilla: If we have non-empty selection we will fire a new event for each
831 // keypress (or mouseup) if the selection changed. Mozilla will also
832 // create the event each time select all is called, even if
833 // everything was previously selected, because technically select all
834 // will first collapse and then extend. Mozilla will never create an
835 // event if the selection collapses to nothing.
836 // FYI: If you want to skip dispatching eFormSelect event and if there are no
837 // event listeners, you can refer
838 // nsPIDOMWindow::HasFormSelectEventListeners(), but be careful about
839 // some C++ event handlers, e.g., HTMLTextAreaElement::PostHandleEvent().
840 bool collapsed = aSelection.IsCollapsed();
841 if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
842 nsISelectionListener::KEYPRESS_REASON |
843 nsISelectionListener::SELECTALL_REASON))) {
844 if (nsCOMPtr<nsIContent> content = mFrame->GetContent()) {
845 if (nsCOMPtr<Document> doc = content->GetComposedDoc()) {
846 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
847 nsEventStatus status = nsEventStatus_eIgnore;
848 WidgetEvent event(true, eFormSelect);
850 presShell->HandleEventWithTarget(&event, mFrame, content, &status);
856 // if the collapsed state did not change, don't fire notifications
857 if (collapsed == mSelectionWasCollapsed) {
858 return;
861 mSelectionWasCollapsed = collapsed;
863 if (!weakFrame.IsAlive() || !mFrame ||
864 !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
865 return;
868 UpdateTextInputCommands(u"select"_ns);
871 MOZ_CAN_RUN_SCRIPT
872 static void DoCommandCallback(Command aCommand, void* aData) {
873 nsTextControlFrame* frame = static_cast<nsTextControlFrame*>(aData);
874 nsIContent* content = frame->GetContent();
876 nsCOMPtr<nsIControllers> controllers;
877 HTMLInputElement* input = HTMLInputElement::FromNode(content);
878 if (input) {
879 input->GetControllers(getter_AddRefs(controllers));
880 } else {
881 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(content);
883 if (textArea) {
884 textArea->GetControllers(getter_AddRefs(controllers));
888 if (!controllers) {
889 NS_WARNING("Could not get controllers");
890 return;
893 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
895 nsCOMPtr<nsIController> controller;
896 controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
897 if (!controller) {
898 return;
901 bool commandEnabled;
902 if (NS_WARN_IF(NS_FAILED(
903 controller->IsCommandEnabled(commandStr, &commandEnabled)))) {
904 return;
906 if (commandEnabled) {
907 controller->DoCommand(commandStr);
911 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
912 TextInputListener::HandleEvent(Event* aEvent) {
913 if (aEvent->DefaultPrevented()) {
914 return NS_OK;
917 if (!aEvent->IsTrusted()) {
918 return NS_OK;
921 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
922 if (!keyEvent) {
923 return NS_ERROR_UNEXPECTED;
926 WidgetKeyboardEvent* widgetKeyEvent =
927 aEvent->WidgetEventPtr()->AsKeyboardEvent();
928 if (!widgetKeyEvent) {
929 return NS_ERROR_UNEXPECTED;
933 auto* input = HTMLInputElement::FromNode(mTxtCtrlElement);
934 if (input && input->StepsInputValue(*widgetKeyEvent)) {
935 // As an special case, don't handle key events that would step the value
936 // of our <input type=number>.
937 return NS_OK;
941 auto ExecuteOurShortcutKeys = [&](TextControlElement& aTextControlElement)
942 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
943 KeyEventHandler* keyHandlers = ShortcutKeys::GetHandlers(
944 aTextControlElement.IsTextArea() ? HandlerType::eTextArea
945 : HandlerType::eInput);
947 RefPtr<nsAtom> eventTypeAtom =
948 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent);
949 for (KeyEventHandler* handler = keyHandlers; handler;
950 handler = handler->GetNextHandler()) {
951 if (!handler->EventTypeEquals(eventTypeAtom)) {
952 continue;
955 if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) {
956 continue;
959 // XXX Do we execute only one handler even if the handler neither stops
960 // propagation nor prevents default of the event?
961 nsresult rv = handler->ExecuteHandler(&aTextControlElement, aEvent);
962 if (NS_SUCCEEDED(rv)) {
963 return true;
966 return false;
969 auto ExecuteNativeKeyBindings =
970 [&](TextControlElement& aTextControlElement)
971 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
972 if (widgetKeyEvent->mMessage != eKeyPress) {
973 return false;
976 NativeKeyBindingsType nativeKeyBindingsType =
977 aTextControlElement.IsTextArea()
978 ? NativeKeyBindingsType::MultiLineEditor
979 : NativeKeyBindingsType::SingleLineEditor;
981 nsIWidget* widget = widgetKeyEvent->mWidget;
982 // If the event is created by chrome script, the widget is nullptr.
983 if (MOZ_UNLIKELY(!widget)) {
984 widget = mFrame->GetNearestWidget();
985 if (MOZ_UNLIKELY(NS_WARN_IF(!widget))) {
986 return false;
990 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
991 // If the event is created by chrome script, it is nullptr but we need to
992 // execute native key bindings. Therefore, we need to set widget to
993 // WidgetEvent::mWidget temporarily.
994 AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(widgetKeyEvent->mWidget);
995 widgetKeyEvent->mWidget = widget;
996 if (widgetKeyEvent->ExecuteEditCommands(nativeKeyBindingsType,
997 DoCommandCallback, mFrame)) {
998 aEvent->PreventDefault();
999 return true;
1001 return false;
1004 OwningNonNull<TextControlElement> textControlElement(*mTxtCtrlElement);
1005 if (StaticPrefs::
1006 ui_key_textcontrol_prefer_native_key_bindings_over_builtin_shortcut_key_definitions()) {
1007 if (!ExecuteNativeKeyBindings(textControlElement)) {
1008 ExecuteOurShortcutKeys(textControlElement);
1010 } else {
1011 if (!ExecuteOurShortcutKeys(textControlElement)) {
1012 ExecuteNativeKeyBindings(textControlElement);
1015 return NS_OK;
1018 nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
1019 if (mFrame) {
1020 // XXX Do we still need this or can we just remove the mFrame and
1021 // frame.IsAlive() conditions below?
1022 AutoWeakFrame weakFrame = mFrame;
1024 // Update the undo / redo menus
1026 size_t numUndoItems = aTextEditor.NumberOfUndoItems();
1027 size_t numRedoItems = aTextEditor.NumberOfRedoItems();
1028 if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
1029 (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
1030 // Modify the menu if undo or redo items are different
1031 UpdateTextInputCommands(u"undo"_ns);
1033 mHadUndoItems = numUndoItems != 0;
1034 mHadRedoItems = numRedoItems != 0;
1037 if (weakFrame.IsAlive()) {
1038 HandleValueChanged(aTextEditor);
1042 return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
1045 void TextInputListener::HandleValueChanged(TextEditor& aTextEditor) {
1046 // Make sure we know we were changed (do NOT set this to false if there are
1047 // no undo items; JS could change the value and we'd still need to save it)
1048 if (mSetValueChanged) {
1049 mTxtCtrlElement->SetValueChanged(true);
1052 if (!mSettingValue) {
1053 // NOTE(emilio): execCommand might get here even though it might not be a
1054 // "proper" user-interactive change. Might be worth reconsidering which
1055 // ValueChangeKind are we passing down.
1056 mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction,
1057 aTextEditor.IsEmpty(), nullptr);
1058 if (mTextControlState) {
1059 mTextControlState->ClearLastInteractiveValue();
1064 nsresult TextInputListener::UpdateTextInputCommands(
1065 const nsAString& aCommandsToUpdate) {
1066 nsIContent* content = mFrame->GetContent();
1067 if (NS_WARN_IF(!content)) {
1068 return NS_ERROR_FAILURE;
1070 nsCOMPtr<Document> doc = content->GetComposedDoc();
1071 if (NS_WARN_IF(!doc)) {
1072 return NS_ERROR_FAILURE;
1074 nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1075 if (NS_WARN_IF(!domWindow)) {
1076 return NS_ERROR_FAILURE;
1078 domWindow->UpdateCommands(aCommandsToUpdate);
1079 return NS_OK;
1082 /*****************************************************************************
1083 * mozilla::AutoTextControlHandlingState
1085 * This class is temporarily created in the stack and can manage nested
1086 * handling state of TextControlState. While this instance exists, lifetime of
1087 * TextControlState which created the instance is guaranteed. In other words,
1088 * you can use this class as "kungFuDeathGrip" for TextControlState.
1089 *****************************************************************************/
1091 enum class TextControlAction {
1092 CommitComposition,
1093 Destructor,
1094 PrepareEditor,
1095 SetRangeText,
1096 SetSelectionRange,
1097 SetValue,
1098 UnbindFromFrame,
1099 Unlink,
1102 class MOZ_STACK_CLASS AutoTextControlHandlingState {
1103 public:
1104 AutoTextControlHandlingState() = delete;
1105 explicit AutoTextControlHandlingState(const AutoTextControlHandlingState&) =
1106 delete;
1107 AutoTextControlHandlingState(AutoTextControlHandlingState&&) = delete;
1108 void operator=(AutoTextControlHandlingState&) = delete;
1109 void operator=(const AutoTextControlHandlingState&) = delete;
1112 * Generic constructor. If TextControlAction does not require additional
1113 * data, must use this constructor.
1115 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1116 TextControlState& aTextControlState, TextControlAction aTextControlAction)
1117 : mParent(aTextControlState.mHandlingState),
1118 mTextControlState(aTextControlState),
1119 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1120 mTextInputListener(aTextControlState.mTextListener),
1121 mTextControlAction(aTextControlAction) {
1122 MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue,
1123 "Use specific constructor");
1124 MOZ_DIAGNOSTIC_ASSERT_IF(
1125 !aTextControlState.mTextListener,
1126 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1127 mTextControlState.mHandlingState = this;
1128 if (Is(TextControlAction::CommitComposition)) {
1129 MOZ_ASSERT(mParent);
1130 MOZ_ASSERT(mParent->Is(TextControlAction::SetValue));
1131 // If we're trying to commit composition before handling SetValue,
1132 // the parent old values will be outdated so that we need to clear
1133 // them.
1134 mParent->InvalidateOldValue();
1139 * TextControlAction::SetValue specific constructor. Current setting value
1140 * must be specified and the creator should check whether we succeeded to
1141 * allocate memory for line breaker conversion.
1143 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1144 TextControlState& aTextControlState, TextControlAction aTextControlAction,
1145 const nsAString& aSettingValue, const nsAString* aOldValue,
1146 const ValueSetterOptions& aOptions, ErrorResult& aRv)
1147 : mParent(aTextControlState.mHandlingState),
1148 mTextControlState(aTextControlState),
1149 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1150 mTextInputListener(aTextControlState.mTextListener),
1151 mSettingValue(aSettingValue),
1152 mOldValue(aOldValue),
1153 mValueSetterOptions(aOptions),
1154 mTextControlAction(aTextControlAction) {
1155 MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue,
1156 "Use generic constructor");
1157 MOZ_DIAGNOSTIC_ASSERT_IF(
1158 !aTextControlState.mTextListener,
1159 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1160 mTextControlState.mHandlingState = this;
1161 if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue, fallible)) {
1162 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1163 return;
1165 // Update all setting value's new value because older value shouldn't
1166 // overwrite newer value.
1167 if (mParent) {
1168 // If SetValue is nested, parents cannot trust their old value anymore.
1169 // So, we need to clear them.
1170 mParent->UpdateSettingValueAndInvalidateOldValue(mSettingValue);
1174 MOZ_CAN_RUN_SCRIPT ~AutoTextControlHandlingState() {
1175 mTextControlState.mHandlingState = mParent;
1176 if (!mParent && mTextControlStateDestroyed) {
1177 mTextControlState.DeleteOrCacheForReuse();
1179 if (!mTextControlStateDestroyed && mPrepareEditorLater) {
1180 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
1181 MOZ_ASSERT(Is(TextControlAction::SetValue));
1182 mTextControlState.PrepareEditor(&mSettingValue);
1186 void OnDestroyTextControlState() {
1187 if (IsHandling(TextControlAction::Destructor)) {
1188 // Do nothing since mTextContrlState.DeleteOrCacheForReuse() has
1189 // already been called.
1190 return;
1192 mTextControlStateDestroyed = true;
1193 if (mParent) {
1194 mParent->OnDestroyTextControlState();
1198 void PrepareEditorLater() {
1199 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1200 MOZ_ASSERT(!IsHandling(TextControlAction::PrepareEditor));
1201 // Look for the top most SetValue.
1202 AutoTextControlHandlingState* settingValue = nullptr;
1203 for (AutoTextControlHandlingState* handlingSomething = this;
1204 handlingSomething; handlingSomething = handlingSomething->mParent) {
1205 if (handlingSomething->Is(TextControlAction::SetValue)) {
1206 settingValue = handlingSomething;
1209 settingValue->mPrepareEditorLater = true;
1213 * WillSetValueWithTextEditor() is called when TextControlState sets
1214 * value with its mTextEditor.
1216 void WillSetValueWithTextEditor() {
1217 MOZ_ASSERT(Is(TextControlAction::SetValue));
1218 MOZ_ASSERT(mTextControlState.mBoundFrame);
1219 mTextControlFrame = mTextControlState.mBoundFrame;
1220 // If we'reemulating user input, we don't need to manage mTextInputListener
1221 // by ourselves since everything should be handled by TextEditor as normal
1222 // user input.
1223 if (mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1224 return;
1226 // Otherwise, if we're setting the value programatically, we need to manage
1227 // mTextInputListener by ourselves since TextEditor users special path
1228 // for the performance.
1229 mTextInputListener->SettingValue(true);
1230 mTextInputListener->SetValueChanged(
1231 mValueSetterOptions.contains(ValueSetterOption::SetValueChanged));
1232 mEditActionHandled = false;
1233 // Even if falling back to `TextControlState::SetValueWithoutTextEditor()`
1234 // due to editor destruction, it shouldn't dispatch "beforeinput" event
1235 // anymore. Therefore, we should mark that we've already dispatched
1236 // "beforeinput" event.
1237 WillDispatchBeforeInputEvent();
1241 * WillDispatchBeforeInputEvent() is called immediately before dispatching
1242 * "beforeinput" event in `TextControlState`.
1244 void WillDispatchBeforeInputEvent() {
1245 mBeforeInputEventHasBeenDispatched = true;
1249 * OnEditActionHandled() is called when the TextEditor handles something
1250 * and immediately before dispatching "input" event.
1252 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled() {
1253 MOZ_ASSERT(!mEditActionHandled);
1254 mEditActionHandled = true;
1255 if (!Is(TextControlAction::SetValue)) {
1256 return NS_OK;
1258 if (!mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1259 mTextInputListener->SetValueChanged(true);
1260 mTextInputListener->SettingValue(
1261 mParent && mParent->IsHandling(TextControlAction::SetValue));
1263 if (!IsOriginalTextControlFrameAlive()) {
1264 return SetValueWithoutTextEditorAgain() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
1266 // The new value never includes line breaks caused by hard-wrap.
1267 // So, mCachedValue can always cache the new value.
1268 nsITextControlFrame* textControlFrame =
1269 do_QueryFrame(mTextControlFrame.GetFrame());
1270 return static_cast<nsTextControlFrame*>(textControlFrame)
1271 ->CacheValue(mSettingValue, fallible)
1272 ? NS_OK
1273 : NS_ERROR_OUT_OF_MEMORY;
1277 * SetValueWithoutTextEditorAgain() should be called if the frame for
1278 * mTextControlState was destroyed during setting value.
1280 [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditorAgain() {
1281 MOZ_ASSERT(!IsOriginalTextControlFrameAlive());
1282 // If the frame was destroyed because of a flush somewhere inside
1283 // TextEditor, mBoundFrame here will be nullptr. But it's also
1284 // possible for the frame to go away because of another reason (such
1285 // as deleting the existing selection -- see bug 574558), in which
1286 // case we don't need to reset the value here.
1287 if (mTextControlState.mBoundFrame) {
1288 return true;
1290 // XXX It's odd to drop flags except
1291 // ValueSetterOption::SetValueChanged.
1292 // Probably, this intended to drop ValueSetterOption::BySetUserInputAPI
1293 // and ValueSetterOption::ByContentAPI, but other flags are added later.
1294 ErrorResult error;
1295 AutoTextControlHandlingState handlingSetValueWithoutEditor(
1296 mTextControlState, TextControlAction::SetValue, mSettingValue,
1297 mOldValue, mValueSetterOptions & ValueSetterOption::SetValueChanged,
1298 error);
1299 if (error.Failed()) {
1300 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
1301 error.SuppressException();
1302 return false;
1304 return mTextControlState.SetValueWithoutTextEditor(
1305 handlingSetValueWithoutEditor);
1308 bool IsTextControlStateDestroyed() const {
1309 return mTextControlStateDestroyed;
1311 bool IsOriginalTextControlFrameAlive() const {
1312 return const_cast<AutoTextControlHandlingState*>(this)
1313 ->mTextControlFrame.IsAlive();
1315 bool HasEditActionHandled() const { return mEditActionHandled; }
1316 bool HasBeforeInputEventDispatched() const {
1317 return mBeforeInputEventHasBeenDispatched;
1319 bool Is(TextControlAction aTextControlAction) const {
1320 return mTextControlAction == aTextControlAction;
1322 bool IsHandling(TextControlAction aTextControlAction) const {
1323 if (mTextControlAction == aTextControlAction) {
1324 return true;
1326 return mParent && mParent->IsHandling(aTextControlAction);
1328 TextControlElement* GetTextControlElement() const { return mTextCtrlElement; }
1329 TextInputListener* GetTextInputListener() const { return mTextInputListener; }
1330 const ValueSetterOptions& ValueSetterOptionsRef() const {
1331 MOZ_ASSERT(Is(TextControlAction::SetValue));
1332 return mValueSetterOptions;
1334 const nsAString* GetOldValue() const {
1335 MOZ_ASSERT(Is(TextControlAction::SetValue));
1336 return mOldValue;
1338 const nsString& GetSettingValue() const {
1339 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1340 if (mTextControlAction == TextControlAction::SetValue) {
1341 return mSettingValue;
1343 return mParent->GetSettingValue();
1346 private:
1347 void UpdateSettingValueAndInvalidateOldValue(const nsString& aSettingValue) {
1348 if (mTextControlAction == TextControlAction::SetValue) {
1349 mSettingValue = aSettingValue;
1351 mOldValue = nullptr;
1352 if (mParent) {
1353 mParent->UpdateSettingValueAndInvalidateOldValue(aSettingValue);
1356 void InvalidateOldValue() {
1357 mOldValue = nullptr;
1358 if (mParent) {
1359 mParent->InvalidateOldValue();
1363 AutoTextControlHandlingState* const mParent;
1364 TextControlState& mTextControlState;
1365 // mTextControlFrame should be set immediately before calling methods
1366 // which may destroy the frame. Then, you can check whether the frame
1367 // was destroyed/replaced.
1368 AutoWeakFrame mTextControlFrame;
1369 // mTextCtrlElement grabs TextControlState::mTextCtrlElement since
1370 // if the text control element releases mTextControlState, only this
1371 // can guarantee the instance of the text control element.
1372 RefPtr<TextControlElement> const mTextCtrlElement;
1373 // mTextInputListener grabs TextControlState::mTextListener because if
1374 // TextControlState is unbind from the frame, it's released.
1375 RefPtr<TextInputListener> const mTextInputListener;
1376 nsAutoString mSettingValue;
1377 const nsAString* mOldValue = nullptr;
1378 ValueSetterOptions mValueSetterOptions;
1379 TextControlAction const mTextControlAction;
1380 bool mTextControlStateDestroyed = false;
1381 bool mEditActionHandled = false;
1382 bool mPrepareEditorLater = false;
1383 bool mBeforeInputEventHasBeenDispatched = false;
1386 /*****************************************************************************
1387 * mozilla::TextControlState
1388 *****************************************************************************/
1391 * For avoiding allocation cost of the instance, we should reuse instances
1392 * as far as possible.
1394 * FYI: `25` is just a magic number considered without enough investigation,
1395 * but at least, this value must not make damage for footprint.
1396 * Feel free to change it if you find better number.
1398 static constexpr size_t kMaxCountOfCacheToReuse = 25;
1399 static AutoTArray<void*, kMaxCountOfCacheToReuse>* sReleasedInstances = nullptr;
1400 static bool sHasShutDown = false;
1402 TextControlState::TextControlState(TextControlElement* aOwningElement)
1403 : mTextCtrlElement(aOwningElement),
1404 mEverInited(false),
1405 mEditorInitialized(false),
1406 mValueTransferInProgress(false),
1407 mSelectionCached(true)
1408 // When adding more member variable initializations here, add the same
1409 // also to ::Construct.
1411 MOZ_COUNT_CTOR(TextControlState);
1412 static_assert(sizeof(*this) <= 128,
1413 "Please keep small TextControlState as far as possible");
1416 TextControlState* TextControlState::Construct(
1417 TextControlElement* aOwningElement) {
1418 void* mem;
1419 if (sReleasedInstances && !sReleasedInstances->IsEmpty()) {
1420 mem = sReleasedInstances->PopLastElement();
1421 } else {
1422 mem = moz_xmalloc(sizeof(TextControlState));
1425 return new (mem) TextControlState(aOwningElement);
1428 TextControlState::~TextControlState() {
1429 MOZ_ASSERT(!mHandlingState);
1430 MOZ_COUNT_DTOR(TextControlState);
1431 AutoTextControlHandlingState handlingDesctructor(
1432 *this, TextControlAction::Destructor);
1433 Clear();
1436 void TextControlState::Shutdown() {
1437 sHasShutDown = true;
1438 if (sReleasedInstances) {
1439 for (void* mem : *sReleasedInstances) {
1440 free(mem);
1442 delete sReleasedInstances;
1446 void TextControlState::Destroy() {
1447 // If we're handling something, we should be deleted later.
1448 if (mHandlingState) {
1449 mHandlingState->OnDestroyTextControlState();
1450 return;
1452 DeleteOrCacheForReuse();
1453 // Note that this instance may have already been deleted here. Don't touch
1454 // any members.
1457 void TextControlState::DeleteOrCacheForReuse() {
1458 MOZ_ASSERT(!IsBusy());
1460 void* mem = this;
1461 this->~TextControlState();
1463 // If we can cache this instance, we should do it instead of deleting it.
1464 if (!sHasShutDown && (!sReleasedInstances || sReleasedInstances->Length() <
1465 kMaxCountOfCacheToReuse)) {
1466 // Put this instance to the cache. Note that now, the array may be full,
1467 // but it's not problem to cache more instances than kMaxCountOfCacheToReuse
1468 // because it just requires reallocation cost of the array buffer.
1469 if (!sReleasedInstances) {
1470 sReleasedInstances = new AutoTArray<void*, kMaxCountOfCacheToReuse>;
1472 sReleasedInstances->AppendElement(mem);
1473 } else {
1474 free(mem);
1478 nsresult TextControlState::OnEditActionHandled() {
1479 return mHandlingState ? mHandlingState->OnEditActionHandled() : NS_OK;
1482 Element* TextControlState::GetRootNode() {
1483 return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1486 Element* TextControlState::GetPreviewNode() {
1487 return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1490 void TextControlState::Clear() {
1491 MOZ_ASSERT(mHandlingState);
1492 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Destructor) ||
1493 mHandlingState->Is(TextControlAction::Unlink));
1494 if (mTextEditor) {
1495 mTextEditor->SetTextInputListener(nullptr);
1498 if (mBoundFrame) {
1499 // Oops, we still have a frame!
1500 // This should happen when the type of a text input control is being changed
1501 // to something which is not a text control. In this case, we should
1502 // pretend that a frame is being destroyed, and clean up after ourselves
1503 // properly.
1504 UnbindFromFrame(mBoundFrame);
1505 mTextEditor = nullptr;
1506 } else {
1507 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1508 // for us.
1509 DestroyEditor();
1510 MOZ_DIAGNOSTIC_ASSERT(!mBoundFrame || !mTextEditor);
1512 mTextListener = nullptr;
1515 void TextControlState::Unlink() {
1516 AutoTextControlHandlingState handlingUnlink(*this, TextControlAction::Unlink);
1517 UnlinkInternal();
1520 void TextControlState::UnlinkInternal() {
1521 MOZ_ASSERT(mHandlingState);
1522 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Unlink));
1523 TextControlState* tmp = this;
1524 tmp->Clear();
1525 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
1526 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
1529 void TextControlState::Traverse(nsCycleCollectionTraversalCallback& cb) {
1530 TextControlState* tmp = this;
1531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
1532 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
1535 nsFrameSelection* TextControlState::GetConstFrameSelection() {
1536 return mSelCon ? mSelCon->GetConstFrameSelection() : nullptr;
1539 TextEditor* TextControlState::GetTextEditor() {
1540 // Note that if the instance is destroyed in PrepareEditor(), it returns
1541 // NS_ERROR_NOT_INITIALIZED so that we don't need to create kungFuDeathGrip
1542 // in this hot path.
1543 if (!mTextEditor && NS_WARN_IF(NS_FAILED(PrepareEditor()))) {
1544 return nullptr;
1546 return mTextEditor;
1549 TextEditor* TextControlState::GetTextEditorWithoutCreation() const {
1550 return mTextEditor;
1553 nsISelectionController* TextControlState::GetSelectionController() const {
1554 return mSelCon;
1557 // Helper class, used below in BindToFrame().
1558 class PrepareEditorEvent : public Runnable {
1559 public:
1560 PrepareEditorEvent(TextControlState& aState, nsIContent* aOwnerContent,
1561 const nsAString& aCurrentValue)
1562 : Runnable("PrepareEditorEvent"),
1563 mState(&aState),
1564 mOwnerContent(aOwnerContent),
1565 mCurrentValue(aCurrentValue) {
1566 aState.mValueTransferInProgress = true;
1569 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
1570 if (NS_WARN_IF(!mState)) {
1571 return NS_ERROR_NULL_POINTER;
1574 // Transfer the saved value to the editor if we have one
1575 const nsAString* value = nullptr;
1576 if (!mCurrentValue.IsEmpty()) {
1577 value = &mCurrentValue;
1580 nsAutoScriptBlocker scriptBlocker;
1582 mState->PrepareEditor(value);
1584 mState->mValueTransferInProgress = false;
1586 return NS_OK;
1589 private:
1590 WeakPtr<TextControlState> mState;
1591 nsCOMPtr<nsIContent> mOwnerContent; // strong reference
1592 nsAutoString mCurrentValue;
1595 nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
1596 MOZ_ASSERT(
1597 !nsContentUtils::IsSafeToRunScript(),
1598 "TextControlState::BindToFrame() has to be called with script blocker");
1599 NS_ASSERTION(aFrame, "The frame to bind to should be valid");
1600 if (!aFrame) {
1601 return NS_ERROR_INVALID_ARG;
1604 NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
1605 if (mBoundFrame) {
1606 return NS_ERROR_FAILURE;
1609 // If we'll need to transfer our current value to the editor, save it before
1610 // binding to the frame.
1611 nsAutoString currentValue;
1612 if (mTextEditor) {
1613 GetValue(currentValue, true, /* aForDisplay = */ false);
1616 mBoundFrame = aFrame;
1618 Element* rootNode = aFrame->GetRootNode();
1619 MOZ_ASSERT(rootNode);
1621 PresShell* presShell = aFrame->PresContext()->GetPresShell();
1622 MOZ_ASSERT(presShell);
1624 // Create a SelectionController
1625 mSelCon = new TextInputSelectionController(presShell, rootNode);
1626 MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
1627 mTextListener = new TextInputListener(mTextCtrlElement);
1629 mTextListener->SetFrame(mBoundFrame);
1631 // Editor will override this as needed from InitializeSelection.
1632 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1634 // Get the caret and make it a selection listener.
1635 // FYI: It's safe to use raw pointer for calling
1636 // Selection::AddSelectionListner() because it only appends the listener
1637 // to its internal array.
1638 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
1639 if (selection) {
1640 RefPtr<nsCaret> caret = presShell->GetCaret();
1641 if (caret) {
1642 selection->AddSelectionListener(caret);
1644 mTextListener->StartToListenToSelectionChange();
1647 // If an editor exists from before, prepare it for usage
1648 if (mTextEditor) {
1649 if (NS_WARN_IF(!mTextCtrlElement)) {
1650 return NS_ERROR_FAILURE;
1653 // Set the correct direction on the newly created root node
1654 if (mTextEditor->IsRightToLeft()) {
1655 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, false);
1656 } else if (mTextEditor->IsLeftToRight()) {
1657 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"ltr"_ns, false);
1658 } else {
1659 // otherwise, inherit the content node's direction
1662 nsContentUtils::AddScriptRunner(
1663 new PrepareEditorEvent(*this, mTextCtrlElement, currentValue));
1666 return NS_OK;
1669 struct MOZ_STACK_CLASS PreDestroyer {
1670 void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
1671 ~PreDestroyer() {
1672 if (mTextEditor) {
1673 // In this case, we don't need to restore the unmasked range of password
1674 // editor.
1675 UniquePtr<PasswordMaskData> passwordMaskData = mTextEditor->PreDestroy();
1678 void Swap(RefPtr<TextEditor>& aTextEditor) {
1679 return mTextEditor.swap(aTextEditor);
1682 private:
1683 RefPtr<TextEditor> mTextEditor;
1686 nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
1687 if (!mBoundFrame) {
1688 // Cannot create an editor without a bound frame.
1689 // Don't return a failure code, because js callers can't handle that.
1690 return NS_OK;
1693 if (mEditorInitialized) {
1694 // Do not initialize the editor multiple times.
1695 return NS_OK;
1698 AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
1700 if (mHandlingState) {
1701 // Don't attempt to initialize recursively!
1702 if (mHandlingState->IsHandling(TextControlAction::PrepareEditor)) {
1703 return NS_ERROR_NOT_INITIALIZED;
1705 // Reschedule creating editor later if we're setting value.
1706 if (mHandlingState->IsHandling(TextControlAction::SetValue)) {
1707 mHandlingState->PrepareEditorLater();
1708 return NS_ERROR_NOT_INITIALIZED;
1712 MOZ_ASSERT(mTextCtrlElement);
1714 AutoTextControlHandlingState preparingEditor(
1715 *this, TextControlAction::PrepareEditor);
1717 // Note that we don't check mTextEditor here, because we might already have
1718 // one around, in which case we don't create a new one, and we'll just tie
1719 // the required machinery to it.
1721 nsPresContext* presContext = mBoundFrame->PresContext();
1722 PresShell* presShell = presContext->GetPresShell();
1724 // Setup the editor flags
1726 // Spell check is diabled at creation time. It is enabled once
1727 // the editor comes into focus.
1728 uint32_t editorFlags = nsIEditor::eEditorSkipSpellCheck;
1730 if (IsSingleLineTextControl()) {
1731 editorFlags |= nsIEditor::eEditorSingleLineMask;
1733 if (IsPasswordTextControl()) {
1734 editorFlags |= nsIEditor::eEditorPasswordMask;
1737 bool shouldInitializeEditor = false;
1738 RefPtr<TextEditor> newTextEditor; // the editor that we might create
1739 PreDestroyer preDestroyer;
1740 if (!mTextEditor) {
1741 shouldInitializeEditor = true;
1743 // Create an editor
1744 newTextEditor = new TextEditor();
1745 preDestroyer.Init(newTextEditor);
1747 // Make sure we clear out the non-breaking space before we initialize the
1748 // editor
1749 nsresult rv = mBoundFrame->UpdateValueDisplay(true, true);
1750 if (NS_FAILED(rv)) {
1751 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1752 return rv;
1754 } else {
1755 if (aValue || !mEditorInitialized) {
1756 // Set the correct value in the root node
1757 nsresult rv =
1758 mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
1759 if (NS_FAILED(rv)) {
1760 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1761 return rv;
1765 newTextEditor = mTextEditor; // just pretend that we have a new editor!
1767 // Don't lose application flags in the process.
1768 if (newTextEditor->IsMailEditor()) {
1769 editorFlags |= nsIEditor::eEditorMailMask;
1773 // Get the current value of the textfield from the content.
1774 // Note that if we've created a new editor, mTextEditor is null at this stage,
1775 // so we will get the real value from the content.
1776 nsAutoString defaultValue;
1777 if (aValue) {
1778 defaultValue = *aValue;
1779 } else {
1780 GetValue(defaultValue, true, /* aForDisplay = */ true);
1783 if (!mEditorInitialized) {
1784 // Now initialize the editor.
1786 // NOTE: Conversion of '\n' to <BR> happens inside the
1787 // editor's Init() call.
1789 // Get the DOM document
1790 nsCOMPtr<Document> doc = presShell->GetDocument();
1791 if (NS_WARN_IF(!doc)) {
1792 return NS_ERROR_FAILURE;
1795 // What follows is a bit of a hack. The editor uses the public DOM APIs
1796 // for its content manipulations, and it causes it to fail some security
1797 // checks deep inside when initializing. So we explictly make it clear that
1798 // we're native code.
1799 // Note that any script that's directly trying to access our value
1800 // has to be going through some scriptable object to do that and that
1801 // already does the relevant security checks.
1802 AutoNoJSAPI nojsapi;
1804 RefPtr<Element> anonymousDivElement = GetRootNode();
1805 if (NS_WARN_IF(!anonymousDivElement) || NS_WARN_IF(!mSelCon)) {
1806 return NS_ERROR_FAILURE;
1808 OwningNonNull<TextInputSelectionController> selectionController(*mSelCon);
1809 UniquePtr<PasswordMaskData> passwordMaskData;
1810 if (editorFlags & nsIEditor::eEditorPasswordMask) {
1811 if (mPasswordMaskData) {
1812 passwordMaskData = std::move(mPasswordMaskData);
1813 } else {
1814 passwordMaskData = MakeUnique<PasswordMaskData>();
1816 } else {
1817 mPasswordMaskData = nullptr;
1819 nsresult rv =
1820 newTextEditor->Init(*doc, *anonymousDivElement, selectionController,
1821 editorFlags, std::move(passwordMaskData));
1822 if (NS_FAILED(rv)) {
1823 NS_WARNING("TextEditor::Init() failed");
1824 return rv;
1828 // Initialize the controller for the editor
1830 nsresult rv = NS_OK;
1831 if (!SuppressEventHandlers(presContext)) {
1832 nsCOMPtr<nsIControllers> controllers;
1833 if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
1834 nsresult rv = inputElement->GetControllers(getter_AddRefs(controllers));
1835 if (NS_WARN_IF(NS_FAILED(rv))) {
1836 return rv;
1838 } else {
1839 auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
1840 if (!textAreaElement) {
1841 return NS_ERROR_FAILURE;
1844 nsresult rv =
1845 textAreaElement->GetControllers(getter_AddRefs(controllers));
1846 if (NS_WARN_IF(NS_FAILED(rv))) {
1847 return rv;
1851 if (controllers) {
1852 // XXX Oddly, nsresult value is overwritten in the following loop, and
1853 // only the last result or `found` decides the value.
1854 uint32_t numControllers;
1855 bool found = false;
1856 rv = controllers->GetControllerCount(&numControllers);
1857 for (uint32_t i = 0; i < numControllers; i++) {
1858 nsCOMPtr<nsIController> controller;
1859 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1860 if (NS_SUCCEEDED(rv) && controller) {
1861 nsCOMPtr<nsIControllerContext> editController =
1862 do_QueryInterface(controller);
1863 if (editController) {
1864 editController->SetCommandContext(
1865 static_cast<nsIEditor*>(newTextEditor));
1866 found = true;
1870 if (!found) {
1871 rv = NS_ERROR_FAILURE;
1876 // Initialize the plaintext editor
1877 if (shouldInitializeEditor) {
1878 const int32_t wrapCols = GetWrapCols();
1879 MOZ_ASSERT(wrapCols >= 0);
1880 newTextEditor->SetWrapColumn(wrapCols);
1883 // Set max text field length
1884 newTextEditor->SetMaxTextLength(mTextCtrlElement->UsedMaxLength());
1886 editorFlags = newTextEditor->Flags();
1888 // Check if the readonly/disabled attributes are set.
1889 if (mTextCtrlElement->IsDisabledOrReadOnly()) {
1890 editorFlags |= nsIEditor::eEditorReadonlyMask;
1893 SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1895 if (shouldInitializeEditor) {
1896 // Hold on to the newly created editor
1897 preDestroyer.Swap(mTextEditor);
1900 // If we have a default value, insert it under the div we created
1901 // above, but be sure to use the editor so that '*' characters get
1902 // displayed for password fields, etc. SetValue() will call the
1903 // editor for us.
1905 if (!defaultValue.IsEmpty()) {
1906 // XXX rv may store error code which indicates there is no controller.
1907 // However, we overwrite it only in this case.
1908 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1909 if (NS_WARN_IF(NS_FAILED(rv))) {
1910 return rv;
1913 // Now call SetValue() which will make the necessary editor calls to set
1914 // the default value. Make sure to turn off undo before setting the default
1915 // value, and turn it back on afterwards. This will make sure we can't undo
1916 // past the default value.
1917 // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
1918 // undo.
1920 if (NS_WARN_IF(!SetValue(defaultValue, ValueSetterOption::ByInternalAPI))) {
1921 return NS_ERROR_OUT_OF_MEMORY;
1924 // Now restore the original editor flags.
1925 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1926 if (NS_WARN_IF(NS_FAILED(rv))) {
1927 return rv;
1931 DebugOnly<bool> enabledUndoRedo =
1932 newTextEditor->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP);
1933 NS_WARNING_ASSERTION(enabledUndoRedo,
1934 "Failed to enable undo/redo transaction");
1936 if (!mEditorInitialized) {
1937 newTextEditor->PostCreate();
1938 mEverInited = true;
1939 mEditorInitialized = true;
1942 if (mTextListener) {
1943 newTextEditor->SetTextInputListener(mTextListener);
1946 // Restore our selection after being bound to a new frame
1947 if (mSelectionCached) {
1948 if (mRestoringSelection) { // paranoia
1949 mRestoringSelection->Revoke();
1951 mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1952 if (mRestoringSelection) {
1953 nsContentUtils::AddScriptRunner(mRestoringSelection);
1957 // The selection cache is no longer going to be valid.
1959 // XXXbz Shouldn't we do this at the point when we're actually about to
1960 // restore the properties or something? As things stand, if UnbindFromFrame
1961 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1962 // selection info, because we will think we don't have it cached and try to
1963 // read it from the selection controller, which will not have it yet.
1964 mSelectionCached = false;
1966 return preparingEditor.IsTextControlStateDestroyed()
1967 ? NS_ERROR_NOT_INITIALIZED
1968 : rv;
1971 void TextControlState::FinishedRestoringSelection() {
1972 mRestoringSelection = nullptr;
1975 void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
1976 if (mBoundFrame) {
1977 UnbindFromFrame(mBoundFrame);
1981 void TextControlState::SetSelectionProperties(
1982 TextControlState::SelectionProperties& aProps) {
1983 if (mBoundFrame) {
1984 mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
1985 aProps.GetDirection());
1986 // The instance may have already been deleted here.
1987 } else {
1988 mSelectionProperties = aProps;
1992 void TextControlState::GetSelectionRange(uint32_t* aSelectionStart,
1993 uint32_t* aSelectionEnd,
1994 ErrorResult& aRv) {
1995 MOZ_ASSERT(aSelectionStart);
1996 MOZ_ASSERT(aSelectionEnd);
1997 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1998 "How can we not have a cached selection if we have no selection "
1999 "controller?");
2001 // Note that we may have both IsSelectionCached() _and_
2002 // GetSelectionController() if we haven't initialized our editor yet.
2003 if (IsSelectionCached()) {
2004 const SelectionProperties& props = GetSelectionProperties();
2005 *aSelectionStart = props.GetStart();
2006 *aSelectionEnd = props.GetEnd();
2007 return;
2010 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2011 if (NS_WARN_IF(!sel)) {
2012 aRv.Throw(NS_ERROR_FAILURE);
2013 return;
2016 Element* root = GetRootNode();
2017 if (NS_WARN_IF(!root)) {
2018 aRv.Throw(NS_ERROR_UNEXPECTED);
2019 return;
2021 nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
2022 *aSelectionEnd);
2025 SelectionDirection TextControlState::GetSelectionDirection(ErrorResult& aRv) {
2026 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2027 "How can we not have a cached selection if we have no selection "
2028 "controller?");
2030 // Note that we may have both IsSelectionCached() _and_
2031 // GetSelectionController() if we haven't initialized our editor yet.
2032 if (IsSelectionCached()) {
2033 return GetSelectionProperties().GetDirection();
2036 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2037 if (NS_WARN_IF(!sel)) {
2038 aRv.Throw(NS_ERROR_FAILURE);
2039 return SelectionDirection::Forward;
2042 nsDirection direction = sel->GetDirection();
2043 if (direction == eDirNext) {
2044 return SelectionDirection::Forward;
2047 MOZ_ASSERT(direction == eDirPrevious);
2048 return SelectionDirection::Backward;
2051 void TextControlState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
2052 SelectionDirection aDirection,
2053 ErrorResult& aRv,
2054 ScrollAfterSelection aScroll) {
2055 MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
2056 "How can we have a non-cached selection but no frame?");
2058 AutoTextControlHandlingState handlingSetSelectionRange(
2059 *this, TextControlAction::SetSelectionRange);
2061 if (aStart > aEnd) {
2062 aStart = aEnd;
2065 if (!IsSelectionCached()) {
2066 MOZ_ASSERT(mBoundFrame, "Our frame should still be valid");
2067 aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
2068 if (aRv.Failed() ||
2069 handlingSetSelectionRange.IsTextControlStateDestroyed()) {
2070 return;
2072 if (aScroll == ScrollAfterSelection::Yes && mBoundFrame) {
2073 // mBoundFrame could be gone if selection listeners flushed layout for
2074 // example.
2075 mBoundFrame->ScrollSelectionIntoViewAsync();
2077 return;
2080 SelectionProperties& props = GetSelectionProperties();
2081 if (!props.HasMaxLength()) {
2082 // A clone without a dirty value flag may not have a max length yet
2083 nsAutoString value;
2084 GetValue(value, false, /* aForDisplay = */ true);
2085 props.SetMaxLength(value.Length());
2088 bool changed = props.SetStart(aStart);
2089 changed |= props.SetEnd(aEnd);
2090 changed |= props.SetDirection(aDirection);
2092 if (!changed) {
2093 return;
2096 // It sure would be nice if we had an existing Element* or so to work with.
2097 RefPtr<AsyncEventDispatcher> asyncDispatcher =
2098 new AsyncEventDispatcher(mTextCtrlElement, eFormSelect, CanBubble::eYes);
2099 asyncDispatcher->PostDOMEvent();
2101 // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
2102 // XXX(krosylight): Shouldn't it fire before select event?
2103 // Currently Gecko and Blink both fire selectionchange after select.
2104 if (IsSelectionCached() &&
2105 StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
2106 asyncDispatcher = new AsyncEventDispatcher(
2107 mTextCtrlElement, eSelectionChange, CanBubble::eYes);
2108 asyncDispatcher->PostDOMEvent();
2112 void TextControlState::SetSelectionStart(const Nullable<uint32_t>& aStart,
2113 ErrorResult& aRv) {
2114 uint32_t start = 0;
2115 if (!aStart.IsNull()) {
2116 start = aStart.Value();
2119 uint32_t ignored, end;
2120 GetSelectionRange(&ignored, &end, aRv);
2121 if (aRv.Failed()) {
2122 return;
2125 SelectionDirection dir = GetSelectionDirection(aRv);
2126 if (aRv.Failed()) {
2127 return;
2130 if (end < start) {
2131 end = start;
2134 SetSelectionRange(start, end, dir, aRv);
2135 // The instance may have already been deleted here.
2138 void TextControlState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
2139 ErrorResult& aRv) {
2140 uint32_t end = 0;
2141 if (!aEnd.IsNull()) {
2142 end = aEnd.Value();
2145 uint32_t start, ignored;
2146 GetSelectionRange(&start, &ignored, aRv);
2147 if (aRv.Failed()) {
2148 return;
2151 SelectionDirection dir = GetSelectionDirection(aRv);
2152 if (aRv.Failed()) {
2153 return;
2156 SetSelectionRange(start, end, dir, aRv);
2157 // The instance may have already been deleted here.
2160 static void DirectionToName(SelectionDirection dir, nsAString& aDirection) {
2161 switch (dir) {
2162 case SelectionDirection::None:
2163 // TODO(mbrodesser): this should be supported, see
2164 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
2165 NS_WARNING("We don't actually support this... how did we get it?");
2166 return aDirection.AssignLiteral("none");
2167 case SelectionDirection::Forward:
2168 return aDirection.AssignLiteral("forward");
2169 case SelectionDirection::Backward:
2170 return aDirection.AssignLiteral("backward");
2172 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
2175 void TextControlState::GetSelectionDirectionString(nsAString& aDirection,
2176 ErrorResult& aRv) {
2177 SelectionDirection dir = GetSelectionDirection(aRv);
2178 if (aRv.Failed()) {
2179 return;
2181 DirectionToName(dir, aDirection);
2184 static SelectionDirection DirectionStringToSelectionDirection(
2185 const nsAString& aDirection) {
2186 if (aDirection.EqualsLiteral("backward")) {
2187 return SelectionDirection::Backward;
2189 // We don't support directionless selections, see bug 1541454.
2190 return SelectionDirection::Forward;
2193 void TextControlState::SetSelectionDirection(const nsAString& aDirection,
2194 ErrorResult& aRv) {
2195 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2197 uint32_t start, end;
2198 GetSelectionRange(&start, &end, aRv);
2199 if (aRv.Failed()) {
2200 return;
2203 SetSelectionRange(start, end, dir, aRv);
2204 // The instance may have already been deleted here.
2207 static SelectionDirection DirectionStringToSelectionDirection(
2208 const Optional<nsAString>& aDirection) {
2209 if (!aDirection.WasPassed()) {
2210 // We don't support directionless selections.
2211 return SelectionDirection::Forward;
2214 return DirectionStringToSelectionDirection(aDirection.Value());
2217 void TextControlState::SetSelectionRange(uint32_t aSelectionStart,
2218 uint32_t aSelectionEnd,
2219 const Optional<nsAString>& aDirection,
2220 ErrorResult& aRv,
2221 ScrollAfterSelection aScroll) {
2222 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2224 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv, aScroll);
2225 // The instance may have already been deleted here.
2228 void TextControlState::SetRangeText(const nsAString& aReplacement,
2229 ErrorResult& aRv) {
2230 uint32_t start, end;
2231 GetSelectionRange(&start, &end, aRv);
2232 if (aRv.Failed()) {
2233 return;
2236 SetRangeText(aReplacement, start, end, SelectionMode::Preserve, aRv,
2237 Some(start), Some(end));
2238 // The instance may have already been deleted here.
2241 void TextControlState::SetRangeText(const nsAString& aReplacement,
2242 uint32_t aStart, uint32_t aEnd,
2243 SelectionMode aSelectMode, ErrorResult& aRv,
2244 const Maybe<uint32_t>& aSelectionStart,
2245 const Maybe<uint32_t>& aSelectionEnd) {
2246 if (aStart > aEnd) {
2247 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2248 return;
2251 AutoTextControlHandlingState handlingSetRangeText(
2252 *this, TextControlAction::SetRangeText);
2254 nsAutoString value;
2255 mTextCtrlElement->GetValueFromSetRangeText(value);
2256 uint32_t inputValueLength = value.Length();
2258 if (aStart > inputValueLength) {
2259 aStart = inputValueLength;
2262 if (aEnd > inputValueLength) {
2263 aEnd = inputValueLength;
2266 uint32_t selectionStart, selectionEnd;
2267 if (!aSelectionStart) {
2268 MOZ_ASSERT(!aSelectionEnd);
2269 GetSelectionRange(&selectionStart, &selectionEnd, aRv);
2270 if (aRv.Failed()) {
2271 return;
2273 } else {
2274 MOZ_ASSERT(aSelectionEnd);
2275 selectionStart = *aSelectionStart;
2276 selectionEnd = *aSelectionEnd;
2279 // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
2280 Selection* selection =
2281 mSelCon ? mSelCon->GetSelection(SelectionType::eNormal) : nullptr;
2282 SelectionBatcher selectionBatcher(
2283 selection, __FUNCTION__,
2284 nsISelectionListener::JS_REASON); // no-op if nullptr
2286 MOZ_ASSERT(aStart <= aEnd);
2287 value.Replace(aStart, aEnd - aStart, aReplacement);
2288 nsresult rv =
2289 MOZ_KnownLive(mTextCtrlElement)->SetValueFromSetRangeText(value);
2290 if (NS_FAILED(rv)) {
2291 aRv.Throw(rv);
2292 return;
2295 uint32_t newEnd = aStart + aReplacement.Length();
2296 int32_t delta = aReplacement.Length() - (aEnd - aStart);
2298 switch (aSelectMode) {
2299 case SelectionMode::Select:
2300 selectionStart = aStart;
2301 selectionEnd = newEnd;
2302 break;
2303 case SelectionMode::Start:
2304 selectionStart = selectionEnd = aStart;
2305 break;
2306 case SelectionMode::End:
2307 selectionStart = selectionEnd = newEnd;
2308 break;
2309 case SelectionMode::Preserve:
2310 if (selectionStart > aEnd) {
2311 selectionStart += delta;
2312 } else if (selectionStart > aStart) {
2313 selectionStart = aStart;
2316 if (selectionEnd > aEnd) {
2317 selectionEnd += delta;
2318 } else if (selectionEnd > aStart) {
2319 selectionEnd = newEnd;
2321 break;
2322 default:
2323 MOZ_ASSERT_UNREACHABLE("Unknown mode!");
2326 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
2327 if (IsSelectionCached()) {
2328 // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
2329 GetSelectionProperties().SetMaxLength(value.Length());
2333 void TextControlState::DestroyEditor() {
2334 // notify the editor that we are going away
2335 if (mEditorInitialized) {
2336 // FYI: TextEditor checks whether it's destroyed or not immediately after
2337 // changes the DOM tree or selection so that it's safe to call
2338 // PreDestroy() here even while we're handling actions with
2339 // mTextEditor.
2340 MOZ_ASSERT(!mPasswordMaskData);
2341 RefPtr<TextEditor> textEditor = mTextEditor;
2342 mPasswordMaskData = textEditor->PreDestroy();
2343 MOZ_ASSERT_IF(mPasswordMaskData, !mPasswordMaskData->mTimer);
2344 mEditorInitialized = false;
2348 void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
2349 if (NS_WARN_IF(!mBoundFrame)) {
2350 return;
2353 // If it was, however, it should be unbounded from the same frame.
2354 MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2355 if (aFrame && aFrame != mBoundFrame) {
2356 return;
2359 AutoTextControlHandlingState handlingUnbindFromFrame(
2360 *this, TextControlAction::UnbindFromFrame);
2362 if (mSelCon) {
2363 mSelCon->SelectionWillLoseFocus();
2366 // We need to start storing the value outside of the editor if we're not
2367 // going to use it anymore, so retrieve it for now.
2368 nsAutoString value;
2369 GetValue(value, true, /* aForDisplay = */ false);
2371 if (mRestoringSelection) {
2372 mRestoringSelection->Revoke();
2373 mRestoringSelection = nullptr;
2376 // Save our selection state if needed.
2377 // Note that GetSelectionRange will attempt to work with our selection
2378 // controller, so we should make sure we do it before we start doing things
2379 // like destroying our editor (if we have one), tearing down the selection
2380 // controller, and so forth.
2381 if (!IsSelectionCached()) {
2382 // Go ahead and cache it now.
2383 uint32_t start = 0, end = 0;
2384 GetSelectionRange(&start, &end, IgnoreErrors());
2386 nsITextControlFrame::SelectionDirection direction =
2387 GetSelectionDirection(IgnoreErrors());
2389 SelectionProperties& props = GetSelectionProperties();
2390 props.SetMaxLength(value.Length());
2391 props.SetStart(start);
2392 props.SetEnd(end);
2393 props.SetDirection(direction);
2394 mSelectionCached = true;
2397 // Destroy our editor
2398 DestroyEditor();
2400 // Clean up the controller
2401 if (!SuppressEventHandlers(mBoundFrame->PresContext())) {
2402 nsCOMPtr<nsIControllers> controllers;
2403 if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
2404 inputElement->GetControllers(getter_AddRefs(controllers));
2405 } else {
2406 auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
2407 if (textAreaElement) {
2408 textAreaElement->GetControllers(getter_AddRefs(controllers));
2412 if (controllers) {
2413 uint32_t numControllers;
2414 nsresult rv = controllers->GetControllerCount(&numControllers);
2415 NS_ASSERTION((NS_SUCCEEDED(rv)),
2416 "bad result in gfx text control destructor");
2417 for (uint32_t i = 0; i < numControllers; i++) {
2418 nsCOMPtr<nsIController> controller;
2419 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2420 if (NS_SUCCEEDED(rv) && controller) {
2421 nsCOMPtr<nsIControllerContext> editController =
2422 do_QueryInterface(controller);
2423 if (editController) {
2424 editController->SetCommandContext(nullptr);
2431 if (mSelCon) {
2432 if (mTextListener) {
2433 mTextListener->EndListeningToSelectionChange();
2436 mSelCon->SetScrollableFrame(nullptr);
2437 mSelCon = nullptr;
2440 if (mTextListener) {
2441 mTextListener->SetFrame(nullptr);
2443 EventListenerManager* manager =
2444 mTextCtrlElement->GetExistingListenerManager();
2445 if (manager) {
2446 manager->RemoveEventListenerByType(mTextListener, u"keydown"_ns,
2447 TrustedEventsAtSystemGroupBubble());
2448 manager->RemoveEventListenerByType(mTextListener, u"keypress"_ns,
2449 TrustedEventsAtSystemGroupBubble());
2450 manager->RemoveEventListenerByType(mTextListener, u"keyup"_ns,
2451 TrustedEventsAtSystemGroupBubble());
2454 mTextListener = nullptr;
2457 mBoundFrame = nullptr;
2459 // Now that we don't have a frame any more, store the value in the text
2460 // buffer. The only case where we don't do this is if a value transfer is in
2461 // progress.
2462 if (!mValueTransferInProgress) {
2463 DebugOnly<bool> ok = SetValue(value, ValueSetterOption::ByInternalAPI);
2464 // TODO Find something better to do if this fails...
2465 NS_WARNING_ASSERTION(ok, "SetValue() couldn't allocate memory");
2469 void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
2470 bool aForDisplay) const {
2471 // While SetValue() is being called and requesting to commit composition to
2472 // IME, GetValue() may be called for appending text or something. Then, we
2473 // need to return the latest aValue of SetValue() since the value hasn't
2474 // been set to the editor yet.
2475 // XXX After implementing "beforeinput" event, this becomes wrong. The
2476 // value should be modified immediately after "beforeinput" event for
2477 // "insertReplacementText".
2478 if (mHandlingState &&
2479 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2480 aValue = mHandlingState->GetSettingValue();
2481 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2482 return;
2485 if (mTextEditor && mBoundFrame &&
2486 (mEditorInitialized || !IsSingleLineTextControl())) {
2487 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2488 aValue = mBoundFrame->CachedValue();
2489 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2490 return;
2493 aValue.Truncate(); // initialize out param
2495 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2496 nsIDocumentEncoder::OutputPreformatted |
2497 nsIDocumentEncoder::OutputPersistNBSP |
2498 nsIDocumentEncoder::OutputBodyOnly);
2499 if (!aIgnoreWrap) {
2500 TextControlElement::nsHTMLTextWrap wrapProp;
2501 if (mTextCtrlElement &&
2502 TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
2503 wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
2504 flags |= nsIDocumentEncoder::OutputWrap;
2508 // What follows is a bit of a hack. The problem is that we could be in
2509 // this method because we're being destroyed for whatever reason while
2510 // script is executing. If that happens, editor will run with the
2511 // privileges of the executing script, which means it may not be able to
2512 // access its own DOM nodes! Let's try to deal with that by pushing a null
2513 // JSContext on the JSContext stack to make it clear that we're native
2514 // code. Note that any script that's directly trying to access our value
2515 // has to be going through some scriptable object to do that and that
2516 // already does the relevant security checks.
2517 // XXXbz if we could just get the textContent of our anonymous content (eg
2518 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2519 // this.
2520 { /* Scope for AutoNoJSAPI. */
2521 AutoNoJSAPI nojsapi;
2523 DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2524 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2525 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2527 // Only when the result doesn't include line breaks caused by hard-wrap,
2528 // mCacheValue should cache the value.
2529 if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2530 mBoundFrame->CacheValue(aValue);
2531 } else {
2532 mBoundFrame->ClearCachedValue();
2534 } else if (!mTextCtrlElement->ValueChanged() || mValue.IsVoid()) {
2535 // Use nsString to avoid copying string buffer at setting aValue.
2536 nsString value;
2537 mTextCtrlElement->GetDefaultValueFromContent(value, aForDisplay);
2538 // TODO: We should make default value not include \r.
2539 nsContentUtils::PlatformToDOMLineBreaks(value);
2540 aValue = std::move(value);
2541 } else {
2542 aValue = mValue;
2543 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2547 bool TextControlState::ValueEquals(const nsAString& aValue) const {
2548 nsAutoString value;
2549 GetValue(value, true, /* aForDisplay = */ true);
2550 return aValue.Equals(value);
2553 #ifdef DEBUG
2554 // @param aOptions TextControlState::ValueSetterOptions
2555 bool AreFlagsNotDemandingContradictingMovements(
2556 const ValueSetterOptions& aOptions) {
2557 return !aOptions.contains(
2558 {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward,
2559 ValueSetterOption::MoveCursorToEndIfValueChanged});
2561 #endif // DEBUG
2563 bool TextControlState::SetValue(const nsAString& aValue,
2564 const nsAString* aOldValue,
2565 const ValueSetterOptions& aOptions) {
2566 if (mHandlingState &&
2567 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2568 // GetValue doesn't return current text frame's content during committing.
2569 // So we cannot trust this old value
2570 aOldValue = nullptr;
2573 if (mPasswordMaskData) {
2574 if (mHandlingState &&
2575 mHandlingState->Is(TextControlAction::UnbindFromFrame)) {
2576 // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
2577 } else {
2578 // Otherwise, we should mask the new password, even if it's same value
2579 // since the same value may be one for different web app's.
2580 mPasswordMaskData->Reset();
2584 const bool wasHandlingSetValue =
2585 mHandlingState && mHandlingState->IsHandling(TextControlAction::SetValue);
2587 ErrorResult error;
2588 AutoTextControlHandlingState handlingSetValue(
2589 *this, TextControlAction::SetValue, aValue, aOldValue, aOptions, error);
2590 if (error.Failed()) {
2591 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
2592 error.SuppressException();
2593 return false;
2596 const auto changeKind = [&] {
2597 if (aOptions.contains(ValueSetterOption::ByInternalAPI)) {
2598 return ValueChangeKind::Internal;
2600 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2601 return ValueChangeKind::UserInteraction;
2603 return ValueChangeKind::Script;
2604 }();
2606 if (changeKind == ValueChangeKind::Script) {
2607 // This value change will not be interactive. If we're an input that was
2608 // interactively edited, save the last interactive value now before it goes
2609 // away.
2610 if (auto* input = HTMLInputElement::FromNode(mTextCtrlElement)) {
2611 if (input->LastValueChangeWasInteractive()) {
2612 GetValue(mLastInteractiveValue, /* aIgnoreWrap = */ true,
2613 /* aForDisplay = */ true);
2618 // Note that if this may be called during reframe of the editor. In such
2619 // case, we shouldn't commit composition. Therefore, when this is called
2620 // for internal processing, we shouldn't commit the composition.
2621 // TODO: In strictly speaking, we should move committing composition into
2622 // editor because if "beforeinput" for this setting value is canceled,
2623 // we shouldn't commit composition. However, in Firefox, we never
2624 // call this via `setUserInput` during composition. Therefore, the
2625 // bug must not be reproducible actually.
2626 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
2627 aOptions.contains(ValueSetterOption::ByContentAPI)) {
2628 if (EditorHasComposition()) {
2629 // When this is called recursively, there shouldn't be composition.
2630 if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
2631 // Don't request to commit composition again. But if it occurs,
2632 // we should skip to set the new value to the editor here. It should
2633 // be set later with the newest value.
2634 return true;
2636 if (NS_WARN_IF(!mBoundFrame)) {
2637 // We're not sure if this case is possible.
2638 } else {
2639 // If setting value won't change current value, we shouldn't commit
2640 // composition for compatibility with the other browsers.
2641 MOZ_ASSERT(!aOldValue || ValueEquals(*aOldValue));
2642 bool isSameAsCurrentValue =
2643 aOldValue ? aOldValue->Equals(handlingSetValue.GetSettingValue())
2644 : ValueEquals(handlingSetValue.GetSettingValue());
2645 if (isSameAsCurrentValue) {
2646 // Note that in this case, we shouldn't fire any events with setting
2647 // value because event handlers may try to set value recursively but
2648 // we cannot commit composition at that time due to unsafe to run
2649 // script (see below).
2650 return true;
2653 // If there is composition, need to commit composition first because
2654 // other browsers do that.
2655 // NOTE: We don't need to block nested calls of this because input nor
2656 // other events won't be fired by setting values and script blocker
2657 // is used during setting the value to the editor. IE also allows
2658 // to set the editor value on the input event which is caused by
2659 // forcibly committing composition.
2660 AutoTextControlHandlingState handlingCommitComposition(
2661 *this, TextControlAction::CommitComposition);
2662 if (nsContentUtils::IsSafeToRunScript()) {
2663 // WARNING: During this call, compositionupdate, compositionend, input
2664 // events will be fired. Therefore, everything can occur. E.g., the
2665 // document may be unloaded.
2666 RefPtr<TextEditor> textEditor = mTextEditor;
2667 nsresult rv = textEditor->CommitComposition();
2668 if (handlingCommitComposition.IsTextControlStateDestroyed()) {
2669 return true;
2671 if (NS_FAILED(rv)) {
2672 NS_WARNING("TextControlState failed to commit composition");
2673 return true;
2675 // Note that if a composition event listener sets editor value again,
2676 // we should use the new value here. The new value is stored in
2677 // handlingSetValue right now.
2678 } else {
2679 NS_WARNING(
2680 "SetValue() is called when there is composition but "
2681 "it's not safe to request to commit the composition");
2686 if (mTextEditor && mBoundFrame) {
2687 if (!SetValueWithTextEditor(handlingSetValue)) {
2688 return false;
2690 } else if (!SetValueWithoutTextEditor(handlingSetValue)) {
2691 return false;
2694 // If we were handling SetValue() before, don't update the DOM state twice,
2695 // just let the outer call do so.
2696 if (!wasHandlingSetValue) {
2697 handlingSetValue.GetTextControlElement()->OnValueChanged(
2698 changeKind, handlingSetValue.GetSettingValue());
2700 return true;
2703 bool TextControlState::SetValueWithTextEditor(
2704 AutoTextControlHandlingState& aHandlingSetValue) {
2705 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2706 MOZ_ASSERT(mTextEditor);
2707 MOZ_ASSERT(mBoundFrame);
2708 NS_WARNING_ASSERTION(!EditorHasComposition(),
2709 "Failed to commit composition before setting value. "
2710 "Investigate the cause!");
2712 #ifdef DEBUG
2713 if (IsSingleLineTextControl()) {
2714 NS_ASSERTION(mEditorInitialized || aHandlingSetValue.IsHandling(
2715 TextControlAction::PrepareEditor),
2716 "We should never try to use the editor if we're not "
2717 "initialized unless we're being initialized");
2719 #endif
2721 MOZ_ASSERT(!aHandlingSetValue.GetOldValue() ||
2722 ValueEquals(*aHandlingSetValue.GetOldValue()));
2723 const bool isSameAsCurrentValue =
2724 aHandlingSetValue.GetOldValue()
2725 ? aHandlingSetValue.GetOldValue()->Equals(
2726 aHandlingSetValue.GetSettingValue())
2727 : ValueEquals(aHandlingSetValue.GetSettingValue());
2729 // this is necessary to avoid infinite recursion
2730 if (isSameAsCurrentValue) {
2731 return true;
2734 RefPtr<TextEditor> textEditor = mTextEditor;
2736 nsCOMPtr<Document> document = textEditor->GetDocument();
2737 if (NS_WARN_IF(!document)) {
2738 return true;
2741 // Time to mess with our security context... See comments in GetValue()
2742 // for why this is needed. Note that we have to do this up here, because
2743 // otherwise SelectAll() will fail.
2744 AutoNoJSAPI nojsapi;
2746 // FYI: It's safe to use raw pointer for selection here because
2747 // SelectionBatcher will grab it with RefPtr.
2748 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
2749 SelectionBatcher selectionBatcher(selection, __FUNCTION__);
2751 // get the flags, remove readonly, disabled and max-length,
2752 // set the value, restore flags
2753 AutoRestoreEditorState restoreState(textEditor);
2755 aHandlingSetValue.WillSetValueWithTextEditor();
2757 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2758 ValueSetterOption::BySetUserInputAPI)) {
2759 // If the caller inserts text as part of user input, for example,
2760 // autocomplete, we need to replace the text as "insert string"
2761 // because undo should cancel only this operation (i.e., previous
2762 // transactions typed by user shouldn't be merged with this).
2763 // In this case, we need to dispatch "input" event because
2764 // web apps may need to know the user's operation.
2765 // In this case, we need to dispatch "beforeinput" events since
2766 // we're emulating the user's input. Passing nullptr as
2767 // nsIPrincipal means that that may be user's input. So, let's
2768 // do it.
2769 nsresult rv = textEditor->ReplaceTextAsAction(
2770 aHandlingSetValue.GetSettingValue(), nullptr,
2771 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2772 ? TextEditor::AllowBeforeInputEventCancelable::Yes
2773 : TextEditor::AllowBeforeInputEventCancelable::No,
2774 nullptr);
2775 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2776 "EditorBase::ReplaceTextAsAction() failed");
2777 return rv != NS_ERROR_OUT_OF_MEMORY;
2780 // Don't dispatch "beforeinput" event nor "input" event for setting value
2781 // by script.
2782 AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
2784 // On <input> or <textarea>, we shouldn't preserve existing undo
2785 // transactions because other browsers do not preserve them too
2786 // and not preserving transactions makes setting value faster.
2788 // (Except if chrome opts into this behavior).
2789 Maybe<AutoDisableUndo> disableUndo;
2790 if (!aHandlingSetValue.ValueSetterOptionsRef().contains(
2791 ValueSetterOption::PreserveUndoHistory)) {
2792 disableUndo.emplace(textEditor);
2795 if (selection) {
2796 // Since we don't use undo transaction, we don't need to store
2797 // selection state. SetText will set selection to tail.
2798 IgnoredErrorResult ignoredError;
2799 MOZ_KnownLive(selection)->RemoveAllRanges(ignoredError);
2800 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2801 "Selection::RemoveAllRanges() failed, but ignored");
2804 // In this case, we makes the editor stop dispatching "input"
2805 // event so that passing nullptr as nsIPrincipal is safe for now.
2806 nsresult rv = textEditor->SetTextAsAction(
2807 aHandlingSetValue.GetSettingValue(),
2808 aHandlingSetValue.ValueSetterOptionsRef().contains(
2809 ValueSetterOption::BySetUserInputAPI) &&
2810 !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2811 ? TextEditor::AllowBeforeInputEventCancelable::No
2812 : TextEditor::AllowBeforeInputEventCancelable::Yes,
2813 nullptr);
2814 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2815 "TextEditor::SetTextAsAction() failed");
2817 // Call the listener's OnEditActionHandled() callback manually if
2818 // OnEditActionHandled() hasn't been called yet since TextEditor don't use
2819 // the transaction manager in this path and it could be that the editor
2820 // would bypass calling the listener for that reason.
2821 if (!aHandlingSetValue.HasEditActionHandled()) {
2822 nsresult rvOnEditActionHandled =
2823 MOZ_KnownLive(aHandlingSetValue.GetTextInputListener())
2824 ->OnEditActionHandled(*textEditor);
2825 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled),
2826 "TextInputListener::OnEditActionHandled() failed");
2827 if (rv != NS_ERROR_OUT_OF_MEMORY) {
2828 rv = rvOnEditActionHandled;
2832 return rv != NS_ERROR_OUT_OF_MEMORY;
2835 bool TextControlState::SetValueWithoutTextEditor(
2836 AutoTextControlHandlingState& aHandlingSetValue) {
2837 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2838 MOZ_ASSERT(!mTextEditor || !mBoundFrame);
2839 NS_WARNING_ASSERTION(!EditorHasComposition(),
2840 "Failed to commit composition before setting value. "
2841 "Investigate the cause!");
2843 if (mValue.IsVoid()) {
2844 mValue.SetIsVoid(false);
2847 // We can't just early-return here, because OnValueChanged below still need to
2848 // be called.
2849 if (!mValue.Equals(aHandlingSetValue.GetSettingValue()) ||
2850 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2851 bool handleSettingValue = true;
2852 // If `SetValue()` call is nested, `GetSettingValue()` result will be
2853 // modified. So, we need to store input event data value before
2854 // dispatching beforeinput event.
2855 nsString inputEventData(aHandlingSetValue.GetSettingValue());
2856 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2857 ValueSetterOption::BySetUserInputAPI) &&
2858 !aHandlingSetValue.HasBeforeInputEventDispatched()) {
2859 // This probably occurs when session restorer sets the old value with
2860 // `setUserInput`. If so, we need to dispatch "beforeinput" event of
2861 // "insertReplacementText" for conforming to the spec. However, the
2862 // spec does NOT treat the session restoring case. Therefore, if this
2863 // breaks session restorere in a lot of web apps, we should probably
2864 // stop dispatching it or make it non-cancelable.
2865 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2866 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2867 aHandlingSetValue.WillDispatchBeforeInputEvent();
2868 nsEventStatus status = nsEventStatus_eIgnore;
2869 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2870 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2871 eEditorBeforeInput, EditorInputType::eInsertReplacementText, nullptr,
2872 InputEventOptions(
2873 inputEventData,
2874 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2875 ? InputEventOptions::NeverCancelable::No
2876 : InputEventOptions::NeverCancelable::Yes),
2877 &status);
2878 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2879 "Failed to dispatch beforeinput event");
2880 if (status == nsEventStatus_eConsumeNoDefault) {
2881 return true; // "beforeinput" event was canceled.
2883 // If we were destroyed by "beforeinput" event listeners, probably, we
2884 // don't need to keep handling it.
2885 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2886 return true;
2888 // Even if "beforeinput" event was not canceled, its listeners may do
2889 // something. If it causes creating `TextEditor` and bind this to a
2890 // frame, we need to use the path, but `TextEditor` shouldn't fire
2891 // "beforeinput" event again. Therefore, we need to prevent editor
2892 // to dispatch it.
2893 if (mTextEditor && mBoundFrame) {
2894 AutoInputEventSuppresser suppressInputEvent(mTextEditor);
2895 if (!SetValueWithTextEditor(aHandlingSetValue)) {
2896 return false;
2898 // If we were destroyed by "beforeinput" event listeners, probably, we
2899 // don't need to keep handling it.
2900 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2901 return true;
2903 handleSettingValue = false;
2907 if (handleSettingValue) {
2908 if (!mValue.Assign(aHandlingSetValue.GetSettingValue(), fallible)) {
2909 return false;
2912 // Since we have no editor we presumably have cached selection state.
2913 if (IsSelectionCached()) {
2914 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
2915 aHandlingSetValue.ValueSetterOptionsRef()));
2917 SelectionProperties& props = GetSelectionProperties();
2918 // Setting a max length and thus capping selection range early prevents
2919 // selection change detection in setRangeText. Temporarily disable
2920 // capping here with UINT32_MAX, and set it later in ::SetRangeText().
2921 props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
2922 ValueSetterOption::BySetRangeTextAPI)
2923 ? UINT32_MAX
2924 : aHandlingSetValue.GetSettingValue().Length());
2925 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2926 ValueSetterOption::MoveCursorToEndIfValueChanged)) {
2927 props.SetStart(aHandlingSetValue.GetSettingValue().Length());
2928 props.SetEnd(aHandlingSetValue.GetSettingValue().Length());
2929 props.SetDirection(SelectionDirection::Forward);
2930 } else if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2931 ValueSetterOption::
2932 MoveCursorToBeginSetSelectionDirectionForward)) {
2933 props.SetStart(0);
2934 props.SetEnd(0);
2935 props.SetDirection(SelectionDirection::Forward);
2939 // Update the frame display if needed
2940 if (mBoundFrame) {
2941 mBoundFrame->UpdateValueDisplay(true);
2944 // If the text control element has focus, IMEContentObserver is not
2945 // observing the content changes due to no bound frame or no TextEditor.
2946 // Therefore, we need to let IMEContentObserver know all values are being
2947 // replaced.
2948 if (IMEContentObserver* observer = GetIMEContentObserver()) {
2949 observer->OnTextControlValueChangedWhileNotObservable(mValue);
2953 // If this is called as part of user input, we need to dispatch "input"
2954 // event with "insertReplacementText" since web apps may want to know
2955 // the user operation which changes editor value with a built-in function
2956 // like autocomplete, password manager, session restore, etc.
2957 // XXX Should we stop dispatching `input` event if the text control
2958 // element has already removed from the DOM tree by a `beforeinput`
2959 // event listener?
2960 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2961 ValueSetterOption::BySetUserInputAPI)) {
2962 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2964 // Update validity state before dispatching "input" event for its
2965 // listeners like `EditorBase::NotifyEditorObservers()`.
2966 aHandlingSetValue.GetTextControlElement()->OnValueChanged(
2967 ValueChangeKind::UserInteraction,
2968 aHandlingSetValue.GetSettingValue());
2970 ClearLastInteractiveValue();
2972 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2973 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2974 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2975 eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
2976 InputEventOptions(inputEventData,
2977 InputEventOptions::NeverCancelable::No));
2978 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2979 "Failed to dispatch input event");
2981 } else {
2982 // Even if our value is not actually changing, apparently we need to mark
2983 // our SelectionProperties dirty to make accessibility tests happy.
2984 // Probably because they depend on the SetSelectionRange() call we make on
2985 // our frame in RestoreSelectionState, but I have no idea why they do.
2986 if (IsSelectionCached()) {
2987 SelectionProperties& props = GetSelectionProperties();
2988 props.SetIsDirty();
2992 return true;
2995 void TextControlState::InitializeKeyboardEventListeners() {
2996 // register key listeners
2997 EventListenerManager* manager =
2998 mTextCtrlElement->GetOrCreateListenerManager();
2999 if (manager) {
3000 manager->AddEventListenerByType(mTextListener, u"keydown"_ns,
3001 TrustedEventsAtSystemGroupBubble());
3002 manager->AddEventListenerByType(mTextListener, u"keypress"_ns,
3003 TrustedEventsAtSystemGroupBubble());
3004 manager->AddEventListenerByType(mTextListener, u"keyup"_ns,
3005 TrustedEventsAtSystemGroupBubble());
3008 mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
3011 void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
3012 // If we don't have a preview div, there's nothing to do.
3013 Element* previewDiv = GetPreviewNode();
3014 if (!previewDiv) {
3015 return;
3018 nsAutoString previewValue(aValue);
3020 nsContentUtils::RemoveNewlines(previewValue);
3021 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3022 previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
3025 void TextControlState::GetPreviewText(nsAString& aValue) {
3026 // If we don't have a preview div, there's nothing to do.
3027 Element* previewDiv = GetPreviewNode();
3028 if (!previewDiv) {
3029 return;
3032 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3033 const nsTextFragment* text = previewDiv->GetFirstChild()->GetText();
3035 aValue.Truncate();
3036 text->AppendTo(aValue);
3039 bool TextControlState::EditorHasComposition() {
3040 return mTextEditor && mTextEditor->IsIMEComposing();
3043 IMEContentObserver* TextControlState::GetIMEContentObserver() const {
3044 if (NS_WARN_IF(!mTextCtrlElement) ||
3045 mTextCtrlElement != IMEStateManager::GetFocusedElement()) {
3046 return nullptr;
3048 IMEContentObserver* observer = IMEStateManager::GetActiveContentObserver();
3049 // The text control element may be an editing host. In this case, the
3050 // observer does not observe the anonymous nodes under mTextCtrlElement.
3051 // So, it means that the observer is not for ours.
3052 return observer && observer->EditorIsTextEditor() ? observer : nullptr;
3055 } // namespace mozilla