Bug 1880216 - Migrate Fenix docs into Sphinx. r=owlish,geckoview-reviewers,android...
[gecko.git] / dom / html / TextControlState.cpp
blob11f761969941319fc20669071a0e24c3934eae99
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 "nsITextControlFrame.h"
18 #include "nsContentCreatorFunctions.h"
19 #include "nsTextControlFrame.h"
20 #include "nsIControllers.h"
21 #include "nsIControllerContext.h"
22 #include "nsAttrValue.h"
23 #include "nsAttrValueInlines.h"
24 #include "nsGenericHTMLElement.h"
25 #include "nsIDOMEventListener.h"
26 #include "nsIWidget.h"
27 #include "nsIDocumentEncoder.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsServiceManagerUtils.h"
30 #include "mozilla/dom/Selection.h"
31 #include "mozilla/EventListenerManager.h"
32 #include "nsContentUtils.h"
33 #include "mozilla/Preferences.h"
34 #include "nsTextNode.h"
35 #include "nsIController.h"
36 #include "nsIScrollableFrame.h"
37 #include "mozilla/AutoRestore.h"
38 #include "mozilla/InputEventOptions.h"
39 #include "mozilla/NativeKeyBindingsType.h"
40 #include "mozilla/PresShell.h"
41 #include "mozilla/TextEvents.h"
42 #include "mozilla/dom/Event.h"
43 #include "mozilla/dom/ScriptSettings.h"
44 #include "mozilla/dom/HTMLInputElement.h"
45 #include "mozilla/dom/HTMLTextAreaElement.h"
46 #include "mozilla/dom/Text.h"
47 #include "mozilla/StaticPrefs_dom.h"
48 #include "mozilla/StaticPrefs_ui.h"
49 #include "nsFrameSelection.h"
50 #include "mozilla/ErrorResult.h"
51 #include "mozilla/Telemetry.h"
52 #include "mozilla/ShortcutKeys.h"
53 #include "mozilla/KeyEventHandler.h"
54 #include "mozilla/dom/KeyboardEvent.h"
55 #include "mozilla/ScrollTypes.h"
57 namespace mozilla {
59 using namespace dom;
60 using ValueSetterOption = TextControlState::ValueSetterOption;
61 using ValueSetterOptions = TextControlState::ValueSetterOptions;
62 using SelectionDirection = nsITextControlFrame::SelectionDirection;
64 /*****************************************************************************
65 * TextControlElement
66 *****************************************************************************/
68 NS_IMPL_CYCLE_COLLECTION_CLASS(TextControlElement)
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
71 TextControlElement, nsGenericHTMLFormControlElementWithState)
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
74 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
75 TextControlElement, nsGenericHTMLFormControlElementWithState)
76 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
78 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
79 TextControlElement, nsGenericHTMLFormControlElementWithState)
81 /*static*/
82 bool TextControlElement::GetWrapPropertyEnum(
83 nsIContent* aContent, TextControlElement::nsHTMLTextWrap& aWrapProp) {
84 // soft is the default; "physical" defaults to soft as well because all other
85 // browsers treat it that way and there is no real reason to maintain physical
86 // and virtual as separate entities if no one else does. Only hard and off
87 // do anything different.
88 aWrapProp = eHTMLTextWrap_Soft; // the default
90 if (!aContent->IsHTMLElement()) {
91 return false;
94 static mozilla::dom::Element::AttrValuesArray strings[] = {
95 nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
96 switch (aContent->AsElement()->FindAttrValueIn(
97 kNameSpaceID_None, nsGkAtoms::wrap, strings, eIgnoreCase)) {
98 case 0:
99 aWrapProp = eHTMLTextWrap_Hard;
100 break;
101 case 1:
102 aWrapProp = eHTMLTextWrap_Off;
103 break;
106 return true;
109 /*static*/
110 already_AddRefed<TextControlElement>
111 TextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
112 if (!aHost) {
113 return nullptr;
116 RefPtr<TextControlElement> parent =
117 TextControlElement::FromNodeOrNull(aHost->GetParent());
118 return parent.forget();
121 TextControlElement::FocusTristate TextControlElement::FocusState() {
122 // We can't be focused if we aren't in a (composed) document
123 Document* doc = GetComposedDoc();
124 if (!doc) {
125 return FocusTristate::eUnfocusable;
128 // first see if we are disabled or not. If disabled then do nothing.
129 if (IsDisabled()) {
130 return FocusTristate::eUnfocusable;
133 return IsInActiveTab(doc) ? FocusTristate::eActiveWindow
134 : FocusTristate::eInactiveWindow;
137 using ValueChangeKind = TextControlElement::ValueChangeKind;
139 MOZ_CAN_RUN_SCRIPT inline nsresult SetEditorFlagsIfNecessary(
140 EditorBase& aEditorBase, uint32_t aFlags) {
141 if (aEditorBase.Flags() == aFlags) {
142 return NS_OK;
144 return aEditorBase.SetFlags(aFlags);
147 /*****************************************************************************
148 * mozilla::AutoInputEventSuppresser
149 *****************************************************************************/
151 class MOZ_STACK_CLASS AutoInputEventSuppresser final {
152 public:
153 explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
154 : mTextEditor(aTextEditor),
155 // To protect against a reentrant call to SetValue, we check whether
156 // another SetValue is already happening for this editor. If it is,
157 // we must wait until we unwind to re-enable oninput events.
158 mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent()) {
159 MOZ_ASSERT(mTextEditor);
160 mTextEditor->SuppressDispatchingInputEvent(true);
162 ~AutoInputEventSuppresser() {
163 mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
166 private:
167 RefPtr<TextEditor> mTextEditor;
168 bool mOuterTransaction;
171 /*****************************************************************************
172 * mozilla::RestoreSelectionState
173 *****************************************************************************/
175 class RestoreSelectionState : public Runnable {
176 public:
177 RestoreSelectionState(TextControlState* aState, nsTextControlFrame* aFrame)
178 : Runnable("RestoreSelectionState"),
179 mFrame(aFrame),
180 mTextControlState(aState) {}
182 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
183 if (!mTextControlState) {
184 return NS_OK;
187 AutoHideSelectionChanges hideSelectionChanges(
188 mFrame->GetConstFrameSelection());
190 if (mFrame) {
191 // EnsureEditorInitialized and SetSelectionRange leads to
192 // Selection::AddRangeAndSelectFramesAndNotifyListeners which flushes
193 // Layout - need to block script to avoid nested PrepareEditor calls (bug
194 // 642800).
195 nsAutoScriptBlocker scriptBlocker;
196 mFrame->EnsureEditorInitialized();
197 TextControlState::SelectionProperties& properties =
198 mTextControlState->GetSelectionProperties();
199 if (properties.IsDirty()) {
200 mFrame->SetSelectionRange(properties.GetStart(), properties.GetEnd(),
201 properties.GetDirection());
205 if (mTextControlState) {
206 mTextControlState->FinishedRestoringSelection();
208 return NS_OK;
211 // Let the text editor tell us we're no longer relevant - avoids use of
212 // AutoWeakFrame
213 void Revoke() {
214 mFrame = nullptr;
215 mTextControlState = nullptr;
218 private:
219 nsTextControlFrame* mFrame;
220 TextControlState* mTextControlState;
223 /*****************************************************************************
224 * mozilla::AutoRestoreEditorState
225 *****************************************************************************/
227 class MOZ_RAII AutoRestoreEditorState final {
228 public:
229 MOZ_CAN_RUN_SCRIPT explicit AutoRestoreEditorState(TextEditor* aTextEditor)
230 : mTextEditor(aTextEditor),
231 mSavedFlags(mTextEditor->Flags()),
232 mSavedMaxLength(mTextEditor->MaxTextLength()),
233 mSavedEchoingPasswordPrevented(
234 mTextEditor->EchoingPasswordPrevented()) {
235 MOZ_ASSERT(mTextEditor);
237 // EditorBase::SetFlags() is a virtual method. Even though it does nothing
238 // if new flags and current flags are same, the calling cost causes
239 // appearing the method in profile. So, this class should check if it's
240 // necessary to call.
241 uint32_t flags = mSavedFlags;
242 flags &= ~nsIEditor::eEditorReadonlyMask;
243 if (mSavedFlags != flags) {
244 // It's aTextEditor and whose lifetime must be guaranteed by the caller.
245 MOZ_KnownLive(mTextEditor)->SetFlags(flags);
247 mTextEditor->PreventToEchoPassword();
248 mTextEditor->SetMaxTextLength(-1);
251 MOZ_CAN_RUN_SCRIPT ~AutoRestoreEditorState() {
252 if (!mSavedEchoingPasswordPrevented) {
253 mTextEditor->AllowToEchoPassword();
255 mTextEditor->SetMaxTextLength(mSavedMaxLength);
256 // mTextEditor's lifetime must be guaranteed by owner of the instance
257 // since the constructor is marked as `MOZ_CAN_RUN_SCRIPT` and this is
258 // a stack only class.
259 SetEditorFlagsIfNecessary(MOZ_KnownLive(*mTextEditor), mSavedFlags);
262 private:
263 TextEditor* mTextEditor;
264 uint32_t mSavedFlags;
265 int32_t mSavedMaxLength;
266 bool mSavedEchoingPasswordPrevented;
269 /*****************************************************************************
270 * mozilla::AutoDisableUndo
271 *****************************************************************************/
273 class MOZ_RAII AutoDisableUndo final {
274 public:
275 explicit AutoDisableUndo(TextEditor* aTextEditor)
276 : mTextEditor(aTextEditor), mNumberOfMaximumTransactions(0) {
277 MOZ_ASSERT(mTextEditor);
279 mNumberOfMaximumTransactions =
280 mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
281 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
282 NS_WARNING_ASSERTION(disabledUndoRedo,
283 "Failed to disable undo/redo transactions");
286 ~AutoDisableUndo() {
287 // Don't change enable/disable of undo/redo if it's enabled after
288 // it's disabled by the constructor because we shouldn't change
289 // the maximum undo/redo count to the old value.
290 if (mTextEditor->IsUndoRedoEnabled()) {
291 return;
293 // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
294 // than 0. Only when it's 0, it was disabled.
295 if (mNumberOfMaximumTransactions) {
296 DebugOnly<bool> enabledUndoRedo =
297 mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
298 NS_WARNING_ASSERTION(enabledUndoRedo,
299 "Failed to enable undo/redo transactions");
300 } else {
301 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
302 NS_WARNING_ASSERTION(disabledUndoRedo,
303 "Failed to disable undo/redo transactions");
307 private:
308 TextEditor* mTextEditor;
309 int32_t mNumberOfMaximumTransactions;
312 static bool SuppressEventHandlers(nsPresContext* aPresContext) {
313 bool suppressHandlers = false;
315 if (aPresContext) {
316 // Right now we only suppress event handlers and controller manipulation
317 // when in a print preview or print context!
319 // In the current implementation, we only paginate when
320 // printing or in print preview.
322 suppressHandlers = aPresContext->IsPaginated();
325 return suppressHandlers;
328 /*****************************************************************************
329 * mozilla::TextInputSelectionController
330 *****************************************************************************/
332 class TextInputSelectionController final : public nsSupportsWeakReference,
333 public nsISelectionController {
334 ~TextInputSelectionController() = default;
336 public:
337 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
338 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputSelectionController,
339 nsISelectionController)
341 TextInputSelectionController(PresShell* aPresShell, nsIContent* aLimiter);
343 void SetScrollableFrame(nsIScrollableFrame* aScrollableFrame);
344 nsFrameSelection* GetConstFrameSelection() { return mFrameSelection; }
345 // Will return null if !mFrameSelection.
346 Selection* GetSelection(SelectionType aSelectionType);
348 // NSISELECTIONCONTROLLER INTERFACES
349 NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
350 NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
351 NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
352 NS_IMETHOD GetSelectionFlags(int16_t* aOutEnable) override;
353 NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
354 Selection** aSelection) override;
355 Selection* GetSelection(RawSelectionType aRawSelectionType) override;
356 NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
357 int16_t aRegion, int16_t aFlags) override;
358 NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
359 nsresult RepaintSelection(nsPresContext* aPresContext,
360 SelectionType aSelectionType);
361 NS_IMETHOD SetCaretEnabled(bool enabled) override;
362 NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
363 NS_IMETHOD GetCaretEnabled(bool* _retval) override;
364 NS_IMETHOD GetCaretVisible(bool* _retval) override;
365 NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
366 NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount,
367 bool aExtend) override;
368 NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
369 NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
370 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD LineMove(bool aForward,
371 bool aExtend) override;
372 NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
373 MOZ_CAN_RUN_SCRIPT
374 NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
375 NS_IMETHOD CompleteScroll(bool aForward) override;
376 MOZ_CAN_RUN_SCRIPT NS_IMETHOD CompleteMove(bool aForward,
377 bool aExtend) override;
378 NS_IMETHOD ScrollPage(bool aForward) override;
379 NS_IMETHOD ScrollLine(bool aForward) override;
380 NS_IMETHOD ScrollCharacter(bool aRight) override;
381 void SelectionWillTakeFocus() override;
382 void SelectionWillLoseFocus() override;
384 private:
385 RefPtr<nsFrameSelection> mFrameSelection;
386 nsIScrollableFrame* mScrollFrame;
387 nsWeakPtr mPresShellWeak;
390 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputSelectionController)
391 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputSelectionController)
392 NS_INTERFACE_TABLE_HEAD(TextInputSelectionController)
393 NS_INTERFACE_TABLE(TextInputSelectionController, nsISelectionController,
394 nsISelectionDisplay, nsISupportsWeakReference)
395 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TextInputSelectionController)
396 NS_INTERFACE_MAP_END
398 NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController, mFrameSelection)
400 TextInputSelectionController::TextInputSelectionController(
401 PresShell* aPresShell, nsIContent* aLimiter)
402 : mScrollFrame(nullptr) {
403 if (aPresShell) {
404 bool accessibleCaretEnabled =
405 PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
406 mFrameSelection =
407 new nsFrameSelection(aPresShell, aLimiter, accessibleCaretEnabled);
408 mPresShellWeak = do_GetWeakReference(aPresShell);
412 void TextInputSelectionController::SetScrollableFrame(
413 nsIScrollableFrame* aScrollableFrame) {
414 mScrollFrame = aScrollableFrame;
415 if (!mScrollFrame && mFrameSelection) {
416 mFrameSelection->DisconnectFromPresShell();
417 mFrameSelection = nullptr;
421 Selection* TextInputSelectionController::GetSelection(
422 SelectionType aSelectionType) {
423 if (!mFrameSelection) {
424 return nullptr;
427 return mFrameSelection->GetSelection(aSelectionType);
430 NS_IMETHODIMP
431 TextInputSelectionController::SetDisplaySelection(int16_t aToggle) {
432 if (!mFrameSelection) {
433 return NS_ERROR_NULL_POINTER;
435 mFrameSelection->SetDisplaySelection(aToggle);
436 return NS_OK;
439 NS_IMETHODIMP
440 TextInputSelectionController::GetDisplaySelection(int16_t* aToggle) {
441 if (!mFrameSelection) {
442 return NS_ERROR_NULL_POINTER;
444 *aToggle = mFrameSelection->GetDisplaySelection();
445 return NS_OK;
448 NS_IMETHODIMP
449 TextInputSelectionController::SetSelectionFlags(int16_t aToggle) {
450 return NS_OK; // stub this out. not used in input
453 NS_IMETHODIMP
454 TextInputSelectionController::GetSelectionFlags(int16_t* aOutEnable) {
455 *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
456 return NS_OK;
459 NS_IMETHODIMP
460 TextInputSelectionController::GetSelectionFromScript(
461 RawSelectionType aRawSelectionType, Selection** aSelection) {
462 if (!mFrameSelection) {
463 return NS_ERROR_NULL_POINTER;
466 *aSelection =
467 mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
469 // GetSelection() fails only when aRawSelectionType is invalid value.
470 if (!(*aSelection)) {
471 return NS_ERROR_INVALID_ARG;
474 NS_ADDREF(*aSelection);
475 return NS_OK;
478 Selection* TextInputSelectionController::GetSelection(
479 RawSelectionType aRawSelectionType) {
480 return GetSelection(ToSelectionType(aRawSelectionType));
483 NS_IMETHODIMP
484 TextInputSelectionController::ScrollSelectionIntoView(
485 RawSelectionType aRawSelectionType, int16_t aRegion, int16_t aFlags) {
486 if (!mFrameSelection) {
487 return NS_ERROR_NULL_POINTER;
489 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
490 return frameSelection->ScrollSelectionIntoView(
491 ToSelectionType(aRawSelectionType), aRegion, aFlags);
494 NS_IMETHODIMP
495 TextInputSelectionController::RepaintSelection(
496 RawSelectionType aRawSelectionType) {
497 if (!mFrameSelection) {
498 return NS_ERROR_NULL_POINTER;
500 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
501 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
504 nsresult TextInputSelectionController::RepaintSelection(
505 nsPresContext* aPresContext, SelectionType aSelectionType) {
506 if (!mFrameSelection) {
507 return NS_ERROR_NULL_POINTER;
509 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
510 return frameSelection->RepaintSelection(aSelectionType);
513 NS_IMETHODIMP
514 TextInputSelectionController::SetCaretEnabled(bool enabled) {
515 if (!mPresShellWeak) {
516 return NS_ERROR_NOT_INITIALIZED;
518 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak);
519 if (!presShell) {
520 return NS_ERROR_FAILURE;
523 // tell the pres shell to enable the caret, rather than settings its
524 // visibility directly. this way the presShell's idea of caret visibility is
525 // maintained.
526 presShell->SetCaretEnabled(enabled);
528 return NS_OK;
531 NS_IMETHODIMP
532 TextInputSelectionController::SetCaretReadOnly(bool aReadOnly) {
533 if (!mPresShellWeak) {
534 return NS_ERROR_NOT_INITIALIZED;
536 nsresult rv;
537 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
538 if (!presShell) {
539 return NS_ERROR_FAILURE;
541 RefPtr<nsCaret> caret = presShell->GetCaret();
542 if (!caret) {
543 return NS_ERROR_FAILURE;
546 if (!mFrameSelection) {
547 return NS_ERROR_FAILURE;
550 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
551 if (selection) {
552 caret->SetCaretReadOnly(aReadOnly);
554 return NS_OK;
557 NS_IMETHODIMP
558 TextInputSelectionController::GetCaretEnabled(bool* _retval) {
559 return GetCaretVisible(_retval);
562 NS_IMETHODIMP
563 TextInputSelectionController::GetCaretVisible(bool* _retval) {
564 if (!mPresShellWeak) {
565 return NS_ERROR_NOT_INITIALIZED;
567 nsresult rv;
568 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
569 if (!presShell) {
570 return NS_ERROR_FAILURE;
572 RefPtr<nsCaret> caret = presShell->GetCaret();
573 if (!caret) {
574 return NS_ERROR_FAILURE;
576 *_retval = caret->IsVisible();
577 return NS_OK;
580 NS_IMETHODIMP
581 TextInputSelectionController::SetCaretVisibilityDuringSelection(
582 bool aVisibility) {
583 if (!mPresShellWeak) {
584 return NS_ERROR_NOT_INITIALIZED;
586 nsresult rv;
587 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
588 if (!presShell) {
589 return NS_ERROR_FAILURE;
591 RefPtr<nsCaret> caret = presShell->GetCaret();
592 if (!caret) {
593 return NS_ERROR_FAILURE;
595 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
596 if (selection) {
597 caret->SetVisibilityDuringSelection(aVisibility);
599 return NS_OK;
602 NS_IMETHODIMP
603 TextInputSelectionController::PhysicalMove(int16_t aDirection, int16_t aAmount,
604 bool aExtend) {
605 if (!mFrameSelection) {
606 return NS_ERROR_NULL_POINTER;
608 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
609 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
612 NS_IMETHODIMP
613 TextInputSelectionController::CharacterMove(bool aForward, bool aExtend) {
614 if (!mFrameSelection) {
615 return NS_ERROR_NULL_POINTER;
617 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
618 return frameSelection->CharacterMove(aForward, aExtend);
621 NS_IMETHODIMP
622 TextInputSelectionController::WordMove(bool aForward, bool aExtend) {
623 if (!mFrameSelection) {
624 return NS_ERROR_NULL_POINTER;
626 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
627 return frameSelection->WordMove(aForward, aExtend);
630 NS_IMETHODIMP
631 TextInputSelectionController::LineMove(bool aForward, bool aExtend) {
632 if (!mFrameSelection) {
633 return NS_ERROR_NULL_POINTER;
635 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
636 nsresult result = frameSelection->LineMove(aForward, aExtend);
637 if (NS_FAILED(result)) {
638 result = CompleteMove(aForward, aExtend);
640 return result;
643 NS_IMETHODIMP
644 TextInputSelectionController::IntraLineMove(bool aForward, bool aExtend) {
645 if (!mFrameSelection) {
646 return NS_ERROR_NULL_POINTER;
648 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
649 return frameSelection->IntraLineMove(aForward, aExtend);
652 NS_IMETHODIMP
653 TextInputSelectionController::PageMove(bool aForward, bool aExtend) {
654 // expected behavior for PageMove is to scroll AND move the caret
655 // and to remain relative position of the caret in view. see Bug 4302.
656 if (mScrollFrame) {
657 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
658 nsIFrame* scrollFrame = do_QueryFrame(mScrollFrame);
659 // We won't scroll parent scrollable element of mScrollFrame. Therefore,
660 // this may be handled when mScrollFrame is completely outside of the view.
661 // In such case, user may be confused since they might have wanted to
662 // scroll a parent scrollable element. For making clearer which element
663 // handles PageDown/PageUp, we should move selection into view even if
664 // selection is not changed.
665 return frameSelection->PageMove(aForward, aExtend, scrollFrame,
666 nsFrameSelection::SelectionIntoView::Yes);
668 // Similarly, if there is no scrollable frame, we should move the editor
669 // frame into the view for making it clearer which element handles
670 // PageDown/PageUp.
671 return ScrollSelectionIntoView(
672 nsISelectionController::SELECTION_NORMAL,
673 nsISelectionController::SELECTION_FOCUS_REGION,
674 nsISelectionController::SCROLL_SYNCHRONOUS |
675 nsISelectionController::SCROLL_FOR_CARET_MOVE);
678 NS_IMETHODIMP
679 TextInputSelectionController::CompleteScroll(bool aForward) {
680 if (!mScrollFrame) {
681 return NS_ERROR_NOT_INITIALIZED;
684 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE,
685 ScrollMode::Instant);
686 return NS_OK;
689 NS_IMETHODIMP
690 TextInputSelectionController::CompleteMove(bool aForward, bool aExtend) {
691 if (NS_WARN_IF(!mFrameSelection)) {
692 return NS_ERROR_NULL_POINTER;
694 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
696 // grab the parent / root DIV for this text widget
697 nsIContent* parentDIV = frameSelection->GetLimiter();
698 if (!parentDIV) {
699 return NS_ERROR_UNEXPECTED;
702 // make the caret be either at the very beginning (0) or the very end
703 int32_t offset = 0;
704 CaretAssociationHint hint = CaretAssociationHint::Before;
705 if (aForward) {
706 offset = parentDIV->GetChildCount();
708 // Prevent the caret from being placed after the last
709 // BR node in the content tree!
711 if (offset > 0) {
712 nsIContent* child = parentDIV->GetLastChild();
714 if (child->IsHTMLElement(nsGkAtoms::br)) {
715 --offset;
716 hint = CaretAssociationHint::After; // for Bug 106855
721 const RefPtr<nsIContent> pinnedParentDIV{parentDIV};
722 const nsFrameSelection::FocusMode focusMode =
723 aExtend ? nsFrameSelection::FocusMode::kExtendSelection
724 : nsFrameSelection::FocusMode::kCollapseToNewPoint;
725 frameSelection->HandleClick(pinnedParentDIV, offset, offset, focusMode, hint);
727 // if we got this far, attempt to scroll no matter what the above result is
728 return CompleteScroll(aForward);
731 NS_IMETHODIMP
732 TextInputSelectionController::ScrollPage(bool aForward) {
733 if (!mScrollFrame) {
734 return NS_ERROR_NOT_INITIALIZED;
737 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
738 ScrollMode::Smooth);
739 return NS_OK;
742 NS_IMETHODIMP
743 TextInputSelectionController::ScrollLine(bool aForward) {
744 if (!mScrollFrame) {
745 return NS_ERROR_NOT_INITIALIZED;
748 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::LINES,
749 ScrollMode::Smooth);
750 return NS_OK;
753 NS_IMETHODIMP
754 TextInputSelectionController::ScrollCharacter(bool aRight) {
755 if (!mScrollFrame) {
756 return NS_ERROR_NOT_INITIALIZED;
759 mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0), ScrollUnit::LINES,
760 ScrollMode::Smooth);
761 return NS_OK;
764 void TextInputSelectionController::SelectionWillTakeFocus() {
765 if (mFrameSelection) {
766 if (PresShell* shell = mFrameSelection->GetPresShell()) {
767 shell->FrameSelectionWillTakeFocus(*mFrameSelection);
772 void TextInputSelectionController::SelectionWillLoseFocus() {
773 if (mFrameSelection) {
774 if (PresShell* shell = mFrameSelection->GetPresShell()) {
775 shell->FrameSelectionWillLoseFocus(*mFrameSelection);
780 /*****************************************************************************
781 * mozilla::TextInputListener
782 *****************************************************************************/
784 TextInputListener::TextInputListener(TextControlElement* aTxtCtrlElement)
785 : mFrame(nullptr),
786 mTxtCtrlElement(aTxtCtrlElement),
787 mTextControlState(aTxtCtrlElement ? aTxtCtrlElement->GetTextControlState()
788 : nullptr),
789 mSelectionWasCollapsed(true),
790 mHadUndoItems(false),
791 mHadRedoItems(false),
792 mSettingValue(false),
793 mSetValueChanged(true),
794 mListeningToSelectionChange(false) {}
796 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
797 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
799 NS_INTERFACE_MAP_BEGIN(TextInputListener)
800 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
801 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
802 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
803 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
804 NS_INTERFACE_MAP_END
806 NS_IMPL_CYCLE_COLLECTION_CLASS(TextInputListener)
807 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TextInputListener)
808 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
809 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
810 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TextInputListener)
811 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
813 void TextInputListener::OnSelectionChange(Selection& aSelection,
814 int16_t aReason) {
815 if (!mListeningToSelectionChange) {
816 return;
819 AutoWeakFrame weakFrame = mFrame;
821 // Fire the select event
822 // The specs don't exactly say when we should fire the select event.
823 // IE: Whenever you add/remove a character to/from the selection. Also
824 // each time for select all. Also if you get to the end of the text
825 // field you will get new event for each keypress or a continuous
826 // stream of events if you use the mouse. IE will fire select event
827 // when the selection collapses to nothing if you are holding down
828 // the shift or mouse button.
829 // Mozilla: If we have non-empty selection we will fire a new event for each
830 // keypress (or mouseup) if the selection changed. Mozilla will also
831 // create the event each time select all is called, even if
832 // everything was previously selected, because technically select all
833 // will first collapse and then extend. Mozilla will never create an
834 // event if the selection collapses to nothing.
835 // FYI: If you want to skip dispatching eFormSelect event and if there are no
836 // event listeners, you can refer
837 // nsPIDOMWindow::HasFormSelectEventListeners(), but be careful about
838 // some C++ event handlers, e.g., HTMLTextAreaElement::PostHandleEvent().
839 bool collapsed = aSelection.IsCollapsed();
840 if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
841 nsISelectionListener::KEYPRESS_REASON |
842 nsISelectionListener::SELECTALL_REASON))) {
843 if (nsCOMPtr<nsIContent> content = mFrame->GetContent()) {
844 if (nsCOMPtr<Document> doc = content->GetComposedDoc()) {
845 if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
846 nsEventStatus status = nsEventStatus_eIgnore;
847 WidgetEvent event(true, eFormSelect);
849 presShell->HandleEventWithTarget(&event, mFrame, content, &status);
855 // if the collapsed state did not change, don't fire notifications
856 if (collapsed == mSelectionWasCollapsed) {
857 return;
860 mSelectionWasCollapsed = collapsed;
862 if (!weakFrame.IsAlive() || !mFrame ||
863 !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
864 return;
867 UpdateTextInputCommands(u"select"_ns);
870 MOZ_CAN_RUN_SCRIPT
871 static void DoCommandCallback(Command aCommand, void* aData) {
872 nsTextControlFrame* frame = static_cast<nsTextControlFrame*>(aData);
873 nsIContent* content = frame->GetContent();
875 nsCOMPtr<nsIControllers> controllers;
876 HTMLInputElement* input = HTMLInputElement::FromNode(content);
877 if (input) {
878 input->GetControllers(getter_AddRefs(controllers));
879 } else {
880 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(content);
882 if (textArea) {
883 textArea->GetControllers(getter_AddRefs(controllers));
887 if (!controllers) {
888 NS_WARNING("Could not get controllers");
889 return;
892 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
894 nsCOMPtr<nsIController> controller;
895 controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
896 if (!controller) {
897 return;
900 bool commandEnabled;
901 if (NS_WARN_IF(NS_FAILED(
902 controller->IsCommandEnabled(commandStr, &commandEnabled)))) {
903 return;
905 if (commandEnabled) {
906 controller->DoCommand(commandStr);
910 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
911 TextInputListener::HandleEvent(Event* aEvent) {
912 if (aEvent->DefaultPrevented()) {
913 return NS_OK;
916 if (!aEvent->IsTrusted()) {
917 return NS_OK;
920 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
921 if (!keyEvent) {
922 return NS_ERROR_UNEXPECTED;
925 WidgetKeyboardEvent* widgetKeyEvent =
926 aEvent->WidgetEventPtr()->AsKeyboardEvent();
927 if (!widgetKeyEvent) {
928 return NS_ERROR_UNEXPECTED;
932 auto* input = HTMLInputElement::FromNode(mTxtCtrlElement);
933 if (input && input->StepsInputValue(*widgetKeyEvent)) {
934 // As an special case, don't handle key events that would step the value
935 // of our <input type=number>.
936 return NS_OK;
940 auto ExecuteOurShortcutKeys = [&](TextControlElement& aTextControlElement)
941 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
942 KeyEventHandler* keyHandlers = ShortcutKeys::GetHandlers(
943 aTextControlElement.IsTextArea() ? HandlerType::eTextArea
944 : HandlerType::eInput);
946 RefPtr<nsAtom> eventTypeAtom =
947 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent);
948 for (KeyEventHandler* handler = keyHandlers; handler;
949 handler = handler->GetNextHandler()) {
950 if (!handler->EventTypeEquals(eventTypeAtom)) {
951 continue;
954 if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) {
955 continue;
958 // XXX Do we execute only one handler even if the handler neither stops
959 // propagation nor prevents default of the event?
960 nsresult rv = handler->ExecuteHandler(&aTextControlElement, aEvent);
961 if (NS_SUCCEEDED(rv)) {
962 return true;
965 return false;
968 auto ExecuteNativeKeyBindings =
969 [&](TextControlElement& aTextControlElement)
970 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
971 if (widgetKeyEvent->mMessage != eKeyPress) {
972 return false;
975 NativeKeyBindingsType nativeKeyBindingsType =
976 aTextControlElement.IsTextArea()
977 ? NativeKeyBindingsType::MultiLineEditor
978 : NativeKeyBindingsType::SingleLineEditor;
980 nsIWidget* widget = widgetKeyEvent->mWidget;
981 // If the event is created by chrome script, the widget is nullptr.
982 if (MOZ_UNLIKELY(!widget)) {
983 widget = mFrame->GetNearestWidget();
984 if (MOZ_UNLIKELY(NS_WARN_IF(!widget))) {
985 return false;
989 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
990 // If the event is created by chrome script, it is nullptr but we need to
991 // execute native key bindings. Therefore, we need to set widget to
992 // WidgetEvent::mWidget temporarily.
993 AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(widgetKeyEvent->mWidget);
994 widgetKeyEvent->mWidget = widget;
995 if (widgetKeyEvent->ExecuteEditCommands(nativeKeyBindingsType,
996 DoCommandCallback, mFrame)) {
997 aEvent->PreventDefault();
998 return true;
1000 return false;
1003 OwningNonNull<TextControlElement> textControlElement(*mTxtCtrlElement);
1004 if (StaticPrefs::
1005 ui_key_textcontrol_prefer_native_key_bindings_over_builtin_shortcut_key_definitions()) {
1006 if (!ExecuteNativeKeyBindings(textControlElement)) {
1007 ExecuteOurShortcutKeys(textControlElement);
1009 } else {
1010 if (!ExecuteOurShortcutKeys(textControlElement)) {
1011 ExecuteNativeKeyBindings(textControlElement);
1014 return NS_OK;
1017 nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) {
1018 if (mFrame) {
1019 // XXX Do we still need this or can we just remove the mFrame and
1020 // frame.IsAlive() conditions below?
1021 AutoWeakFrame weakFrame = mFrame;
1023 // Update the undo / redo menus
1025 size_t numUndoItems = aTextEditor.NumberOfUndoItems();
1026 size_t numRedoItems = aTextEditor.NumberOfRedoItems();
1027 if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
1028 (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
1029 // Modify the menu if undo or redo items are different
1030 UpdateTextInputCommands(u"undo"_ns);
1032 mHadUndoItems = numUndoItems != 0;
1033 mHadRedoItems = numRedoItems != 0;
1036 if (weakFrame.IsAlive()) {
1037 HandleValueChanged(aTextEditor);
1041 return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
1044 void TextInputListener::HandleValueChanged(TextEditor& aTextEditor) {
1045 // Make sure we know we were changed (do NOT set this to false if there are
1046 // no undo items; JS could change the value and we'd still need to save it)
1047 if (mSetValueChanged) {
1048 mTxtCtrlElement->SetValueChanged(true);
1051 if (!mSettingValue) {
1052 // NOTE(emilio): execCommand might get here even though it might not be a
1053 // "proper" user-interactive change. Might be worth reconsidering which
1054 // ValueChangeKind are we passing down.
1055 mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction,
1056 aTextEditor.IsEmpty(), nullptr);
1057 if (mTextControlState) {
1058 mTextControlState->ClearLastInteractiveValue();
1063 nsresult TextInputListener::UpdateTextInputCommands(
1064 const nsAString& aCommandsToUpdate) {
1065 nsIContent* content = mFrame->GetContent();
1066 if (NS_WARN_IF(!content)) {
1067 return NS_ERROR_FAILURE;
1069 nsCOMPtr<Document> doc = content->GetComposedDoc();
1070 if (NS_WARN_IF(!doc)) {
1071 return NS_ERROR_FAILURE;
1073 nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1074 if (NS_WARN_IF(!domWindow)) {
1075 return NS_ERROR_FAILURE;
1077 domWindow->UpdateCommands(aCommandsToUpdate);
1078 return NS_OK;
1081 /*****************************************************************************
1082 * mozilla::AutoTextControlHandlingState
1084 * This class is temporarily created in the stack and can manage nested
1085 * handling state of TextControlState. While this instance exists, lifetime of
1086 * TextControlState which created the instance is guaranteed. In other words,
1087 * you can use this class as "kungFuDeathGrip" for TextControlState.
1088 *****************************************************************************/
1090 enum class TextControlAction {
1091 CommitComposition,
1092 Destructor,
1093 PrepareEditor,
1094 SetRangeText,
1095 SetSelectionRange,
1096 SetValue,
1097 UnbindFromFrame,
1098 Unlink,
1101 class MOZ_STACK_CLASS AutoTextControlHandlingState {
1102 public:
1103 AutoTextControlHandlingState() = delete;
1104 explicit AutoTextControlHandlingState(const AutoTextControlHandlingState&) =
1105 delete;
1106 AutoTextControlHandlingState(AutoTextControlHandlingState&&) = delete;
1107 void operator=(AutoTextControlHandlingState&) = delete;
1108 void operator=(const AutoTextControlHandlingState&) = delete;
1111 * Generic constructor. If TextControlAction does not require additional
1112 * data, must use this constructor.
1114 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1115 TextControlState& aTextControlState, TextControlAction aTextControlAction)
1116 : mParent(aTextControlState.mHandlingState),
1117 mTextControlState(aTextControlState),
1118 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1119 mTextInputListener(aTextControlState.mTextListener),
1120 mTextControlAction(aTextControlAction) {
1121 MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue,
1122 "Use specific constructor");
1123 MOZ_DIAGNOSTIC_ASSERT_IF(
1124 !aTextControlState.mTextListener,
1125 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1126 mTextControlState.mHandlingState = this;
1127 if (Is(TextControlAction::CommitComposition)) {
1128 MOZ_ASSERT(mParent);
1129 MOZ_ASSERT(mParent->Is(TextControlAction::SetValue));
1130 // If we're trying to commit composition before handling SetValue,
1131 // the parent old values will be outdated so that we need to clear
1132 // them.
1133 mParent->InvalidateOldValue();
1138 * TextControlAction::SetValue specific constructor. Current setting value
1139 * must be specified and the creator should check whether we succeeded to
1140 * allocate memory for line breaker conversion.
1142 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1143 TextControlState& aTextControlState, TextControlAction aTextControlAction,
1144 const nsAString& aSettingValue, const nsAString* aOldValue,
1145 const ValueSetterOptions& aOptions, ErrorResult& aRv)
1146 : mParent(aTextControlState.mHandlingState),
1147 mTextControlState(aTextControlState),
1148 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1149 mTextInputListener(aTextControlState.mTextListener),
1150 mSettingValue(aSettingValue),
1151 mOldValue(aOldValue),
1152 mValueSetterOptions(aOptions),
1153 mTextControlAction(aTextControlAction) {
1154 MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue,
1155 "Use generic constructor");
1156 MOZ_DIAGNOSTIC_ASSERT_IF(
1157 !aTextControlState.mTextListener,
1158 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1159 mTextControlState.mHandlingState = this;
1160 if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue, fallible)) {
1161 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1162 return;
1164 // Update all setting value's new value because older value shouldn't
1165 // overwrite newer value.
1166 if (mParent) {
1167 // If SetValue is nested, parents cannot trust their old value anymore.
1168 // So, we need to clear them.
1169 mParent->UpdateSettingValueAndInvalidateOldValue(mSettingValue);
1173 MOZ_CAN_RUN_SCRIPT ~AutoTextControlHandlingState() {
1174 mTextControlState.mHandlingState = mParent;
1175 if (!mParent && mTextControlStateDestroyed) {
1176 mTextControlState.DeleteOrCacheForReuse();
1178 if (!mTextControlStateDestroyed && mPrepareEditorLater) {
1179 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
1180 MOZ_ASSERT(Is(TextControlAction::SetValue));
1181 mTextControlState.PrepareEditor(&mSettingValue);
1185 void OnDestroyTextControlState() {
1186 if (IsHandling(TextControlAction::Destructor)) {
1187 // Do nothing since mTextContrlState.DeleteOrCacheForReuse() has
1188 // already been called.
1189 return;
1191 mTextControlStateDestroyed = true;
1192 if (mParent) {
1193 mParent->OnDestroyTextControlState();
1197 void PrepareEditorLater() {
1198 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1199 MOZ_ASSERT(!IsHandling(TextControlAction::PrepareEditor));
1200 // Look for the top most SetValue.
1201 AutoTextControlHandlingState* settingValue = nullptr;
1202 for (AutoTextControlHandlingState* handlingSomething = this;
1203 handlingSomething; handlingSomething = handlingSomething->mParent) {
1204 if (handlingSomething->Is(TextControlAction::SetValue)) {
1205 settingValue = handlingSomething;
1208 settingValue->mPrepareEditorLater = true;
1212 * WillSetValueWithTextEditor() is called when TextControlState sets
1213 * value with its mTextEditor.
1215 void WillSetValueWithTextEditor() {
1216 MOZ_ASSERT(Is(TextControlAction::SetValue));
1217 MOZ_ASSERT(mTextControlState.mBoundFrame);
1218 mTextControlFrame = mTextControlState.mBoundFrame;
1219 // If we'reemulating user input, we don't need to manage mTextInputListener
1220 // by ourselves since everything should be handled by TextEditor as normal
1221 // user input.
1222 if (mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1223 return;
1225 // Otherwise, if we're setting the value programatically, we need to manage
1226 // mTextInputListener by ourselves since TextEditor users special path
1227 // for the performance.
1228 mTextInputListener->SettingValue(true);
1229 mTextInputListener->SetValueChanged(
1230 mValueSetterOptions.contains(ValueSetterOption::SetValueChanged));
1231 mEditActionHandled = false;
1232 // Even if falling back to `TextControlState::SetValueWithoutTextEditor()`
1233 // due to editor destruction, it shouldn't dispatch "beforeinput" event
1234 // anymore. Therefore, we should mark that we've already dispatched
1235 // "beforeinput" event.
1236 WillDispatchBeforeInputEvent();
1240 * WillDispatchBeforeInputEvent() is called immediately before dispatching
1241 * "beforeinput" event in `TextControlState`.
1243 void WillDispatchBeforeInputEvent() {
1244 mBeforeInputEventHasBeenDispatched = true;
1248 * OnEditActionHandled() is called when the TextEditor handles something
1249 * and immediately before dispatching "input" event.
1251 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled() {
1252 MOZ_ASSERT(!mEditActionHandled);
1253 mEditActionHandled = true;
1254 if (!Is(TextControlAction::SetValue)) {
1255 return NS_OK;
1257 if (!mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
1258 mTextInputListener->SetValueChanged(true);
1259 mTextInputListener->SettingValue(
1260 mParent && mParent->IsHandling(TextControlAction::SetValue));
1262 if (!IsOriginalTextControlFrameAlive()) {
1263 return SetValueWithoutTextEditorAgain() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
1265 // The new value never includes line breaks caused by hard-wrap.
1266 // So, mCachedValue can always cache the new value.
1267 nsITextControlFrame* textControlFrame =
1268 do_QueryFrame(mTextControlFrame.GetFrame());
1269 return static_cast<nsTextControlFrame*>(textControlFrame)
1270 ->CacheValue(mSettingValue, fallible)
1271 ? NS_OK
1272 : NS_ERROR_OUT_OF_MEMORY;
1276 * SetValueWithoutTextEditorAgain() should be called if the frame for
1277 * mTextControlState was destroyed during setting value.
1279 [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditorAgain() {
1280 MOZ_ASSERT(!IsOriginalTextControlFrameAlive());
1281 // If the frame was destroyed because of a flush somewhere inside
1282 // TextEditor, mBoundFrame here will be nullptr. But it's also
1283 // possible for the frame to go away because of another reason (such
1284 // as deleting the existing selection -- see bug 574558), in which
1285 // case we don't need to reset the value here.
1286 if (mTextControlState.mBoundFrame) {
1287 return true;
1289 // XXX It's odd to drop flags except
1290 // ValueSetterOption::SetValueChanged.
1291 // Probably, this intended to drop ValueSetterOption::BySetUserInputAPI
1292 // and ValueSetterOption::ByContentAPI, but other flags are added later.
1293 ErrorResult error;
1294 AutoTextControlHandlingState handlingSetValueWithoutEditor(
1295 mTextControlState, TextControlAction::SetValue, mSettingValue,
1296 mOldValue, mValueSetterOptions & ValueSetterOption::SetValueChanged,
1297 error);
1298 if (error.Failed()) {
1299 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
1300 error.SuppressException();
1301 return false;
1303 return mTextControlState.SetValueWithoutTextEditor(
1304 handlingSetValueWithoutEditor);
1307 bool IsTextControlStateDestroyed() const {
1308 return mTextControlStateDestroyed;
1310 bool IsOriginalTextControlFrameAlive() const {
1311 return const_cast<AutoTextControlHandlingState*>(this)
1312 ->mTextControlFrame.IsAlive();
1314 bool HasEditActionHandled() const { return mEditActionHandled; }
1315 bool HasBeforeInputEventDispatched() const {
1316 return mBeforeInputEventHasBeenDispatched;
1318 bool Is(TextControlAction aTextControlAction) const {
1319 return mTextControlAction == aTextControlAction;
1321 bool IsHandling(TextControlAction aTextControlAction) const {
1322 if (mTextControlAction == aTextControlAction) {
1323 return true;
1325 return mParent && mParent->IsHandling(aTextControlAction);
1327 TextControlElement* GetTextControlElement() const { return mTextCtrlElement; }
1328 TextInputListener* GetTextInputListener() const { return mTextInputListener; }
1329 const ValueSetterOptions& ValueSetterOptionsRef() const {
1330 MOZ_ASSERT(Is(TextControlAction::SetValue));
1331 return mValueSetterOptions;
1333 const nsAString* GetOldValue() const {
1334 MOZ_ASSERT(Is(TextControlAction::SetValue));
1335 return mOldValue;
1337 const nsString& GetSettingValue() const {
1338 MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
1339 if (mTextControlAction == TextControlAction::SetValue) {
1340 return mSettingValue;
1342 return mParent->GetSettingValue();
1345 private:
1346 void UpdateSettingValueAndInvalidateOldValue(const nsString& aSettingValue) {
1347 if (mTextControlAction == TextControlAction::SetValue) {
1348 mSettingValue = aSettingValue;
1350 mOldValue = nullptr;
1351 if (mParent) {
1352 mParent->UpdateSettingValueAndInvalidateOldValue(aSettingValue);
1355 void InvalidateOldValue() {
1356 mOldValue = nullptr;
1357 if (mParent) {
1358 mParent->InvalidateOldValue();
1362 AutoTextControlHandlingState* const mParent;
1363 TextControlState& mTextControlState;
1364 // mTextControlFrame should be set immediately before calling methods
1365 // which may destroy the frame. Then, you can check whether the frame
1366 // was destroyed/replaced.
1367 AutoWeakFrame mTextControlFrame;
1368 // mTextCtrlElement grabs TextControlState::mTextCtrlElement since
1369 // if the text control element releases mTextControlState, only this
1370 // can guarantee the instance of the text control element.
1371 RefPtr<TextControlElement> const mTextCtrlElement;
1372 // mTextInputListener grabs TextControlState::mTextListener because if
1373 // TextControlState is unbind from the frame, it's released.
1374 RefPtr<TextInputListener> const mTextInputListener;
1375 nsAutoString mSettingValue;
1376 const nsAString* mOldValue = nullptr;
1377 ValueSetterOptions mValueSetterOptions;
1378 TextControlAction const mTextControlAction;
1379 bool mTextControlStateDestroyed = false;
1380 bool mEditActionHandled = false;
1381 bool mPrepareEditorLater = false;
1382 bool mBeforeInputEventHasBeenDispatched = false;
1385 /*****************************************************************************
1386 * mozilla::TextControlState
1387 *****************************************************************************/
1390 * For avoiding allocation cost of the instance, we should reuse instances
1391 * as far as possible.
1393 * FYI: `25` is just a magic number considered without enough investigation,
1394 * but at least, this value must not make damage for footprint.
1395 * Feel free to change it if you find better number.
1397 static constexpr size_t kMaxCountOfCacheToReuse = 25;
1398 static AutoTArray<void*, kMaxCountOfCacheToReuse>* sReleasedInstances = nullptr;
1399 static bool sHasShutDown = false;
1401 TextControlState::TextControlState(TextControlElement* aOwningElement)
1402 : mTextCtrlElement(aOwningElement),
1403 mEverInited(false),
1404 mEditorInitialized(false),
1405 mValueTransferInProgress(false),
1406 mSelectionCached(true)
1407 // When adding more member variable initializations here, add the same
1408 // also to ::Construct.
1410 MOZ_COUNT_CTOR(TextControlState);
1411 static_assert(sizeof(*this) <= 128,
1412 "Please keep small TextControlState as far as possible");
1415 TextControlState* TextControlState::Construct(
1416 TextControlElement* aOwningElement) {
1417 void* mem;
1418 if (sReleasedInstances && !sReleasedInstances->IsEmpty()) {
1419 mem = sReleasedInstances->PopLastElement();
1420 } else {
1421 mem = moz_xmalloc(sizeof(TextControlState));
1424 return new (mem) TextControlState(aOwningElement);
1427 TextControlState::~TextControlState() {
1428 MOZ_ASSERT(!mHandlingState);
1429 MOZ_COUNT_DTOR(TextControlState);
1430 AutoTextControlHandlingState handlingDesctructor(
1431 *this, TextControlAction::Destructor);
1432 Clear();
1435 void TextControlState::Shutdown() {
1436 sHasShutDown = true;
1437 if (sReleasedInstances) {
1438 for (void* mem : *sReleasedInstances) {
1439 free(mem);
1441 delete sReleasedInstances;
1445 void TextControlState::Destroy() {
1446 // If we're handling something, we should be deleted later.
1447 if (mHandlingState) {
1448 mHandlingState->OnDestroyTextControlState();
1449 return;
1451 DeleteOrCacheForReuse();
1452 // Note that this instance may have already been deleted here. Don't touch
1453 // any members.
1456 void TextControlState::DeleteOrCacheForReuse() {
1457 MOZ_ASSERT(!IsBusy());
1459 void* mem = this;
1460 this->~TextControlState();
1462 // If we can cache this instance, we should do it instead of deleting it.
1463 if (!sHasShutDown && (!sReleasedInstances || sReleasedInstances->Length() <
1464 kMaxCountOfCacheToReuse)) {
1465 // Put this instance to the cache. Note that now, the array may be full,
1466 // but it's not problem to cache more instances than kMaxCountOfCacheToReuse
1467 // because it just requires reallocation cost of the array buffer.
1468 if (!sReleasedInstances) {
1469 sReleasedInstances = new AutoTArray<void*, kMaxCountOfCacheToReuse>;
1471 sReleasedInstances->AppendElement(mem);
1472 } else {
1473 free(mem);
1477 nsresult TextControlState::OnEditActionHandled() {
1478 return mHandlingState ? mHandlingState->OnEditActionHandled() : NS_OK;
1481 Element* TextControlState::GetRootNode() {
1482 return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1485 Element* TextControlState::GetPreviewNode() {
1486 return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1489 void TextControlState::Clear() {
1490 MOZ_ASSERT(mHandlingState);
1491 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Destructor) ||
1492 mHandlingState->Is(TextControlAction::Unlink));
1493 if (mTextEditor) {
1494 mTextEditor->SetTextInputListener(nullptr);
1497 if (mBoundFrame) {
1498 // Oops, we still have a frame!
1499 // This should happen when the type of a text input control is being changed
1500 // to something which is not a text control. In this case, we should
1501 // pretend that a frame is being destroyed, and clean up after ourselves
1502 // properly.
1503 UnbindFromFrame(mBoundFrame);
1504 mTextEditor = nullptr;
1505 } else {
1506 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1507 // for us.
1508 DestroyEditor();
1509 MOZ_DIAGNOSTIC_ASSERT(!mBoundFrame || !mTextEditor);
1511 mTextListener = nullptr;
1514 void TextControlState::Unlink() {
1515 AutoTextControlHandlingState handlingUnlink(*this, TextControlAction::Unlink);
1516 UnlinkInternal();
1519 void TextControlState::UnlinkInternal() {
1520 MOZ_ASSERT(mHandlingState);
1521 MOZ_ASSERT(mHandlingState->Is(TextControlAction::Unlink));
1522 TextControlState* tmp = this;
1523 tmp->Clear();
1524 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
1525 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
1528 void TextControlState::Traverse(nsCycleCollectionTraversalCallback& cb) {
1529 TextControlState* tmp = this;
1530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
1531 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
1534 nsFrameSelection* TextControlState::GetConstFrameSelection() {
1535 return mSelCon ? mSelCon->GetConstFrameSelection() : nullptr;
1538 TextEditor* TextControlState::GetTextEditor() {
1539 // Note that if the instance is destroyed in PrepareEditor(), it returns
1540 // NS_ERROR_NOT_INITIALIZED so that we don't need to create kungFuDeathGrip
1541 // in this hot path.
1542 if (!mTextEditor && NS_WARN_IF(NS_FAILED(PrepareEditor()))) {
1543 return nullptr;
1545 return mTextEditor;
1548 TextEditor* TextControlState::GetTextEditorWithoutCreation() const {
1549 return mTextEditor;
1552 nsISelectionController* TextControlState::GetSelectionController() const {
1553 return mSelCon;
1556 // Helper class, used below in BindToFrame().
1557 class PrepareEditorEvent : public Runnable {
1558 public:
1559 PrepareEditorEvent(TextControlState& aState, nsIContent* aOwnerContent,
1560 const nsAString& aCurrentValue)
1561 : Runnable("PrepareEditorEvent"),
1562 mState(&aState),
1563 mOwnerContent(aOwnerContent),
1564 mCurrentValue(aCurrentValue) {
1565 aState.mValueTransferInProgress = true;
1568 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
1569 if (NS_WARN_IF(!mState)) {
1570 return NS_ERROR_NULL_POINTER;
1573 // Transfer the saved value to the editor if we have one
1574 const nsAString* value = nullptr;
1575 if (!mCurrentValue.IsEmpty()) {
1576 value = &mCurrentValue;
1579 nsAutoScriptBlocker scriptBlocker;
1581 mState->PrepareEditor(value);
1583 mState->mValueTransferInProgress = false;
1585 return NS_OK;
1588 private:
1589 WeakPtr<TextControlState> mState;
1590 nsCOMPtr<nsIContent> mOwnerContent; // strong reference
1591 nsAutoString mCurrentValue;
1594 nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
1595 MOZ_ASSERT(
1596 !nsContentUtils::IsSafeToRunScript(),
1597 "TextControlState::BindToFrame() has to be called with script blocker");
1598 NS_ASSERTION(aFrame, "The frame to bind to should be valid");
1599 if (!aFrame) {
1600 return NS_ERROR_INVALID_ARG;
1603 NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
1604 if (mBoundFrame) {
1605 return NS_ERROR_FAILURE;
1608 // If we'll need to transfer our current value to the editor, save it before
1609 // binding to the frame.
1610 nsAutoString currentValue;
1611 if (mTextEditor) {
1612 GetValue(currentValue, true, /* aForDisplay = */ false);
1615 mBoundFrame = aFrame;
1617 Element* rootNode = aFrame->GetRootNode();
1618 MOZ_ASSERT(rootNode);
1620 PresShell* presShell = aFrame->PresContext()->GetPresShell();
1621 MOZ_ASSERT(presShell);
1623 // Create a SelectionController
1624 mSelCon = new TextInputSelectionController(presShell, rootNode);
1625 MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
1626 mTextListener = new TextInputListener(mTextCtrlElement);
1628 mTextListener->SetFrame(mBoundFrame);
1630 // Editor will override this as needed from InitializeSelection.
1631 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
1633 // Get the caret and make it a selection listener.
1634 // FYI: It's safe to use raw pointer for calling
1635 // Selection::AddSelectionListner() because it only appends the listener
1636 // to its internal array.
1637 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
1638 if (selection) {
1639 RefPtr<nsCaret> caret = presShell->GetCaret();
1640 if (caret) {
1641 selection->AddSelectionListener(caret);
1643 mTextListener->StartToListenToSelectionChange();
1646 // If an editor exists from before, prepare it for usage
1647 if (mTextEditor) {
1648 if (NS_WARN_IF(!mTextCtrlElement)) {
1649 return NS_ERROR_FAILURE;
1652 // Set the correct direction on the newly created root node
1653 if (mTextEditor->IsRightToLeft()) {
1654 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, false);
1655 } else if (mTextEditor->IsLeftToRight()) {
1656 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"ltr"_ns, false);
1657 } else {
1658 // otherwise, inherit the content node's direction
1661 nsContentUtils::AddScriptRunner(
1662 new PrepareEditorEvent(*this, mTextCtrlElement, currentValue));
1665 return NS_OK;
1668 struct MOZ_STACK_CLASS PreDestroyer {
1669 void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
1670 ~PreDestroyer() {
1671 if (mTextEditor) {
1672 // In this case, we don't need to restore the unmasked range of password
1673 // editor.
1674 UniquePtr<PasswordMaskData> passwordMaskData = mTextEditor->PreDestroy();
1677 void Swap(RefPtr<TextEditor>& aTextEditor) {
1678 return mTextEditor.swap(aTextEditor);
1681 private:
1682 RefPtr<TextEditor> mTextEditor;
1685 nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
1686 if (!mBoundFrame) {
1687 // Cannot create an editor without a bound frame.
1688 // Don't return a failure code, because js callers can't handle that.
1689 return NS_OK;
1692 if (mEditorInitialized) {
1693 // Do not initialize the editor multiple times.
1694 return NS_OK;
1697 AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
1699 if (mHandlingState) {
1700 // Don't attempt to initialize recursively!
1701 if (mHandlingState->IsHandling(TextControlAction::PrepareEditor)) {
1702 return NS_ERROR_NOT_INITIALIZED;
1704 // Reschedule creating editor later if we're setting value.
1705 if (mHandlingState->IsHandling(TextControlAction::SetValue)) {
1706 mHandlingState->PrepareEditorLater();
1707 return NS_ERROR_NOT_INITIALIZED;
1711 MOZ_ASSERT(mTextCtrlElement);
1713 AutoTextControlHandlingState preparingEditor(
1714 *this, TextControlAction::PrepareEditor);
1716 // Note that we don't check mTextEditor here, because we might already have
1717 // one around, in which case we don't create a new one, and we'll just tie
1718 // the required machinery to it.
1720 nsPresContext* presContext = mBoundFrame->PresContext();
1721 PresShell* presShell = presContext->GetPresShell();
1723 // Setup the editor flags
1725 // Spell check is diabled at creation time. It is enabled once
1726 // the editor comes into focus.
1727 uint32_t editorFlags = nsIEditor::eEditorSkipSpellCheck;
1729 if (IsSingleLineTextControl()) {
1730 editorFlags |= nsIEditor::eEditorSingleLineMask;
1732 if (IsPasswordTextControl()) {
1733 editorFlags |= nsIEditor::eEditorPasswordMask;
1736 bool shouldInitializeEditor = false;
1737 RefPtr<TextEditor> newTextEditor; // the editor that we might create
1738 PreDestroyer preDestroyer;
1739 if (!mTextEditor) {
1740 shouldInitializeEditor = true;
1742 // Create an editor
1743 newTextEditor = new TextEditor();
1744 preDestroyer.Init(newTextEditor);
1746 // Make sure we clear out the non-breaking space before we initialize the
1747 // editor
1748 nsresult rv = mBoundFrame->UpdateValueDisplay(true, true);
1749 if (NS_FAILED(rv)) {
1750 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1751 return rv;
1753 } else {
1754 if (aValue || !mEditorInitialized) {
1755 // Set the correct value in the root node
1756 nsresult rv =
1757 mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
1758 if (NS_FAILED(rv)) {
1759 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1760 return rv;
1764 newTextEditor = mTextEditor; // just pretend that we have a new editor!
1766 // Don't lose application flags in the process.
1767 if (newTextEditor->IsMailEditor()) {
1768 editorFlags |= nsIEditor::eEditorMailMask;
1772 // Get the current value of the textfield from the content.
1773 // Note that if we've created a new editor, mTextEditor is null at this stage,
1774 // so we will get the real value from the content.
1775 nsAutoString defaultValue;
1776 if (aValue) {
1777 defaultValue = *aValue;
1778 } else {
1779 GetValue(defaultValue, true, /* aForDisplay = */ true);
1782 if (!mEditorInitialized) {
1783 // Now initialize the editor.
1785 // NOTE: Conversion of '\n' to <BR> happens inside the
1786 // editor's Init() call.
1788 // Get the DOM document
1789 nsCOMPtr<Document> doc = presShell->GetDocument();
1790 if (NS_WARN_IF(!doc)) {
1791 return NS_ERROR_FAILURE;
1794 // What follows is a bit of a hack. The editor uses the public DOM APIs
1795 // for its content manipulations, and it causes it to fail some security
1796 // checks deep inside when initializing. So we explictly make it clear that
1797 // we're native code.
1798 // Note that any script that's directly trying to access our value
1799 // has to be going through some scriptable object to do that and that
1800 // already does the relevant security checks.
1801 AutoNoJSAPI nojsapi;
1803 RefPtr<Element> anonymousDivElement = GetRootNode();
1804 if (NS_WARN_IF(!anonymousDivElement) || NS_WARN_IF(!mSelCon)) {
1805 return NS_ERROR_FAILURE;
1807 OwningNonNull<TextInputSelectionController> selectionController(*mSelCon);
1808 UniquePtr<PasswordMaskData> passwordMaskData;
1809 if (editorFlags & nsIEditor::eEditorPasswordMask) {
1810 if (mPasswordMaskData) {
1811 passwordMaskData = std::move(mPasswordMaskData);
1812 } else {
1813 passwordMaskData = MakeUnique<PasswordMaskData>();
1815 } else {
1816 mPasswordMaskData = nullptr;
1818 nsresult rv =
1819 newTextEditor->Init(*doc, *anonymousDivElement, selectionController,
1820 editorFlags, std::move(passwordMaskData));
1821 if (NS_FAILED(rv)) {
1822 NS_WARNING("TextEditor::Init() failed");
1823 return rv;
1827 // Initialize the controller for the editor
1829 nsresult rv = NS_OK;
1830 if (!SuppressEventHandlers(presContext)) {
1831 nsCOMPtr<nsIControllers> controllers;
1832 if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
1833 nsresult rv = inputElement->GetControllers(getter_AddRefs(controllers));
1834 if (NS_WARN_IF(NS_FAILED(rv))) {
1835 return rv;
1837 } else {
1838 auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
1839 if (!textAreaElement) {
1840 return NS_ERROR_FAILURE;
1843 nsresult rv =
1844 textAreaElement->GetControllers(getter_AddRefs(controllers));
1845 if (NS_WARN_IF(NS_FAILED(rv))) {
1846 return rv;
1850 if (controllers) {
1851 // XXX Oddly, nsresult value is overwritten in the following loop, and
1852 // only the last result or `found` decides the value.
1853 uint32_t numControllers;
1854 bool found = false;
1855 rv = controllers->GetControllerCount(&numControllers);
1856 for (uint32_t i = 0; i < numControllers; i++) {
1857 nsCOMPtr<nsIController> controller;
1858 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1859 if (NS_SUCCEEDED(rv) && controller) {
1860 nsCOMPtr<nsIControllerContext> editController =
1861 do_QueryInterface(controller);
1862 if (editController) {
1863 editController->SetCommandContext(
1864 static_cast<nsIEditor*>(newTextEditor));
1865 found = true;
1869 if (!found) {
1870 rv = NS_ERROR_FAILURE;
1875 // Initialize the plaintext editor
1876 if (shouldInitializeEditor) {
1877 const int32_t wrapCols = GetWrapCols();
1878 MOZ_ASSERT(wrapCols >= 0);
1879 newTextEditor->SetWrapColumn(wrapCols);
1882 // Set max text field length
1883 newTextEditor->SetMaxTextLength(mTextCtrlElement->UsedMaxLength());
1885 editorFlags = newTextEditor->Flags();
1887 // Check if the readonly/disabled attributes are set.
1888 if (mTextCtrlElement->IsDisabledOrReadOnly()) {
1889 editorFlags |= nsIEditor::eEditorReadonlyMask;
1892 SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1894 if (shouldInitializeEditor) {
1895 // Hold on to the newly created editor
1896 preDestroyer.Swap(mTextEditor);
1899 // If we have a default value, insert it under the div we created
1900 // above, but be sure to use the editor so that '*' characters get
1901 // displayed for password fields, etc. SetValue() will call the
1902 // editor for us.
1904 if (!defaultValue.IsEmpty()) {
1905 // XXX rv may store error code which indicates there is no controller.
1906 // However, we overwrite it only in this case.
1907 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1908 if (NS_WARN_IF(NS_FAILED(rv))) {
1909 return rv;
1912 // Now call SetValue() which will make the necessary editor calls to set
1913 // the default value. Make sure to turn off undo before setting the default
1914 // value, and turn it back on afterwards. This will make sure we can't undo
1915 // past the default value.
1916 // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
1917 // undo.
1919 if (NS_WARN_IF(!SetValue(defaultValue, ValueSetterOption::ByInternalAPI))) {
1920 return NS_ERROR_OUT_OF_MEMORY;
1923 // Now restore the original editor flags.
1924 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1925 if (NS_WARN_IF(NS_FAILED(rv))) {
1926 return rv;
1929 // When the default value is empty, we don't call SetValue(). That means that
1930 // we have not notified IMEContentObserver of the empty value when the
1931 // <textarea> is not dirty (i.e., the default value is mirrored into the
1932 // anonymous subtree asynchronously) and the value was changed during a
1933 // reframe (i.e., while IMEContentObserver was not observing the mutation of
1934 // the anonymous subtree). Therefore, we notify IMEContentObserver here in
1935 // that case.
1936 else if (mTextCtrlElement && mTextCtrlElement->IsTextArea() &&
1937 !mTextCtrlElement->ValueChanged()) {
1938 MOZ_ASSERT(defaultValue.IsEmpty());
1939 IMEContentObserver* observer = GetIMEContentObserver();
1940 if (observer && observer->WasInitializedWith(*newTextEditor)) {
1941 observer->OnTextControlValueChangedWhileNotObservable(defaultValue);
1945 DebugOnly<bool> enabledUndoRedo =
1946 newTextEditor->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP);
1947 NS_WARNING_ASSERTION(enabledUndoRedo,
1948 "Failed to enable undo/redo transaction");
1950 if (!mEditorInitialized) {
1951 newTextEditor->PostCreate();
1952 mEverInited = true;
1953 mEditorInitialized = true;
1956 if (mTextListener) {
1957 newTextEditor->SetTextInputListener(mTextListener);
1960 // Restore our selection after being bound to a new frame
1961 if (mSelectionCached) {
1962 if (mRestoringSelection) { // paranoia
1963 mRestoringSelection->Revoke();
1965 mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1966 if (mRestoringSelection) {
1967 nsContentUtils::AddScriptRunner(mRestoringSelection);
1971 // The selection cache is no longer going to be valid.
1973 // XXXbz Shouldn't we do this at the point when we're actually about to
1974 // restore the properties or something? As things stand, if UnbindFromFrame
1975 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1976 // selection info, because we will think we don't have it cached and try to
1977 // read it from the selection controller, which will not have it yet.
1978 mSelectionCached = false;
1980 return preparingEditor.IsTextControlStateDestroyed()
1981 ? NS_ERROR_NOT_INITIALIZED
1982 : rv;
1985 void TextControlState::FinishedRestoringSelection() {
1986 mRestoringSelection = nullptr;
1989 void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
1990 if (mBoundFrame) {
1991 UnbindFromFrame(mBoundFrame);
1995 void TextControlState::SetSelectionProperties(
1996 TextControlState::SelectionProperties& aProps) {
1997 if (mBoundFrame) {
1998 mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
1999 aProps.GetDirection());
2000 // The instance may have already been deleted here.
2001 } else {
2002 mSelectionProperties = aProps;
2006 void TextControlState::GetSelectionRange(uint32_t* aSelectionStart,
2007 uint32_t* aSelectionEnd,
2008 ErrorResult& aRv) {
2009 MOZ_ASSERT(aSelectionStart);
2010 MOZ_ASSERT(aSelectionEnd);
2011 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2012 "How can we not have a cached selection if we have no selection "
2013 "controller?");
2015 // Note that we may have both IsSelectionCached() _and_
2016 // GetSelectionController() if we haven't initialized our editor yet.
2017 if (IsSelectionCached()) {
2018 const SelectionProperties& props = GetSelectionProperties();
2019 *aSelectionStart = props.GetStart();
2020 *aSelectionEnd = props.GetEnd();
2021 return;
2024 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2025 if (NS_WARN_IF(!sel)) {
2026 aRv.Throw(NS_ERROR_FAILURE);
2027 return;
2030 Element* root = GetRootNode();
2031 if (NS_WARN_IF(!root)) {
2032 aRv.Throw(NS_ERROR_UNEXPECTED);
2033 return;
2035 nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
2036 *aSelectionEnd);
2039 SelectionDirection TextControlState::GetSelectionDirection(ErrorResult& aRv) {
2040 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2041 "How can we not have a cached selection if we have no selection "
2042 "controller?");
2044 // Note that we may have both IsSelectionCached() _and_
2045 // GetSelectionController() if we haven't initialized our editor yet.
2046 if (IsSelectionCached()) {
2047 return GetSelectionProperties().GetDirection();
2050 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2051 if (NS_WARN_IF(!sel)) {
2052 aRv.Throw(NS_ERROR_FAILURE);
2053 return SelectionDirection::Forward;
2056 nsDirection direction = sel->GetDirection();
2057 if (direction == eDirNext) {
2058 return SelectionDirection::Forward;
2061 MOZ_ASSERT(direction == eDirPrevious);
2062 return SelectionDirection::Backward;
2065 void TextControlState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
2066 SelectionDirection aDirection,
2067 ErrorResult& aRv,
2068 ScrollAfterSelection aScroll) {
2069 MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
2070 "How can we have a non-cached selection but no frame?");
2072 AutoTextControlHandlingState handlingSetSelectionRange(
2073 *this, TextControlAction::SetSelectionRange);
2075 if (aStart > aEnd) {
2076 aStart = aEnd;
2079 if (!IsSelectionCached()) {
2080 MOZ_ASSERT(mBoundFrame, "Our frame should still be valid");
2081 aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
2082 if (aRv.Failed() ||
2083 handlingSetSelectionRange.IsTextControlStateDestroyed()) {
2084 return;
2086 if (aScroll == ScrollAfterSelection::Yes && mBoundFrame) {
2087 // mBoundFrame could be gone if selection listeners flushed layout for
2088 // example.
2089 mBoundFrame->ScrollSelectionIntoViewAsync();
2091 return;
2094 SelectionProperties& props = GetSelectionProperties();
2095 if (!props.HasMaxLength()) {
2096 // A clone without a dirty value flag may not have a max length yet
2097 nsAutoString value;
2098 GetValue(value, false, /* aForDisplay = */ true);
2099 props.SetMaxLength(value.Length());
2102 bool changed = props.SetStart(aStart);
2103 changed |= props.SetEnd(aEnd);
2104 changed |= props.SetDirection(aDirection);
2106 if (!changed) {
2107 return;
2110 // It sure would be nice if we had an existing Element* or so to work with.
2111 RefPtr<AsyncEventDispatcher> asyncDispatcher =
2112 new AsyncEventDispatcher(mTextCtrlElement, eFormSelect, CanBubble::eYes);
2113 asyncDispatcher->PostDOMEvent();
2115 // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
2116 // XXX(krosylight): Shouldn't it fire before select event?
2117 // Currently Gecko and Blink both fire selectionchange after select.
2118 if (IsSelectionCached() &&
2119 StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
2120 asyncDispatcher = new AsyncEventDispatcher(
2121 mTextCtrlElement, eSelectionChange, CanBubble::eYes);
2122 asyncDispatcher->PostDOMEvent();
2126 void TextControlState::SetSelectionStart(const Nullable<uint32_t>& aStart,
2127 ErrorResult& aRv) {
2128 uint32_t start = 0;
2129 if (!aStart.IsNull()) {
2130 start = aStart.Value();
2133 uint32_t ignored, end;
2134 GetSelectionRange(&ignored, &end, aRv);
2135 if (aRv.Failed()) {
2136 return;
2139 SelectionDirection dir = GetSelectionDirection(aRv);
2140 if (aRv.Failed()) {
2141 return;
2144 if (end < start) {
2145 end = start;
2148 SetSelectionRange(start, end, dir, aRv);
2149 // The instance may have already been deleted here.
2152 void TextControlState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
2153 ErrorResult& aRv) {
2154 uint32_t end = 0;
2155 if (!aEnd.IsNull()) {
2156 end = aEnd.Value();
2159 uint32_t start, ignored;
2160 GetSelectionRange(&start, &ignored, aRv);
2161 if (aRv.Failed()) {
2162 return;
2165 SelectionDirection dir = GetSelectionDirection(aRv);
2166 if (aRv.Failed()) {
2167 return;
2170 SetSelectionRange(start, end, dir, aRv);
2171 // The instance may have already been deleted here.
2174 static void DirectionToName(SelectionDirection dir, nsAString& aDirection) {
2175 switch (dir) {
2176 case SelectionDirection::None:
2177 // TODO(mbrodesser): this should be supported, see
2178 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
2179 NS_WARNING("We don't actually support this... how did we get it?");
2180 return aDirection.AssignLiteral("none");
2181 case SelectionDirection::Forward:
2182 return aDirection.AssignLiteral("forward");
2183 case SelectionDirection::Backward:
2184 return aDirection.AssignLiteral("backward");
2186 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
2189 void TextControlState::GetSelectionDirectionString(nsAString& aDirection,
2190 ErrorResult& aRv) {
2191 SelectionDirection dir = GetSelectionDirection(aRv);
2192 if (aRv.Failed()) {
2193 return;
2195 DirectionToName(dir, aDirection);
2198 static SelectionDirection DirectionStringToSelectionDirection(
2199 const nsAString& aDirection) {
2200 if (aDirection.EqualsLiteral("backward")) {
2201 return SelectionDirection::Backward;
2203 // We don't support directionless selections, see bug 1541454.
2204 return SelectionDirection::Forward;
2207 void TextControlState::SetSelectionDirection(const nsAString& aDirection,
2208 ErrorResult& aRv) {
2209 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2211 uint32_t start, end;
2212 GetSelectionRange(&start, &end, aRv);
2213 if (aRv.Failed()) {
2214 return;
2217 SetSelectionRange(start, end, dir, aRv);
2218 // The instance may have already been deleted here.
2221 static SelectionDirection DirectionStringToSelectionDirection(
2222 const Optional<nsAString>& aDirection) {
2223 if (!aDirection.WasPassed()) {
2224 // We don't support directionless selections.
2225 return SelectionDirection::Forward;
2228 return DirectionStringToSelectionDirection(aDirection.Value());
2231 void TextControlState::SetSelectionRange(uint32_t aSelectionStart,
2232 uint32_t aSelectionEnd,
2233 const Optional<nsAString>& aDirection,
2234 ErrorResult& aRv,
2235 ScrollAfterSelection aScroll) {
2236 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2238 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv, aScroll);
2239 // The instance may have already been deleted here.
2242 void TextControlState::SetRangeText(const nsAString& aReplacement,
2243 ErrorResult& aRv) {
2244 uint32_t start, end;
2245 GetSelectionRange(&start, &end, aRv);
2246 if (aRv.Failed()) {
2247 return;
2250 SetRangeText(aReplacement, start, end, SelectionMode::Preserve, aRv,
2251 Some(start), Some(end));
2252 // The instance may have already been deleted here.
2255 void TextControlState::SetRangeText(const nsAString& aReplacement,
2256 uint32_t aStart, uint32_t aEnd,
2257 SelectionMode aSelectMode, ErrorResult& aRv,
2258 const Maybe<uint32_t>& aSelectionStart,
2259 const Maybe<uint32_t>& aSelectionEnd) {
2260 if (aStart > aEnd) {
2261 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2262 return;
2265 AutoTextControlHandlingState handlingSetRangeText(
2266 *this, TextControlAction::SetRangeText);
2268 nsAutoString value;
2269 mTextCtrlElement->GetValueFromSetRangeText(value);
2270 uint32_t inputValueLength = value.Length();
2272 if (aStart > inputValueLength) {
2273 aStart = inputValueLength;
2276 if (aEnd > inputValueLength) {
2277 aEnd = inputValueLength;
2280 uint32_t selectionStart, selectionEnd;
2281 if (!aSelectionStart) {
2282 MOZ_ASSERT(!aSelectionEnd);
2283 GetSelectionRange(&selectionStart, &selectionEnd, aRv);
2284 if (aRv.Failed()) {
2285 return;
2287 } else {
2288 MOZ_ASSERT(aSelectionEnd);
2289 selectionStart = *aSelectionStart;
2290 selectionEnd = *aSelectionEnd;
2293 // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
2294 Selection* selection =
2295 mSelCon ? mSelCon->GetSelection(SelectionType::eNormal) : nullptr;
2296 SelectionBatcher selectionBatcher(
2297 selection, __FUNCTION__,
2298 nsISelectionListener::JS_REASON); // no-op if nullptr
2300 MOZ_ASSERT(aStart <= aEnd);
2301 value.Replace(aStart, aEnd - aStart, aReplacement);
2302 nsresult rv =
2303 MOZ_KnownLive(mTextCtrlElement)->SetValueFromSetRangeText(value);
2304 if (NS_FAILED(rv)) {
2305 aRv.Throw(rv);
2306 return;
2309 uint32_t newEnd = aStart + aReplacement.Length();
2310 int32_t delta = aReplacement.Length() - (aEnd - aStart);
2312 switch (aSelectMode) {
2313 case SelectionMode::Select:
2314 selectionStart = aStart;
2315 selectionEnd = newEnd;
2316 break;
2317 case SelectionMode::Start:
2318 selectionStart = selectionEnd = aStart;
2319 break;
2320 case SelectionMode::End:
2321 selectionStart = selectionEnd = newEnd;
2322 break;
2323 case SelectionMode::Preserve:
2324 if (selectionStart > aEnd) {
2325 selectionStart += delta;
2326 } else if (selectionStart > aStart) {
2327 selectionStart = aStart;
2330 if (selectionEnd > aEnd) {
2331 selectionEnd += delta;
2332 } else if (selectionEnd > aStart) {
2333 selectionEnd = newEnd;
2335 break;
2336 default:
2337 MOZ_ASSERT_UNREACHABLE("Unknown mode!");
2340 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
2341 if (IsSelectionCached()) {
2342 // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
2343 GetSelectionProperties().SetMaxLength(value.Length());
2347 void TextControlState::DestroyEditor() {
2348 // notify the editor that we are going away
2349 if (mEditorInitialized) {
2350 // FYI: TextEditor checks whether it's destroyed or not immediately after
2351 // changes the DOM tree or selection so that it's safe to call
2352 // PreDestroy() here even while we're handling actions with
2353 // mTextEditor.
2354 MOZ_ASSERT(!mPasswordMaskData);
2355 RefPtr<TextEditor> textEditor = mTextEditor;
2356 mPasswordMaskData = textEditor->PreDestroy();
2357 MOZ_ASSERT_IF(mPasswordMaskData, !mPasswordMaskData->mTimer);
2358 mEditorInitialized = false;
2362 void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
2363 if (NS_WARN_IF(!mBoundFrame)) {
2364 return;
2367 // If it was, however, it should be unbounded from the same frame.
2368 MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2369 if (aFrame && aFrame != mBoundFrame) {
2370 return;
2373 AutoTextControlHandlingState handlingUnbindFromFrame(
2374 *this, TextControlAction::UnbindFromFrame);
2376 if (mSelCon) {
2377 mSelCon->SelectionWillLoseFocus();
2380 // We need to start storing the value outside of the editor if we're not
2381 // going to use it anymore, so retrieve it for now.
2382 nsAutoString value;
2383 GetValue(value, true, /* aForDisplay = */ false);
2385 if (mRestoringSelection) {
2386 mRestoringSelection->Revoke();
2387 mRestoringSelection = nullptr;
2390 // Save our selection state if needed.
2391 // Note that GetSelectionRange will attempt to work with our selection
2392 // controller, so we should make sure we do it before we start doing things
2393 // like destroying our editor (if we have one), tearing down the selection
2394 // controller, and so forth.
2395 if (!IsSelectionCached()) {
2396 // Go ahead and cache it now.
2397 uint32_t start = 0, end = 0;
2398 GetSelectionRange(&start, &end, IgnoreErrors());
2400 nsITextControlFrame::SelectionDirection direction =
2401 GetSelectionDirection(IgnoreErrors());
2403 SelectionProperties& props = GetSelectionProperties();
2404 props.SetMaxLength(value.Length());
2405 props.SetStart(start);
2406 props.SetEnd(end);
2407 props.SetDirection(direction);
2408 mSelectionCached = true;
2411 // Destroy our editor
2412 DestroyEditor();
2414 // Clean up the controller
2415 if (!SuppressEventHandlers(mBoundFrame->PresContext())) {
2416 nsCOMPtr<nsIControllers> controllers;
2417 if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
2418 inputElement->GetControllers(getter_AddRefs(controllers));
2419 } else {
2420 auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
2421 if (textAreaElement) {
2422 textAreaElement->GetControllers(getter_AddRefs(controllers));
2426 if (controllers) {
2427 uint32_t numControllers;
2428 nsresult rv = controllers->GetControllerCount(&numControllers);
2429 NS_ASSERTION((NS_SUCCEEDED(rv)),
2430 "bad result in gfx text control destructor");
2431 for (uint32_t i = 0; i < numControllers; i++) {
2432 nsCOMPtr<nsIController> controller;
2433 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2434 if (NS_SUCCEEDED(rv) && controller) {
2435 nsCOMPtr<nsIControllerContext> editController =
2436 do_QueryInterface(controller);
2437 if (editController) {
2438 editController->SetCommandContext(nullptr);
2445 if (mSelCon) {
2446 if (mTextListener) {
2447 mTextListener->EndListeningToSelectionChange();
2450 mSelCon->SetScrollableFrame(nullptr);
2451 mSelCon = nullptr;
2454 if (mTextListener) {
2455 mTextListener->SetFrame(nullptr);
2457 EventListenerManager* manager =
2458 mTextCtrlElement->GetExistingListenerManager();
2459 if (manager) {
2460 manager->RemoveEventListenerByType(mTextListener, u"keydown"_ns,
2461 TrustedEventsAtSystemGroupBubble());
2462 manager->RemoveEventListenerByType(mTextListener, u"keypress"_ns,
2463 TrustedEventsAtSystemGroupBubble());
2464 manager->RemoveEventListenerByType(mTextListener, u"keyup"_ns,
2465 TrustedEventsAtSystemGroupBubble());
2468 mTextListener = nullptr;
2471 mBoundFrame = nullptr;
2473 // Now that we don't have a frame any more, store the value in the text
2474 // buffer. The only case where we don't do this is if a value transfer is in
2475 // progress.
2476 if (!mValueTransferInProgress) {
2477 DebugOnly<bool> ok = SetValue(value, ValueSetterOption::ByInternalAPI);
2478 // TODO Find something better to do if this fails...
2479 NS_WARNING_ASSERTION(ok, "SetValue() couldn't allocate memory");
2483 void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
2484 bool aForDisplay) const {
2485 // While SetValue() is being called and requesting to commit composition to
2486 // IME, GetValue() may be called for appending text or something. Then, we
2487 // need to return the latest aValue of SetValue() since the value hasn't
2488 // been set to the editor yet.
2489 // XXX After implementing "beforeinput" event, this becomes wrong. The
2490 // value should be modified immediately after "beforeinput" event for
2491 // "insertReplacementText".
2492 if (mHandlingState &&
2493 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2494 aValue = mHandlingState->GetSettingValue();
2495 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2496 return;
2499 if (mTextEditor && mBoundFrame &&
2500 (mEditorInitialized || !IsSingleLineTextControl())) {
2501 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2502 aValue = mBoundFrame->CachedValue();
2503 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2504 return;
2507 aValue.Truncate(); // initialize out param
2509 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2510 nsIDocumentEncoder::OutputPreformatted |
2511 nsIDocumentEncoder::OutputPersistNBSP |
2512 nsIDocumentEncoder::OutputBodyOnly);
2513 if (!aIgnoreWrap) {
2514 TextControlElement::nsHTMLTextWrap wrapProp;
2515 if (mTextCtrlElement &&
2516 TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
2517 wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
2518 flags |= nsIDocumentEncoder::OutputWrap;
2522 // What follows is a bit of a hack. The problem is that we could be in
2523 // this method because we're being destroyed for whatever reason while
2524 // script is executing. If that happens, editor will run with the
2525 // privileges of the executing script, which means it may not be able to
2526 // access its own DOM nodes! Let's try to deal with that by pushing a null
2527 // JSContext on the JSContext stack to make it clear that we're native
2528 // code. Note that any script that's directly trying to access our value
2529 // has to be going through some scriptable object to do that and that
2530 // already does the relevant security checks.
2531 // XXXbz if we could just get the textContent of our anonymous content (eg
2532 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2533 // this.
2534 { /* Scope for AutoNoJSAPI. */
2535 AutoNoJSAPI nojsapi;
2537 DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2538 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2541 // Only when the result doesn't include line breaks caused by hard-wrap,
2542 // mCacheValue should cache the value.
2543 if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2544 mBoundFrame->CacheValue(aValue);
2545 } else {
2546 mBoundFrame->ClearCachedValue();
2548 } else if (!mTextCtrlElement->ValueChanged() || mValue.IsVoid()) {
2549 // Use nsString to avoid copying string buffer at setting aValue.
2550 nsString value;
2551 mTextCtrlElement->GetDefaultValueFromContent(value, aForDisplay);
2552 // TODO: We should make default value not include \r.
2553 nsContentUtils::PlatformToDOMLineBreaks(value);
2554 aValue = std::move(value);
2555 } else {
2556 aValue = mValue;
2557 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2561 bool TextControlState::ValueEquals(const nsAString& aValue) const {
2562 nsAutoString value;
2563 GetValue(value, true, /* aForDisplay = */ true);
2564 return aValue.Equals(value);
2567 #ifdef DEBUG
2568 // @param aOptions TextControlState::ValueSetterOptions
2569 bool AreFlagsNotDemandingContradictingMovements(
2570 const ValueSetterOptions& aOptions) {
2571 return !aOptions.contains(
2572 {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward,
2573 ValueSetterOption::MoveCursorToEndIfValueChanged});
2575 #endif // DEBUG
2577 bool TextControlState::SetValue(const nsAString& aValue,
2578 const nsAString* aOldValue,
2579 const ValueSetterOptions& aOptions) {
2580 if (mHandlingState &&
2581 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2582 // GetValue doesn't return current text frame's content during committing.
2583 // So we cannot trust this old value
2584 aOldValue = nullptr;
2587 if (mPasswordMaskData) {
2588 if (mHandlingState &&
2589 mHandlingState->Is(TextControlAction::UnbindFromFrame)) {
2590 // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
2591 } else {
2592 // Otherwise, we should mask the new password, even if it's same value
2593 // since the same value may be one for different web app's.
2594 mPasswordMaskData->Reset();
2598 const bool wasHandlingSetValue =
2599 mHandlingState && mHandlingState->IsHandling(TextControlAction::SetValue);
2601 ErrorResult error;
2602 AutoTextControlHandlingState handlingSetValue(
2603 *this, TextControlAction::SetValue, aValue, aOldValue, aOptions, error);
2604 if (error.Failed()) {
2605 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
2606 error.SuppressException();
2607 return false;
2610 const auto changeKind = [&] {
2611 if (aOptions.contains(ValueSetterOption::ByInternalAPI)) {
2612 return ValueChangeKind::Internal;
2614 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2615 return ValueChangeKind::UserInteraction;
2617 return ValueChangeKind::Script;
2618 }();
2620 if (changeKind == ValueChangeKind::Script) {
2621 // This value change will not be interactive. If we're an input that was
2622 // interactively edited, save the last interactive value now before it goes
2623 // away.
2624 if (auto* input = HTMLInputElement::FromNode(mTextCtrlElement)) {
2625 if (input->LastValueChangeWasInteractive()) {
2626 GetValue(mLastInteractiveValue, /* aIgnoreWrap = */ true,
2627 /* aForDisplay = */ true);
2632 // Note that if this may be called during reframe of the editor. In such
2633 // case, we shouldn't commit composition. Therefore, when this is called
2634 // for internal processing, we shouldn't commit the composition.
2635 // TODO: In strictly speaking, we should move committing composition into
2636 // editor because if "beforeinput" for this setting value is canceled,
2637 // we shouldn't commit composition. However, in Firefox, we never
2638 // call this via `setUserInput` during composition. Therefore, the
2639 // bug must not be reproducible actually.
2640 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
2641 aOptions.contains(ValueSetterOption::ByContentAPI)) {
2642 if (EditorHasComposition()) {
2643 // When this is called recursively, there shouldn't be composition.
2644 if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
2645 // Don't request to commit composition again. But if it occurs,
2646 // we should skip to set the new value to the editor here. It should
2647 // be set later with the newest value.
2648 return true;
2650 if (NS_WARN_IF(!mBoundFrame)) {
2651 // We're not sure if this case is possible.
2652 } else {
2653 // If setting value won't change current value, we shouldn't commit
2654 // composition for compatibility with the other browsers.
2655 MOZ_ASSERT(!aOldValue || ValueEquals(*aOldValue));
2656 bool isSameAsCurrentValue =
2657 aOldValue ? aOldValue->Equals(handlingSetValue.GetSettingValue())
2658 : ValueEquals(handlingSetValue.GetSettingValue());
2659 if (isSameAsCurrentValue) {
2660 // Note that in this case, we shouldn't fire any events with setting
2661 // value because event handlers may try to set value recursively but
2662 // we cannot commit composition at that time due to unsafe to run
2663 // script (see below).
2664 return true;
2667 // If there is composition, need to commit composition first because
2668 // other browsers do that.
2669 // NOTE: We don't need to block nested calls of this because input nor
2670 // other events won't be fired by setting values and script blocker
2671 // is used during setting the value to the editor. IE also allows
2672 // to set the editor value on the input event which is caused by
2673 // forcibly committing composition.
2674 AutoTextControlHandlingState handlingCommitComposition(
2675 *this, TextControlAction::CommitComposition);
2676 if (nsContentUtils::IsSafeToRunScript()) {
2677 // WARNING: During this call, compositionupdate, compositionend, input
2678 // events will be fired. Therefore, everything can occur. E.g., the
2679 // document may be unloaded.
2680 RefPtr<TextEditor> textEditor = mTextEditor;
2681 nsresult rv = textEditor->CommitComposition();
2682 if (handlingCommitComposition.IsTextControlStateDestroyed()) {
2683 return true;
2685 if (NS_FAILED(rv)) {
2686 NS_WARNING("TextControlState failed to commit composition");
2687 return true;
2689 // Note that if a composition event listener sets editor value again,
2690 // we should use the new value here. The new value is stored in
2691 // handlingSetValue right now.
2692 } else {
2693 NS_WARNING(
2694 "SetValue() is called when there is composition but "
2695 "it's not safe to request to commit the composition");
2700 if (mTextEditor && mBoundFrame) {
2701 if (!SetValueWithTextEditor(handlingSetValue)) {
2702 return false;
2704 } else if (!SetValueWithoutTextEditor(handlingSetValue)) {
2705 return false;
2708 // If we were handling SetValue() before, don't update the DOM state twice,
2709 // just let the outer call do so.
2710 if (!wasHandlingSetValue) {
2711 handlingSetValue.GetTextControlElement()->OnValueChanged(
2712 changeKind, handlingSetValue.GetSettingValue());
2714 return true;
2717 bool TextControlState::SetValueWithTextEditor(
2718 AutoTextControlHandlingState& aHandlingSetValue) {
2719 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2720 MOZ_ASSERT(mTextEditor);
2721 MOZ_ASSERT(mBoundFrame);
2722 NS_WARNING_ASSERTION(!EditorHasComposition(),
2723 "Failed to commit composition before setting value. "
2724 "Investigate the cause!");
2726 #ifdef DEBUG
2727 if (IsSingleLineTextControl()) {
2728 NS_ASSERTION(mEditorInitialized || aHandlingSetValue.IsHandling(
2729 TextControlAction::PrepareEditor),
2730 "We should never try to use the editor if we're not "
2731 "initialized unless we're being initialized");
2733 #endif
2735 MOZ_ASSERT(!aHandlingSetValue.GetOldValue() ||
2736 ValueEquals(*aHandlingSetValue.GetOldValue()));
2737 const bool isSameAsCurrentValue =
2738 aHandlingSetValue.GetOldValue()
2739 ? aHandlingSetValue.GetOldValue()->Equals(
2740 aHandlingSetValue.GetSettingValue())
2741 : ValueEquals(aHandlingSetValue.GetSettingValue());
2743 // this is necessary to avoid infinite recursion
2744 if (isSameAsCurrentValue) {
2745 return true;
2748 RefPtr<TextEditor> textEditor = mTextEditor;
2750 nsCOMPtr<Document> document = textEditor->GetDocument();
2751 if (NS_WARN_IF(!document)) {
2752 return true;
2755 // Time to mess with our security context... See comments in GetValue()
2756 // for why this is needed. Note that we have to do this up here, because
2757 // otherwise SelectAll() will fail.
2758 AutoNoJSAPI nojsapi;
2760 // FYI: It's safe to use raw pointer for selection here because
2761 // SelectionBatcher will grab it with RefPtr.
2762 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
2763 SelectionBatcher selectionBatcher(selection, __FUNCTION__);
2765 // get the flags, remove readonly, disabled and max-length,
2766 // set the value, restore flags
2767 AutoRestoreEditorState restoreState(textEditor);
2769 aHandlingSetValue.WillSetValueWithTextEditor();
2771 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2772 ValueSetterOption::BySetUserInputAPI)) {
2773 // If the caller inserts text as part of user input, for example,
2774 // autocomplete, we need to replace the text as "insert string"
2775 // because undo should cancel only this operation (i.e., previous
2776 // transactions typed by user shouldn't be merged with this).
2777 // In this case, we need to dispatch "input" event because
2778 // web apps may need to know the user's operation.
2779 // In this case, we need to dispatch "beforeinput" events since
2780 // we're emulating the user's input. Passing nullptr as
2781 // nsIPrincipal means that that may be user's input. So, let's
2782 // do it.
2783 nsresult rv = textEditor->ReplaceTextAsAction(
2784 aHandlingSetValue.GetSettingValue(), nullptr,
2785 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2786 ? TextEditor::AllowBeforeInputEventCancelable::Yes
2787 : TextEditor::AllowBeforeInputEventCancelable::No,
2788 nullptr);
2789 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2790 "EditorBase::ReplaceTextAsAction() failed");
2791 return rv != NS_ERROR_OUT_OF_MEMORY;
2794 // Don't dispatch "beforeinput" event nor "input" event for setting value
2795 // by script.
2796 AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
2798 // On <input> or <textarea>, we shouldn't preserve existing undo
2799 // transactions because other browsers do not preserve them too
2800 // and not preserving transactions makes setting value faster.
2802 // (Except if chrome opts into this behavior).
2803 Maybe<AutoDisableUndo> disableUndo;
2804 if (!aHandlingSetValue.ValueSetterOptionsRef().contains(
2805 ValueSetterOption::PreserveUndoHistory)) {
2806 disableUndo.emplace(textEditor);
2809 if (selection) {
2810 // Since we don't use undo transaction, we don't need to store
2811 // selection state. SetText will set selection to tail.
2812 IgnoredErrorResult ignoredError;
2813 MOZ_KnownLive(selection)->RemoveAllRanges(ignoredError);
2814 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2815 "Selection::RemoveAllRanges() failed, but ignored");
2818 // In this case, we makes the editor stop dispatching "input"
2819 // event so that passing nullptr as nsIPrincipal is safe for now.
2820 nsresult rv = textEditor->SetTextAsAction(
2821 aHandlingSetValue.GetSettingValue(),
2822 aHandlingSetValue.ValueSetterOptionsRef().contains(
2823 ValueSetterOption::BySetUserInputAPI) &&
2824 !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2825 ? TextEditor::AllowBeforeInputEventCancelable::No
2826 : TextEditor::AllowBeforeInputEventCancelable::Yes,
2827 nullptr);
2828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2829 "TextEditor::SetTextAsAction() failed");
2831 // Call the listener's OnEditActionHandled() callback manually if
2832 // OnEditActionHandled() hasn't been called yet since TextEditor don't use
2833 // the transaction manager in this path and it could be that the editor
2834 // would bypass calling the listener for that reason.
2835 if (!aHandlingSetValue.HasEditActionHandled()) {
2836 nsresult rvOnEditActionHandled =
2837 MOZ_KnownLive(aHandlingSetValue.GetTextInputListener())
2838 ->OnEditActionHandled(*textEditor);
2839 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled),
2840 "TextInputListener::OnEditActionHandled() failed");
2841 if (rv != NS_ERROR_OUT_OF_MEMORY) {
2842 rv = rvOnEditActionHandled;
2846 // When the <textarea> is not dirty, the default value is mirrored into the
2847 // anonymous subtree asynchronously. This may occur during a reframe.
2848 // Therefore, if IMEContentObserver was initialized with our editor but our
2849 // editor is being initialized, it has not been observing the new anonymous
2850 // subtree. In this case, we need to notify IMEContentObserver of the default
2851 // value change.
2852 if (mTextCtrlElement && mTextCtrlElement->IsTextArea() &&
2853 !mTextCtrlElement->ValueChanged() && textEditor->IsBeingInitialized() &&
2854 !textEditor->Destroyed()) {
2855 IMEContentObserver* observer = GetIMEContentObserver();
2856 if (observer && observer->WasInitializedWith(*textEditor)) {
2857 nsAutoString currentValue;
2858 textEditor->ComputeTextValue(0, currentValue);
2859 observer->OnTextControlValueChangedWhileNotObservable(currentValue);
2863 return rv != NS_ERROR_OUT_OF_MEMORY;
2866 bool TextControlState::SetValueWithoutTextEditor(
2867 AutoTextControlHandlingState& aHandlingSetValue) {
2868 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2869 MOZ_ASSERT(!mTextEditor || !mBoundFrame);
2870 NS_WARNING_ASSERTION(!EditorHasComposition(),
2871 "Failed to commit composition before setting value. "
2872 "Investigate the cause!");
2874 if (mValue.IsVoid()) {
2875 mValue.SetIsVoid(false);
2878 // We can't just early-return here, because OnValueChanged below still need to
2879 // be called.
2880 if (!mValue.Equals(aHandlingSetValue.GetSettingValue()) ||
2881 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2882 bool handleSettingValue = true;
2883 // If `SetValue()` call is nested, `GetSettingValue()` result will be
2884 // modified. So, we need to store input event data value before
2885 // dispatching beforeinput event.
2886 nsString inputEventData(aHandlingSetValue.GetSettingValue());
2887 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2888 ValueSetterOption::BySetUserInputAPI) &&
2889 !aHandlingSetValue.HasBeforeInputEventDispatched()) {
2890 // This probably occurs when session restorer sets the old value with
2891 // `setUserInput`. If so, we need to dispatch "beforeinput" event of
2892 // "insertReplacementText" for conforming to the spec. However, the
2893 // spec does NOT treat the session restoring case. Therefore, if this
2894 // breaks session restorere in a lot of web apps, we should probably
2895 // stop dispatching it or make it non-cancelable.
2896 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2897 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2898 aHandlingSetValue.WillDispatchBeforeInputEvent();
2899 nsEventStatus status = nsEventStatus_eIgnore;
2900 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2901 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2902 eEditorBeforeInput, EditorInputType::eInsertReplacementText, nullptr,
2903 InputEventOptions(
2904 inputEventData,
2905 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2906 ? InputEventOptions::NeverCancelable::No
2907 : InputEventOptions::NeverCancelable::Yes),
2908 &status);
2909 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2910 "Failed to dispatch beforeinput event");
2911 if (status == nsEventStatus_eConsumeNoDefault) {
2912 return true; // "beforeinput" event was canceled.
2914 // If we were destroyed by "beforeinput" event listeners, probably, we
2915 // don't need to keep handling it.
2916 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2917 return true;
2919 // Even if "beforeinput" event was not canceled, its listeners may do
2920 // something. If it causes creating `TextEditor` and bind this to a
2921 // frame, we need to use the path, but `TextEditor` shouldn't fire
2922 // "beforeinput" event again. Therefore, we need to prevent editor
2923 // to dispatch it.
2924 if (mTextEditor && mBoundFrame) {
2925 AutoInputEventSuppresser suppressInputEvent(mTextEditor);
2926 if (!SetValueWithTextEditor(aHandlingSetValue)) {
2927 return false;
2929 // If we were destroyed by "beforeinput" event listeners, probably, we
2930 // don't need to keep handling it.
2931 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2932 return true;
2934 handleSettingValue = false;
2938 if (handleSettingValue) {
2939 if (!mValue.Assign(aHandlingSetValue.GetSettingValue(), fallible)) {
2940 return false;
2943 // Since we have no editor we presumably have cached selection state.
2944 if (IsSelectionCached()) {
2945 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
2946 aHandlingSetValue.ValueSetterOptionsRef()));
2948 SelectionProperties& props = GetSelectionProperties();
2949 // Setting a max length and thus capping selection range early prevents
2950 // selection change detection in setRangeText. Temporarily disable
2951 // capping here with UINT32_MAX, and set it later in ::SetRangeText().
2952 props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
2953 ValueSetterOption::BySetRangeTextAPI)
2954 ? UINT32_MAX
2955 : aHandlingSetValue.GetSettingValue().Length());
2956 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2957 ValueSetterOption::MoveCursorToEndIfValueChanged)) {
2958 props.SetStart(aHandlingSetValue.GetSettingValue().Length());
2959 props.SetEnd(aHandlingSetValue.GetSettingValue().Length());
2960 props.SetDirection(SelectionDirection::Forward);
2961 } else if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2962 ValueSetterOption::
2963 MoveCursorToBeginSetSelectionDirectionForward)) {
2964 props.SetStart(0);
2965 props.SetEnd(0);
2966 props.SetDirection(SelectionDirection::Forward);
2970 // Update the frame display if needed
2971 if (mBoundFrame) {
2972 mBoundFrame->UpdateValueDisplay(true);
2975 // If the text control element has focus, IMEContentObserver is not
2976 // observing the content changes due to no bound frame or no TextEditor.
2977 // Therefore, we need to let IMEContentObserver know all values are being
2978 // replaced.
2979 if (IMEContentObserver* observer = GetIMEContentObserver()) {
2980 observer->OnTextControlValueChangedWhileNotObservable(mValue);
2984 // If this is called as part of user input, we need to dispatch "input"
2985 // event with "insertReplacementText" since web apps may want to know
2986 // the user operation which changes editor value with a built-in function
2987 // like autocomplete, password manager, session restore, etc.
2988 // XXX Should we stop dispatching `input` event if the text control
2989 // element has already removed from the DOM tree by a `beforeinput`
2990 // event listener?
2991 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2992 ValueSetterOption::BySetUserInputAPI)) {
2993 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2995 // Update validity state before dispatching "input" event for its
2996 // listeners like `EditorBase::NotifyEditorObservers()`.
2997 aHandlingSetValue.GetTextControlElement()->OnValueChanged(
2998 ValueChangeKind::UserInteraction,
2999 aHandlingSetValue.GetSettingValue());
3001 ClearLastInteractiveValue();
3003 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
3004 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
3005 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
3006 eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
3007 InputEventOptions(inputEventData,
3008 InputEventOptions::NeverCancelable::No));
3009 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3010 "Failed to dispatch input event");
3012 } else {
3013 // Even if our value is not actually changing, apparently we need to mark
3014 // our SelectionProperties dirty to make accessibility tests happy.
3015 // Probably because they depend on the SetSelectionRange() call we make on
3016 // our frame in RestoreSelectionState, but I have no idea why they do.
3017 if (IsSelectionCached()) {
3018 SelectionProperties& props = GetSelectionProperties();
3019 props.SetIsDirty();
3023 return true;
3026 void TextControlState::InitializeKeyboardEventListeners() {
3027 // register key listeners
3028 EventListenerManager* manager =
3029 mTextCtrlElement->GetOrCreateListenerManager();
3030 if (manager) {
3031 manager->AddEventListenerByType(mTextListener, u"keydown"_ns,
3032 TrustedEventsAtSystemGroupBubble());
3033 manager->AddEventListenerByType(mTextListener, u"keypress"_ns,
3034 TrustedEventsAtSystemGroupBubble());
3035 manager->AddEventListenerByType(mTextListener, u"keyup"_ns,
3036 TrustedEventsAtSystemGroupBubble());
3039 mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
3042 void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
3043 // If we don't have a preview div, there's nothing to do.
3044 Element* previewDiv = GetPreviewNode();
3045 if (!previewDiv) {
3046 return;
3049 nsAutoString previewValue(aValue);
3051 nsContentUtils::RemoveNewlines(previewValue);
3052 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3053 previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
3056 void TextControlState::GetPreviewText(nsAString& aValue) {
3057 // If we don't have a preview div, there's nothing to do.
3058 Element* previewDiv = GetPreviewNode();
3059 if (!previewDiv) {
3060 return;
3063 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3064 const nsTextFragment* text = previewDiv->GetFirstChild()->GetText();
3066 aValue.Truncate();
3067 text->AppendTo(aValue);
3070 bool TextControlState::EditorHasComposition() {
3071 return mTextEditor && mTextEditor->IsIMEComposing();
3074 IMEContentObserver* TextControlState::GetIMEContentObserver() const {
3075 if (NS_WARN_IF(!mTextCtrlElement) ||
3076 mTextCtrlElement != IMEStateManager::GetFocusedElement()) {
3077 return nullptr;
3079 IMEContentObserver* observer = IMEStateManager::GetActiveContentObserver();
3080 // The text control element may be an editing host. In this case, the
3081 // observer does not observe the anonymous nodes under mTextCtrlElement.
3082 // So, it means that the observer is not for ours.
3083 return observer && observer->EditorIsTextEditor() ? observer : nullptr;
3086 } // namespace mozilla