Bug 1719855 - Take over preventDefaulted infomation for long-tap events to the origin...
[gecko.git] / dom / html / TextControlState.cpp
blobce1536812998ae8f15293194866882cc13c337c9
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/IMEContentObserver.h"
10 #include "mozilla/IMEStateManager.h"
11 #include "mozilla/TextInputListener.h"
13 #include "nsCOMPtr.h"
14 #include "nsView.h"
15 #include "nsCaret.h"
16 #include "nsLayoutCID.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 = CARET_ASSOCIATE_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 = CARET_ASSOCIATE_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, &aSelection, aReason);
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, Selection* aSelection,
1065 int16_t aReason) {
1066 nsIContent* content = mFrame->GetContent();
1067 if (NS_WARN_IF(!content)) {
1068 return NS_ERROR_FAILURE;
1070 nsCOMPtr<Document> doc = content->GetComposedDoc();
1071 if (NS_WARN_IF(!doc)) {
1072 return NS_ERROR_FAILURE;
1074 nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1075 if (NS_WARN_IF(!domWindow)) {
1076 return NS_ERROR_FAILURE;
1078 domWindow->UpdateCommands(aCommandsToUpdate, aSelection, aReason);
1079 return NS_OK;
1082 /*****************************************************************************
1083 * mozilla::AutoTextControlHandlingState
1085 * This class is temporarily created in the stack and can manage nested
1086 * handling state of TextControlState. While this instance exists, lifetime of
1087 * TextControlState which created the instance is guaranteed. In other words,
1088 * you can use this class as "kungFuDeathGrip" for TextControlState.
1089 *****************************************************************************/
1091 enum class TextControlAction {
1092 CommitComposition,
1093 Destructor,
1094 PrepareEditor,
1095 SetRangeText,
1096 SetSelectionRange,
1097 SetValue,
1098 UnbindFromFrame,
1099 Unlink,
1102 class MOZ_STACK_CLASS AutoTextControlHandlingState {
1103 public:
1104 AutoTextControlHandlingState() = delete;
1105 explicit AutoTextControlHandlingState(const AutoTextControlHandlingState&) =
1106 delete;
1107 AutoTextControlHandlingState(AutoTextControlHandlingState&&) = delete;
1108 void operator=(AutoTextControlHandlingState&) = delete;
1109 void operator=(const AutoTextControlHandlingState&) = delete;
1112 * Generic constructor. If TextControlAction does not require additional
1113 * data, must use this constructor.
1115 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1116 TextControlState& aTextControlState, TextControlAction aTextControlAction)
1117 : mParent(aTextControlState.mHandlingState),
1118 mTextControlState(aTextControlState),
1119 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1120 mTextInputListener(aTextControlState.mTextListener),
1121 mTextControlAction(aTextControlAction) {
1122 MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue,
1123 "Use specific constructor");
1124 MOZ_DIAGNOSTIC_ASSERT_IF(
1125 !aTextControlState.mTextListener,
1126 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1127 mTextControlState.mHandlingState = this;
1128 if (Is(TextControlAction::CommitComposition)) {
1129 MOZ_ASSERT(mParent);
1130 MOZ_ASSERT(mParent->Is(TextControlAction::SetValue));
1131 // If we're trying to commit composition before handling SetValue,
1132 // the parent old values will be outdated so that we need to clear
1133 // them.
1134 mParent->InvalidateOldValue();
1139 * TextControlAction::SetValue specific constructor. Current setting value
1140 * must be specified and the creator should check whether we succeeded to
1141 * allocate memory for line breaker conversion.
1143 MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
1144 TextControlState& aTextControlState, TextControlAction aTextControlAction,
1145 const nsAString& aSettingValue, const nsAString* aOldValue,
1146 const ValueSetterOptions& aOptions, ErrorResult& aRv)
1147 : mParent(aTextControlState.mHandlingState),
1148 mTextControlState(aTextControlState),
1149 mTextCtrlElement(aTextControlState.mTextCtrlElement),
1150 mTextInputListener(aTextControlState.mTextListener),
1151 mSettingValue(aSettingValue),
1152 mOldValue(aOldValue),
1153 mValueSetterOptions(aOptions),
1154 mTextControlAction(aTextControlAction) {
1155 MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue,
1156 "Use generic constructor");
1157 MOZ_DIAGNOSTIC_ASSERT_IF(
1158 !aTextControlState.mTextListener,
1159 !aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
1160 mTextControlState.mHandlingState = this;
1161 if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue, fallible)) {
1162 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1163 return;
1165 // Update all setting value's new value because older value shouldn't
1166 // overwrite newer value.
1167 if (mParent) {
1168 // If SetValue is nested, parents cannot trust their old value anymore.
1169 // So, we need to clear them.
1170 mParent->UpdateSettingValueAndInvalidateOldValue(mSettingValue);
1174 MOZ_CAN_RUN_SCRIPT ~AutoTextControlHandlingState() {
1175 mTextControlState.mHandlingState = mParent;
1176 if (!mParent && mTextControlStateDestroyed) {
1177 mTextControlState.DeleteOrCacheForReuse();
1179 if (!mTextControlStateDestroyed && mPreareEditorLater) {
1180 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
1181 mTextControlState.PrepareEditor();
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->mPreareEditorLater = 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) : false;
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 nsString mSettingValue;
1376 const nsAString* mOldValue = nullptr;
1377 ValueSetterOptions mValueSetterOptions;
1378 TextControlAction const mTextControlAction;
1379 bool mTextControlStateDestroyed = false;
1380 bool mEditActionHandled = false;
1381 bool mPreareEditorLater = 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() {
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);
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
1724 uint32_t editorFlags = nsIEditor::eEditorPlaintextMask;
1725 if (IsSingleLineTextControl()) {
1726 editorFlags |= nsIEditor::eEditorSingleLineMask;
1728 if (IsPasswordTextControl()) {
1729 editorFlags |= nsIEditor::eEditorPasswordMask;
1732 // Spell check is diabled at creation time. It is enabled once
1733 // the editor comes into focus.
1734 editorFlags |= nsIEditor::eEditorSkipSpellCheck;
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);
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 (HTMLInputElement* inputElement =
1833 HTMLInputElement::FromNodeOrNull(mTextCtrlElement)) {
1834 nsresult rv = inputElement->GetControllers(getter_AddRefs(controllers));
1835 if (NS_WARN_IF(NS_FAILED(rv))) {
1836 return rv;
1838 } else {
1839 HTMLTextAreaElement* textAreaElement =
1840 HTMLTextAreaElement::FromNodeOrNull(mTextCtrlElement);
1841 if (!textAreaElement) {
1842 return NS_ERROR_FAILURE;
1845 nsresult rv =
1846 textAreaElement->GetControllers(getter_AddRefs(controllers));
1847 if (NS_WARN_IF(NS_FAILED(rv))) {
1848 return rv;
1852 if (controllers) {
1853 // XXX Oddly, nsresult value is overwritten in the following loop, and
1854 // only the last result or `found` decides the value.
1855 uint32_t numControllers;
1856 bool found = false;
1857 rv = controllers->GetControllerCount(&numControllers);
1858 for (uint32_t i = 0; i < numControllers; i++) {
1859 nsCOMPtr<nsIController> controller;
1860 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1861 if (NS_SUCCEEDED(rv) && controller) {
1862 nsCOMPtr<nsIControllerContext> editController =
1863 do_QueryInterface(controller);
1864 if (editController) {
1865 editController->SetCommandContext(
1866 static_cast<nsIEditor*>(newTextEditor));
1867 found = true;
1871 if (!found) {
1872 rv = NS_ERROR_FAILURE;
1877 // Initialize the plaintext editor
1878 if (shouldInitializeEditor) {
1879 const int32_t wrapCols = GetWrapCols();
1880 MOZ_ASSERT(wrapCols >= 0);
1881 newTextEditor->SetWrapColumn(wrapCols);
1884 // Set max text field length
1885 newTextEditor->SetMaxTextLength(mTextCtrlElement->UsedMaxLength());
1887 editorFlags = newTextEditor->Flags();
1889 // Check if the readonly attribute is set.
1891 // TODO: Should probably call IsDisabled(), as it is cheaper.
1892 if (mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
1893 mTextCtrlElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
1894 editorFlags |= nsIEditor::eEditorReadonlyMask;
1897 SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1899 if (shouldInitializeEditor) {
1900 // Hold on to the newly created editor
1901 preDestroyer.Swap(mTextEditor);
1904 // If we have a default value, insert it under the div we created
1905 // above, but be sure to use the editor so that '*' characters get
1906 // displayed for password fields, etc. SetValue() will call the
1907 // editor for us.
1909 if (!defaultValue.IsEmpty()) {
1910 // XXX rv may store error code which indicates there is no controller.
1911 // However, we overwrite it only in this case.
1912 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1913 if (NS_WARN_IF(NS_FAILED(rv))) {
1914 return rv;
1917 // Now call SetValue() which will make the necessary editor calls to set
1918 // the default value. Make sure to turn off undo before setting the default
1919 // value, and turn it back on afterwards. This will make sure we can't undo
1920 // past the default value.
1921 // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
1922 // undo.
1924 if (NS_WARN_IF(!SetValue(defaultValue, ValueSetterOption::ByInternalAPI))) {
1925 return NS_ERROR_OUT_OF_MEMORY;
1928 // Now restore the original editor flags.
1929 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1930 if (NS_WARN_IF(NS_FAILED(rv))) {
1931 return rv;
1935 DebugOnly<bool> enabledUndoRedo =
1936 newTextEditor->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP);
1937 NS_WARNING_ASSERTION(enabledUndoRedo,
1938 "Failed to enable undo/redo transaction");
1940 if (!mEditorInitialized) {
1941 newTextEditor->PostCreate();
1942 mEverInited = true;
1943 mEditorInitialized = true;
1946 if (mTextListener) {
1947 newTextEditor->SetTextInputListener(mTextListener);
1950 // Restore our selection after being bound to a new frame
1951 if (mSelectionCached) {
1952 if (mRestoringSelection) { // paranoia
1953 mRestoringSelection->Revoke();
1955 mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1956 if (mRestoringSelection) {
1957 nsContentUtils::AddScriptRunner(mRestoringSelection);
1961 // The selection cache is no longer going to be valid.
1963 // XXXbz Shouldn't we do this at the point when we're actually about to
1964 // restore the properties or something? As things stand, if UnbindFromFrame
1965 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1966 // selection info, because we will think we don't have it cached and try to
1967 // read it from the selection controller, which will not have it yet.
1968 mSelectionCached = false;
1970 return preparingEditor.IsTextControlStateDestroyed()
1971 ? NS_ERROR_NOT_INITIALIZED
1972 : rv;
1975 void TextControlState::FinishedRestoringSelection() {
1976 mRestoringSelection = nullptr;
1979 void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
1980 if (mBoundFrame) {
1981 UnbindFromFrame(mBoundFrame);
1985 void TextControlState::SetSelectionProperties(
1986 TextControlState::SelectionProperties& aProps) {
1987 if (mBoundFrame) {
1988 mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
1989 aProps.GetDirection());
1990 // The instance may have already been deleted here.
1991 } else {
1992 mSelectionProperties = aProps;
1996 void TextControlState::GetSelectionRange(uint32_t* aSelectionStart,
1997 uint32_t* aSelectionEnd,
1998 ErrorResult& aRv) {
1999 MOZ_ASSERT(aSelectionStart);
2000 MOZ_ASSERT(aSelectionEnd);
2001 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2002 "How can we not have a cached selection if we have no selection "
2003 "controller?");
2005 // Note that we may have both IsSelectionCached() _and_
2006 // GetSelectionController() if we haven't initialized our editor yet.
2007 if (IsSelectionCached()) {
2008 const SelectionProperties& props = GetSelectionProperties();
2009 *aSelectionStart = props.GetStart();
2010 *aSelectionEnd = props.GetEnd();
2011 return;
2014 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2015 if (NS_WARN_IF(!sel)) {
2016 aRv.Throw(NS_ERROR_FAILURE);
2017 return;
2020 Element* root = GetRootNode();
2021 if (NS_WARN_IF(!root)) {
2022 aRv.Throw(NS_ERROR_UNEXPECTED);
2023 return;
2025 nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
2026 *aSelectionEnd);
2029 SelectionDirection TextControlState::GetSelectionDirection(ErrorResult& aRv) {
2030 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2031 "How can we not have a cached selection if we have no selection "
2032 "controller?");
2034 // Note that we may have both IsSelectionCached() _and_
2035 // GetSelectionController() if we haven't initialized our editor yet.
2036 if (IsSelectionCached()) {
2037 return GetSelectionProperties().GetDirection();
2040 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
2041 if (NS_WARN_IF(!sel)) {
2042 aRv.Throw(NS_ERROR_FAILURE);
2043 return SelectionDirection::Forward;
2046 nsDirection direction = sel->GetDirection();
2047 if (direction == eDirNext) {
2048 return SelectionDirection::Forward;
2051 MOZ_ASSERT(direction == eDirPrevious);
2052 return SelectionDirection::Backward;
2055 void TextControlState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
2056 SelectionDirection aDirection,
2057 ErrorResult& aRv,
2058 ScrollAfterSelection aScroll) {
2059 MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
2060 "How can we have a non-cached selection but no frame?");
2062 AutoTextControlHandlingState handlingSetSelectionRange(
2063 *this, TextControlAction::SetSelectionRange);
2065 if (aStart > aEnd) {
2066 aStart = aEnd;
2069 if (!IsSelectionCached()) {
2070 MOZ_ASSERT(mBoundFrame, "Our frame should still be valid");
2071 aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
2072 if (aRv.Failed() ||
2073 handlingSetSelectionRange.IsTextControlStateDestroyed()) {
2074 return;
2076 if (aScroll == ScrollAfterSelection::Yes && mBoundFrame) {
2077 // mBoundFrame could be gone if selection listeners flushed layout for
2078 // example.
2079 mBoundFrame->ScrollSelectionIntoViewAsync();
2081 return;
2084 SelectionProperties& props = GetSelectionProperties();
2085 if (!props.HasMaxLength()) {
2086 // A clone without a dirty value flag may not have a max length yet
2087 nsAutoString value;
2088 GetValue(value, false);
2089 props.SetMaxLength(value.Length());
2092 bool changed = props.SetStart(aStart);
2093 changed |= props.SetEnd(aEnd);
2094 changed |= props.SetDirection(aDirection);
2096 if (!changed) {
2097 return;
2100 // It sure would be nice if we had an existing Element* or so to work with.
2101 RefPtr<AsyncEventDispatcher> asyncDispatcher =
2102 new AsyncEventDispatcher(mTextCtrlElement, eFormSelect, CanBubble::eYes);
2103 asyncDispatcher->PostDOMEvent();
2105 // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
2106 // XXX(krosylight): Shouldn't it fire before select event?
2107 // Currently Gecko and Blink both fire selectionchange after select.
2108 if (IsSelectionCached() &&
2109 StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
2110 asyncDispatcher = new AsyncEventDispatcher(
2111 mTextCtrlElement, eSelectionChange, CanBubble::eYes);
2112 asyncDispatcher->PostDOMEvent();
2116 void TextControlState::SetSelectionStart(const Nullable<uint32_t>& aStart,
2117 ErrorResult& aRv) {
2118 uint32_t start = 0;
2119 if (!aStart.IsNull()) {
2120 start = aStart.Value();
2123 uint32_t ignored, end;
2124 GetSelectionRange(&ignored, &end, aRv);
2125 if (aRv.Failed()) {
2126 return;
2129 SelectionDirection dir = GetSelectionDirection(aRv);
2130 if (aRv.Failed()) {
2131 return;
2134 if (end < start) {
2135 end = start;
2138 SetSelectionRange(start, end, dir, aRv);
2139 // The instance may have already been deleted here.
2142 void TextControlState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
2143 ErrorResult& aRv) {
2144 uint32_t end = 0;
2145 if (!aEnd.IsNull()) {
2146 end = aEnd.Value();
2149 uint32_t start, ignored;
2150 GetSelectionRange(&start, &ignored, aRv);
2151 if (aRv.Failed()) {
2152 return;
2155 SelectionDirection dir = GetSelectionDirection(aRv);
2156 if (aRv.Failed()) {
2157 return;
2160 SetSelectionRange(start, end, dir, aRv);
2161 // The instance may have already been deleted here.
2164 static void DirectionToName(SelectionDirection dir, nsAString& aDirection) {
2165 switch (dir) {
2166 case SelectionDirection::None:
2167 // TODO(mbrodesser): this should be supported, see
2168 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
2169 NS_WARNING("We don't actually support this... how did we get it?");
2170 return aDirection.AssignLiteral("none");
2171 case SelectionDirection::Forward:
2172 return aDirection.AssignLiteral("forward");
2173 case SelectionDirection::Backward:
2174 return aDirection.AssignLiteral("backward");
2176 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
2179 void TextControlState::GetSelectionDirectionString(nsAString& aDirection,
2180 ErrorResult& aRv) {
2181 SelectionDirection dir = GetSelectionDirection(aRv);
2182 if (aRv.Failed()) {
2183 return;
2185 DirectionToName(dir, aDirection);
2188 static SelectionDirection DirectionStringToSelectionDirection(
2189 const nsAString& aDirection) {
2190 if (aDirection.EqualsLiteral("backward")) {
2191 return SelectionDirection::Backward;
2193 // We don't support directionless selections, see bug 1541454.
2194 return SelectionDirection::Forward;
2197 void TextControlState::SetSelectionDirection(const nsAString& aDirection,
2198 ErrorResult& aRv) {
2199 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2201 uint32_t start, end;
2202 GetSelectionRange(&start, &end, aRv);
2203 if (aRv.Failed()) {
2204 return;
2207 SetSelectionRange(start, end, dir, aRv);
2208 // The instance may have already been deleted here.
2211 static SelectionDirection DirectionStringToSelectionDirection(
2212 const Optional<nsAString>& aDirection) {
2213 if (!aDirection.WasPassed()) {
2214 // We don't support directionless selections.
2215 return SelectionDirection::Forward;
2218 return DirectionStringToSelectionDirection(aDirection.Value());
2221 void TextControlState::SetSelectionRange(uint32_t aSelectionStart,
2222 uint32_t aSelectionEnd,
2223 const Optional<nsAString>& aDirection,
2224 ErrorResult& aRv,
2225 ScrollAfterSelection aScroll) {
2226 SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
2228 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv, aScroll);
2229 // The instance may have already been deleted here.
2232 void TextControlState::SetRangeText(const nsAString& aReplacement,
2233 ErrorResult& aRv) {
2234 uint32_t start, end;
2235 GetSelectionRange(&start, &end, aRv);
2236 if (aRv.Failed()) {
2237 return;
2240 SetRangeText(aReplacement, start, end, SelectionMode::Preserve, aRv,
2241 Some(start), Some(end));
2242 // The instance may have already been deleted here.
2245 void TextControlState::SetRangeText(const nsAString& aReplacement,
2246 uint32_t aStart, uint32_t aEnd,
2247 SelectionMode aSelectMode, ErrorResult& aRv,
2248 const Maybe<uint32_t>& aSelectionStart,
2249 const Maybe<uint32_t>& aSelectionEnd) {
2250 if (aStart > aEnd) {
2251 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2252 return;
2255 AutoTextControlHandlingState handlingSetRangeText(
2256 *this, TextControlAction::SetRangeText);
2258 nsAutoString value;
2259 mTextCtrlElement->GetValueFromSetRangeText(value);
2260 uint32_t inputValueLength = value.Length();
2262 if (aStart > inputValueLength) {
2263 aStart = inputValueLength;
2266 if (aEnd > inputValueLength) {
2267 aEnd = inputValueLength;
2270 uint32_t selectionStart, selectionEnd;
2271 if (!aSelectionStart) {
2272 MOZ_ASSERT(!aSelectionEnd);
2273 GetSelectionRange(&selectionStart, &selectionEnd, aRv);
2274 if (aRv.Failed()) {
2275 return;
2277 } else {
2278 MOZ_ASSERT(aSelectionEnd);
2279 selectionStart = *aSelectionStart;
2280 selectionEnd = *aSelectionEnd;
2283 // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
2284 Selection* selection =
2285 mSelCon ? mSelCon->GetSelection(SelectionType::eNormal) : nullptr;
2286 SelectionBatcher selectionBatcher(
2287 selection, __FUNCTION__,
2288 nsISelectionListener::JS_REASON); // no-op if nullptr
2290 MOZ_ASSERT(aStart <= aEnd);
2291 value.Replace(aStart, aEnd - aStart, aReplacement);
2292 nsresult rv =
2293 MOZ_KnownLive(mTextCtrlElement)->SetValueFromSetRangeText(value);
2294 if (NS_FAILED(rv)) {
2295 aRv.Throw(rv);
2296 return;
2299 uint32_t newEnd = aStart + aReplacement.Length();
2300 int32_t delta = aReplacement.Length() - (aEnd - aStart);
2302 switch (aSelectMode) {
2303 case SelectionMode::Select:
2304 selectionStart = aStart;
2305 selectionEnd = newEnd;
2306 break;
2307 case SelectionMode::Start:
2308 selectionStart = selectionEnd = aStart;
2309 break;
2310 case SelectionMode::End:
2311 selectionStart = selectionEnd = newEnd;
2312 break;
2313 case SelectionMode::Preserve:
2314 if (selectionStart > aEnd) {
2315 selectionStart += delta;
2316 } else if (selectionStart > aStart) {
2317 selectionStart = aStart;
2320 if (selectionEnd > aEnd) {
2321 selectionEnd += delta;
2322 } else if (selectionEnd > aStart) {
2323 selectionEnd = newEnd;
2325 break;
2326 default:
2327 MOZ_ASSERT_UNREACHABLE("Unknown mode!");
2330 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
2331 if (IsSelectionCached()) {
2332 // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
2333 GetSelectionProperties().SetMaxLength(value.Length());
2337 void TextControlState::DestroyEditor() {
2338 // notify the editor that we are going away
2339 if (mEditorInitialized) {
2340 // FYI: TextEditor checks whether it's destroyed or not immediately after
2341 // changes the DOM tree or selection so that it's safe to call
2342 // PreDestroy() here even while we're handling actions with
2343 // mTextEditor.
2344 MOZ_ASSERT(!mPasswordMaskData);
2345 RefPtr<TextEditor> textEditor = mTextEditor;
2346 mPasswordMaskData = textEditor->PreDestroy();
2347 MOZ_ASSERT_IF(mPasswordMaskData, !mPasswordMaskData->mTimer);
2348 mEditorInitialized = false;
2352 void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
2353 if (NS_WARN_IF(!mBoundFrame)) {
2354 return;
2357 // If it was, however, it should be unbounded from the same frame.
2358 MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2359 if (aFrame && aFrame != mBoundFrame) {
2360 return;
2363 AutoTextControlHandlingState handlingUnbindFromFrame(
2364 *this, TextControlAction::UnbindFromFrame);
2366 if (mSelCon) {
2367 mSelCon->SelectionWillLoseFocus();
2370 // We need to start storing the value outside of the editor if we're not
2371 // going to use it anymore, so retrieve it for now.
2372 nsAutoString value;
2373 GetValue(value, true);
2375 if (mRestoringSelection) {
2376 mRestoringSelection->Revoke();
2377 mRestoringSelection = nullptr;
2380 // Save our selection state if needed.
2381 // Note that GetSelectionRange will attempt to work with our selection
2382 // controller, so we should make sure we do it before we start doing things
2383 // like destroying our editor (if we have one), tearing down the selection
2384 // controller, and so forth.
2385 if (!IsSelectionCached()) {
2386 // Go ahead and cache it now.
2387 uint32_t start = 0, end = 0;
2388 GetSelectionRange(&start, &end, IgnoreErrors());
2390 nsITextControlFrame::SelectionDirection direction =
2391 GetSelectionDirection(IgnoreErrors());
2393 SelectionProperties& props = GetSelectionProperties();
2394 props.SetMaxLength(value.Length());
2395 props.SetStart(start);
2396 props.SetEnd(end);
2397 props.SetDirection(direction);
2398 mSelectionCached = true;
2401 // Destroy our editor
2402 DestroyEditor();
2404 // Clean up the controller
2405 if (!SuppressEventHandlers(mBoundFrame->PresContext())) {
2406 nsCOMPtr<nsIControllers> controllers;
2407 if (HTMLInputElement* inputElement =
2408 HTMLInputElement::FromNodeOrNull(mTextCtrlElement)) {
2409 inputElement->GetControllers(getter_AddRefs(controllers));
2410 } else {
2411 HTMLTextAreaElement* textAreaElement =
2412 HTMLTextAreaElement::FromNodeOrNull(mTextCtrlElement);
2413 if (textAreaElement) {
2414 textAreaElement->GetControllers(getter_AddRefs(controllers));
2418 if (controllers) {
2419 uint32_t numControllers;
2420 nsresult rv = controllers->GetControllerCount(&numControllers);
2421 NS_ASSERTION((NS_SUCCEEDED(rv)),
2422 "bad result in gfx text control destructor");
2423 for (uint32_t i = 0; i < numControllers; i++) {
2424 nsCOMPtr<nsIController> controller;
2425 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2426 if (NS_SUCCEEDED(rv) && controller) {
2427 nsCOMPtr<nsIControllerContext> editController =
2428 do_QueryInterface(controller);
2429 if (editController) {
2430 editController->SetCommandContext(nullptr);
2437 if (mSelCon) {
2438 if (mTextListener) {
2439 mTextListener->EndListeningToSelectionChange();
2442 mSelCon->SetScrollableFrame(nullptr);
2443 mSelCon = nullptr;
2446 if (mTextListener) {
2447 mTextListener->SetFrame(nullptr);
2449 EventListenerManager* manager =
2450 mTextCtrlElement->GetExistingListenerManager();
2451 if (manager) {
2452 manager->RemoveEventListenerByType(mTextListener, u"keydown"_ns,
2453 TrustedEventsAtSystemGroupBubble());
2454 manager->RemoveEventListenerByType(mTextListener, u"keypress"_ns,
2455 TrustedEventsAtSystemGroupBubble());
2456 manager->RemoveEventListenerByType(mTextListener, u"keyup"_ns,
2457 TrustedEventsAtSystemGroupBubble());
2460 mTextListener = nullptr;
2463 mBoundFrame = nullptr;
2465 // Now that we don't have a frame any more, store the value in the text
2466 // buffer. The only case where we don't do this is if a value transfer is in
2467 // progress.
2468 if (!mValueTransferInProgress) {
2469 DebugOnly<bool> ok = SetValue(value, ValueSetterOption::ByInternalAPI);
2470 // TODO Find something better to do if this fails...
2471 NS_WARNING_ASSERTION(ok, "SetValue() couldn't allocate memory");
2475 void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap) const {
2476 // While SetValue() is being called and requesting to commit composition to
2477 // IME, GetValue() may be called for appending text or something. Then, we
2478 // need to return the latest aValue of SetValue() since the value hasn't
2479 // been set to the editor yet.
2480 // XXX After implementing "beforeinput" event, this becomes wrong. The
2481 // value should be modified immediately after "beforeinput" event for
2482 // "insertReplacementText".
2483 if (mHandlingState &&
2484 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2485 aValue = mHandlingState->GetSettingValue();
2486 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2487 return;
2490 if (mTextEditor && mBoundFrame &&
2491 (mEditorInitialized || !IsSingleLineTextControl())) {
2492 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2493 aValue = mBoundFrame->CachedValue();
2494 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2495 return;
2498 aValue.Truncate(); // initialize out param
2500 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2501 nsIDocumentEncoder::OutputPreformatted |
2502 nsIDocumentEncoder::OutputPersistNBSP |
2503 nsIDocumentEncoder::OutputBodyOnly);
2504 if (!aIgnoreWrap) {
2505 TextControlElement::nsHTMLTextWrap wrapProp;
2506 if (mTextCtrlElement &&
2507 TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
2508 wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
2509 flags |= nsIDocumentEncoder::OutputWrap;
2513 // What follows is a bit of a hack. The problem is that we could be in
2514 // this method because we're being destroyed for whatever reason while
2515 // script is executing. If that happens, editor will run with the
2516 // privileges of the executing script, which means it may not be able to
2517 // access its own DOM nodes! Let's try to deal with that by pushing a null
2518 // JSContext on the JSContext stack to make it clear that we're native
2519 // code. Note that any script that's directly trying to access our value
2520 // has to be going through some scriptable object to do that and that
2521 // already does the relevant security checks.
2522 // XXXbz if we could just get the textContent of our anonymous content (eg
2523 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2524 // this.
2525 { /* Scope for AutoNoJSAPI. */
2526 AutoNoJSAPI nojsapi;
2528 DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2529 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2530 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2532 // Only when the result doesn't include line breaks caused by hard-wrap,
2533 // mCacheValue should cache the value.
2534 if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2535 mBoundFrame->CacheValue(aValue);
2536 } else {
2537 mBoundFrame->ClearCachedValue();
2539 } else {
2540 if (!mTextCtrlElement->ValueChanged() || mValue.IsVoid()) {
2541 // Use nsString to avoid copying string buffer at setting aValue.
2542 nsString value;
2543 mTextCtrlElement->GetDefaultValueFromContent(value);
2544 // TODO: We should make default value not include \r.
2545 nsContentUtils::PlatformToDOMLineBreaks(value);
2546 aValue = value;
2547 } else {
2548 aValue = mValue;
2549 MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
2554 bool TextControlState::ValueEquals(const nsAString& aValue) const {
2555 // We can avoid copying string buffer in many cases. Therefore, we should
2556 // use nsString rather than nsAutoString here.
2557 nsString value;
2558 GetValue(value, true);
2559 return aValue.Equals(value);
2562 #ifdef DEBUG
2563 // @param aOptions TextControlState::ValueSetterOptions
2564 bool AreFlagsNotDemandingContradictingMovements(
2565 const ValueSetterOptions& aOptions) {
2566 return !aOptions.contains(
2567 {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward,
2568 ValueSetterOption::MoveCursorToEndIfValueChanged});
2570 #endif // DEBUG
2572 bool TextControlState::SetValue(const nsAString& aValue,
2573 const nsAString* aOldValue,
2574 const ValueSetterOptions& aOptions) {
2575 if (mHandlingState &&
2576 mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
2577 // GetValue doesn't return current text frame's content during committing.
2578 // So we cannot trust this old value
2579 aOldValue = nullptr;
2582 if (mPasswordMaskData) {
2583 if (mHandlingState &&
2584 mHandlingState->Is(TextControlAction::UnbindFromFrame)) {
2585 // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
2586 } else {
2587 // Otherwise, we should mask the new password, even if it's same value
2588 // since the same value may be one for different web app's.
2589 mPasswordMaskData->Reset();
2593 const bool wasHandlingSetValue =
2594 mHandlingState && mHandlingState->IsHandling(TextControlAction::SetValue);
2596 ErrorResult error;
2597 AutoTextControlHandlingState handlingSetValue(
2598 *this, TextControlAction::SetValue, aValue, aOldValue, aOptions, error);
2599 if (error.Failed()) {
2600 MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
2601 error.SuppressException();
2602 return false;
2605 const auto changeKind = [&] {
2606 if (aOptions.contains(ValueSetterOption::ByInternalAPI)) {
2607 return ValueChangeKind::Internal;
2609 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2610 return ValueChangeKind::UserInteraction;
2612 return ValueChangeKind::Script;
2613 }();
2615 if (changeKind == ValueChangeKind::Script) {
2616 // This value change will not be interactive. If we're an input that was
2617 // interactively edited, save the last interactive value now before it goes
2618 // away.
2619 if (auto* input = HTMLInputElement::FromNode(mTextCtrlElement)) {
2620 if (input->LastValueChangeWasInteractive()) {
2621 GetValue(mLastInteractiveValue, /* aIgnoreWrap = */ true);
2626 // Note that if this may be called during reframe of the editor. In such
2627 // case, we shouldn't commit composition. Therefore, when this is called
2628 // for internal processing, we shouldn't commit the composition.
2629 // TODO: In strictly speaking, we should move committing composition into
2630 // editor because if "beforeinput" for this setting value is canceled,
2631 // we shouldn't commit composition. However, in Firefox, we never
2632 // call this via `setUserInput` during composition. Therefore, the
2633 // bug must not be reproducible actually.
2634 if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
2635 aOptions.contains(ValueSetterOption::ByContentAPI)) {
2636 if (EditorHasComposition()) {
2637 // When this is called recursively, there shouldn't be composition.
2638 if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
2639 // Don't request to commit composition again. But if it occurs,
2640 // we should skip to set the new value to the editor here. It should
2641 // be set later with the newest value.
2642 return true;
2644 if (NS_WARN_IF(!mBoundFrame)) {
2645 // We're not sure if this case is possible.
2646 } else {
2647 // If setting value won't change current value, we shouldn't commit
2648 // composition for compatibility with the other browsers.
2649 MOZ_ASSERT(!aOldValue || mBoundFrame->TextEquals(*aOldValue));
2650 bool isSameAsCurrentValue =
2651 aOldValue
2652 ? aOldValue->Equals(handlingSetValue.GetSettingValue())
2653 : mBoundFrame->TextEquals(handlingSetValue.GetSettingValue());
2654 if (isSameAsCurrentValue) {
2655 // Note that in this case, we shouldn't fire any events with setting
2656 // value because event handlers may try to set value recursively but
2657 // we cannot commit composition at that time due to unsafe to run
2658 // script (see below).
2659 return true;
2662 // If there is composition, need to commit composition first because
2663 // other browsers do that.
2664 // NOTE: We don't need to block nested calls of this because input nor
2665 // other events won't be fired by setting values and script blocker
2666 // is used during setting the value to the editor. IE also allows
2667 // to set the editor value on the input event which is caused by
2668 // forcibly committing composition.
2669 AutoTextControlHandlingState handlingCommitComposition(
2670 *this, TextControlAction::CommitComposition);
2671 if (nsContentUtils::IsSafeToRunScript()) {
2672 // WARNING: During this call, compositionupdate, compositionend, input
2673 // events will be fired. Therefore, everything can occur. E.g., the
2674 // document may be unloaded.
2675 RefPtr<TextEditor> textEditor = mTextEditor;
2676 nsresult rv = textEditor->CommitComposition();
2677 if (handlingCommitComposition.IsTextControlStateDestroyed()) {
2678 return true;
2680 if (NS_FAILED(rv)) {
2681 NS_WARNING("TextControlState failed to commit composition");
2682 return true;
2684 // Note that if a composition event listener sets editor value again,
2685 // we should use the new value here. The new value is stored in
2686 // handlingSetValue right now.
2687 } else {
2688 NS_WARNING(
2689 "SetValue() is called when there is composition but "
2690 "it's not safe to request to commit the composition");
2695 if (mTextEditor && mBoundFrame) {
2696 if (!SetValueWithTextEditor(handlingSetValue)) {
2697 return false;
2699 } else if (!SetValueWithoutTextEditor(handlingSetValue)) {
2700 return false;
2703 // If we were handling SetValue() before, don't update the DOM state twice,
2704 // just let the outer call do so.
2705 if (!wasHandlingSetValue) {
2706 handlingSetValue.GetTextControlElement()->OnValueChanged(
2707 changeKind, handlingSetValue.GetSettingValue());
2709 return true;
2712 bool TextControlState::SetValueWithTextEditor(
2713 AutoTextControlHandlingState& aHandlingSetValue) {
2714 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2715 MOZ_ASSERT(mTextEditor);
2716 MOZ_ASSERT(mBoundFrame);
2717 NS_WARNING_ASSERTION(!EditorHasComposition(),
2718 "Failed to commit composition before setting value. "
2719 "Investigate the cause!");
2721 #ifdef DEBUG
2722 if (IsSingleLineTextControl()) {
2723 NS_ASSERTION(mEditorInitialized || aHandlingSetValue.IsHandling(
2724 TextControlAction::PrepareEditor),
2725 "We should never try to use the editor if we're not "
2726 "initialized unless we're being initialized");
2728 #endif
2730 MOZ_ASSERT(!aHandlingSetValue.GetOldValue() ||
2731 mBoundFrame->TextEquals(*aHandlingSetValue.GetOldValue()));
2732 bool isSameAsCurrentValue =
2733 aHandlingSetValue.GetOldValue()
2734 ? aHandlingSetValue.GetOldValue()->Equals(
2735 aHandlingSetValue.GetSettingValue())
2736 : mBoundFrame->TextEquals(aHandlingSetValue.GetSettingValue());
2738 // this is necessary to avoid infinite recursion
2739 if (isSameAsCurrentValue) {
2740 return true;
2743 RefPtr<TextEditor> textEditor = mTextEditor;
2745 nsCOMPtr<Document> document = textEditor->GetDocument();
2746 if (NS_WARN_IF(!document)) {
2747 return true;
2750 // Time to mess with our security context... See comments in GetValue()
2751 // for why this is needed. Note that we have to do this up here, because
2752 // otherwise SelectAll() will fail.
2753 AutoNoJSAPI nojsapi;
2755 // FYI: It's safe to use raw pointer for selection here because
2756 // SelectionBatcher will grab it with RefPtr.
2757 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
2758 SelectionBatcher selectionBatcher(selection, __FUNCTION__);
2760 // get the flags, remove readonly, disabled and max-length,
2761 // set the value, restore flags
2762 AutoRestoreEditorState restoreState(textEditor);
2764 aHandlingSetValue.WillSetValueWithTextEditor();
2766 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2767 ValueSetterOption::BySetUserInputAPI)) {
2768 // If the caller inserts text as part of user input, for example,
2769 // autocomplete, we need to replace the text as "insert string"
2770 // because undo should cancel only this operation (i.e., previous
2771 // transactions typed by user shouldn't be merged with this).
2772 // In this case, we need to dispatch "input" event because
2773 // web apps may need to know the user's operation.
2774 // In this case, we need to dispatch "beforeinput" events since
2775 // we're emulating the user's input. Passing nullptr as
2776 // nsIPrincipal means that that may be user's input. So, let's
2777 // do it.
2778 nsresult rv = textEditor->ReplaceTextAsAction(
2779 aHandlingSetValue.GetSettingValue(), nullptr,
2780 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2781 ? TextEditor::AllowBeforeInputEventCancelable::Yes
2782 : TextEditor::AllowBeforeInputEventCancelable::No,
2783 nullptr);
2784 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2785 "EditorBase::ReplaceTextAsAction() failed");
2786 return rv != NS_ERROR_OUT_OF_MEMORY;
2789 // Don't dispatch "beforeinput" event nor "input" event for setting value
2790 // by script.
2791 AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
2793 // On <input> or <textarea>, we shouldn't preserve existing undo
2794 // transactions because other browsers do not preserve them too
2795 // and not preserving transactions makes setting value faster.
2797 // (Except if chrome opts into this behavior).
2798 Maybe<AutoDisableUndo> disableUndo;
2799 if (!aHandlingSetValue.ValueSetterOptionsRef().contains(
2800 ValueSetterOption::PreserveUndoHistory)) {
2801 disableUndo.emplace(textEditor);
2804 if (selection) {
2805 // Since we don't use undo transaction, we don't need to store
2806 // selection state. SetText will set selection to tail.
2807 IgnoredErrorResult ignoredError;
2808 MOZ_KnownLive(selection)->RemoveAllRanges(ignoredError);
2809 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2810 "Selection::RemoveAllRanges() failed, but ignored");
2813 // In this case, we makes the editor stop dispatching "input"
2814 // event so that passing nullptr as nsIPrincipal is safe for now.
2815 nsresult rv = textEditor->SetTextAsAction(
2816 aHandlingSetValue.GetSettingValue(),
2817 aHandlingSetValue.ValueSetterOptionsRef().contains(
2818 ValueSetterOption::BySetUserInputAPI) &&
2819 !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2820 ? TextEditor::AllowBeforeInputEventCancelable::No
2821 : TextEditor::AllowBeforeInputEventCancelable::Yes,
2822 nullptr);
2823 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2824 "TextEditor::SetTextAsAction() failed");
2826 // Call the listener's OnEditActionHandled() callback manually if
2827 // OnEditActionHandled() hasn't been called yet since TextEditor don't use
2828 // the transaction manager in this path and it could be that the editor
2829 // would bypass calling the listener for that reason.
2830 if (!aHandlingSetValue.HasEditActionHandled()) {
2831 nsresult rvOnEditActionHandled =
2832 MOZ_KnownLive(aHandlingSetValue.GetTextInputListener())
2833 ->OnEditActionHandled(*textEditor);
2834 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled),
2835 "TextInputListener::OnEditActionHandled() failed");
2836 if (rv != NS_ERROR_OUT_OF_MEMORY) {
2837 rv = rvOnEditActionHandled;
2841 return rv != NS_ERROR_OUT_OF_MEMORY;
2844 bool TextControlState::SetValueWithoutTextEditor(
2845 AutoTextControlHandlingState& aHandlingSetValue) {
2846 MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
2847 MOZ_ASSERT(!mTextEditor || !mBoundFrame);
2848 NS_WARNING_ASSERTION(!EditorHasComposition(),
2849 "Failed to commit composition before setting value. "
2850 "Investigate the cause!");
2852 if (mValue.IsVoid()) {
2853 mValue.SetIsVoid(false);
2856 // We can't just early-return here, because OnValueChanged below still need to
2857 // be called.
2858 if (!mValue.Equals(aHandlingSetValue.GetSettingValue()) ||
2859 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2860 bool handleSettingValue = true;
2861 // If `SetValue()` call is nested, `GetSettingValue()` result will be
2862 // modified. So, we need to store input event data value before
2863 // dispatching beforeinput event.
2864 nsString inputEventData(aHandlingSetValue.GetSettingValue());
2865 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2866 ValueSetterOption::BySetUserInputAPI) &&
2867 StaticPrefs::dom_input_events_beforeinput_enabled() &&
2868 !aHandlingSetValue.HasBeforeInputEventDispatched()) {
2869 // This probably occurs when session restorer sets the old value with
2870 // `setUserInput`. If so, we need to dispatch "beforeinput" event of
2871 // "insertReplacementText" for conforming to the spec. However, the
2872 // spec does NOT treat the session restoring case. Therefore, if this
2873 // breaks session restorere in a lot of web apps, we should probably
2874 // stop dispatching it or make it non-cancelable.
2875 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2876 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2877 aHandlingSetValue.WillDispatchBeforeInputEvent();
2878 nsEventStatus status = nsEventStatus_eIgnore;
2879 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2880 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2881 eEditorBeforeInput, EditorInputType::eInsertReplacementText, nullptr,
2882 InputEventOptions(
2883 inputEventData,
2884 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2885 ? InputEventOptions::NeverCancelable::No
2886 : InputEventOptions::NeverCancelable::Yes),
2887 &status);
2888 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2889 "Failed to dispatch beforeinput event");
2890 if (status == nsEventStatus_eConsumeNoDefault) {
2891 return true; // "beforeinput" event was canceled.
2893 // If we were destroyed by "beforeinput" event listeners, probably, we
2894 // don't need to keep handling it.
2895 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2896 return true;
2898 // Even if "beforeinput" event was not canceled, its listeners may do
2899 // something. If it causes creating `TextEditor` and bind this to a
2900 // frame, we need to use the path, but `TextEditor` shouldn't fire
2901 // "beforeinput" event again. Therefore, we need to prevent editor
2902 // to dispatch it.
2903 if (mTextEditor && mBoundFrame) {
2904 AutoInputEventSuppresser suppressInputEvent(mTextEditor);
2905 if (!SetValueWithTextEditor(aHandlingSetValue)) {
2906 return false;
2908 // If we were destroyed by "beforeinput" event listeners, probably, we
2909 // don't need to keep handling it.
2910 if (aHandlingSetValue.IsTextControlStateDestroyed()) {
2911 return true;
2913 handleSettingValue = false;
2917 if (handleSettingValue) {
2918 if (!mValue.Assign(aHandlingSetValue.GetSettingValue(), fallible)) {
2919 return false;
2922 // Since we have no editor we presumably have cached selection state.
2923 if (IsSelectionCached()) {
2924 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
2925 aHandlingSetValue.ValueSetterOptionsRef()));
2927 SelectionProperties& props = GetSelectionProperties();
2928 // Setting a max length and thus capping selection range early prevents
2929 // selection change detection in setRangeText. Temporarily disable
2930 // capping here with UINT32_MAX, and set it later in ::SetRangeText().
2931 props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
2932 ValueSetterOption::BySetRangeTextAPI)
2933 ? UINT32_MAX
2934 : aHandlingSetValue.GetSettingValue().Length());
2935 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2936 ValueSetterOption::MoveCursorToEndIfValueChanged)) {
2937 props.SetStart(aHandlingSetValue.GetSettingValue().Length());
2938 props.SetEnd(aHandlingSetValue.GetSettingValue().Length());
2939 props.SetDirection(SelectionDirection::Forward);
2940 } else if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2941 ValueSetterOption::
2942 MoveCursorToBeginSetSelectionDirectionForward)) {
2943 props.SetStart(0);
2944 props.SetEnd(0);
2945 props.SetDirection(SelectionDirection::Forward);
2949 // Update the frame display if needed
2950 if (mBoundFrame) {
2951 mBoundFrame->UpdateValueDisplay(true);
2954 // If the text control element has focus, IMEContentObserver is not
2955 // observing the content changes due to no bound frame or no TextEditor.
2956 // Therefore, we need to let IMEContentObserver know all values are being
2957 // replaced.
2958 if (IMEContentObserver* observer = GetIMEContentObserver()) {
2959 observer->OnTextControlValueChangedWhileNotObservable(mValue);
2963 // If this is called as part of user input, we need to dispatch "input"
2964 // event with "insertReplacementText" since web apps may want to know
2965 // the user operation which changes editor value with a built-in function
2966 // like autocomplete, password manager, session restore, etc.
2967 // XXX Should we stop dispatching `input` event if the text control
2968 // element has already removed from the DOM tree by a `beforeinput`
2969 // event listener?
2970 if (aHandlingSetValue.ValueSetterOptionsRef().contains(
2971 ValueSetterOption::BySetUserInputAPI)) {
2972 MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
2974 // Update validity state before dispatching "input" event for its
2975 // listeners like `EditorBase::NotifyEditorObservers()`.
2976 aHandlingSetValue.GetTextControlElement()->OnValueChanged(
2977 ValueChangeKind::UserInteraction,
2978 aHandlingSetValue.GetSettingValue());
2980 ClearLastInteractiveValue();
2982 MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
2983 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2984 MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
2985 eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
2986 InputEventOptions(inputEventData,
2987 InputEventOptions::NeverCancelable::No));
2988 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2989 "Failed to dispatch input event");
2991 } else {
2992 // Even if our value is not actually changing, apparently we need to mark
2993 // our SelectionProperties dirty to make accessibility tests happy.
2994 // Probably because they depend on the SetSelectionRange() call we make on
2995 // our frame in RestoreSelectionState, but I have no idea why they do.
2996 if (IsSelectionCached()) {
2997 SelectionProperties& props = GetSelectionProperties();
2998 props.SetIsDirty();
3002 return true;
3005 void TextControlState::InitializeKeyboardEventListeners() {
3006 // register key listeners
3007 EventListenerManager* manager =
3008 mTextCtrlElement->GetOrCreateListenerManager();
3009 if (manager) {
3010 manager->AddEventListenerByType(mTextListener, u"keydown"_ns,
3011 TrustedEventsAtSystemGroupBubble());
3012 manager->AddEventListenerByType(mTextListener, u"keypress"_ns,
3013 TrustedEventsAtSystemGroupBubble());
3014 manager->AddEventListenerByType(mTextListener, u"keyup"_ns,
3015 TrustedEventsAtSystemGroupBubble());
3018 mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
3021 void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
3022 // If we don't have a preview div, there's nothing to do.
3023 Element* previewDiv = GetPreviewNode();
3024 if (!previewDiv) {
3025 return;
3028 nsAutoString previewValue(aValue);
3030 nsContentUtils::RemoveNewlines(previewValue);
3031 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3032 previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
3035 void TextControlState::GetPreviewText(nsAString& aValue) {
3036 // If we don't have a preview div, there's nothing to do.
3037 Element* previewDiv = GetPreviewNode();
3038 if (!previewDiv) {
3039 return;
3042 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
3043 const nsTextFragment* text = previewDiv->GetFirstChild()->GetText();
3045 aValue.Truncate();
3046 text->AppendTo(aValue);
3049 bool TextControlState::EditorHasComposition() {
3050 return mTextEditor && mTextEditor->IsIMEComposing();
3053 IMEContentObserver* TextControlState::GetIMEContentObserver() const {
3054 if (NS_WARN_IF(!mTextCtrlElement) ||
3055 mTextCtrlElement != IMEStateManager::GetFocusedElement()) {
3056 return nullptr;
3058 return IMEStateManager::GetActiveContentObserver();
3061 } // namespace mozilla