1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TextControlState.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/CaretAssociationHint.h"
10 #include "mozilla/IMEContentObserver.h"
11 #include "mozilla/IMEStateManager.h"
12 #include "mozilla/TextInputListener.h"
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"
60 using ValueSetterOption
= TextControlState::ValueSetterOption
;
61 using ValueSetterOptions
= TextControlState::ValueSetterOptions
;
62 using SelectionDirection
= nsITextControlFrame::SelectionDirection
;
64 /*****************************************************************************
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
)
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()) {
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
)) {
99 aWrapProp
= eHTMLTextWrap_Hard
;
102 aWrapProp
= eHTMLTextWrap_Off
;
110 already_AddRefed
<TextControlElement
>
111 TextControlElement::GetTextControlElementFromEditingHost(nsIContent
* aHost
) {
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();
125 return FocusTristate::eUnfocusable
;
128 // first see if we are disabled or not. If disabled then do nothing.
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
) {
144 return aEditorBase
.SetFlags(aFlags
);
147 /*****************************************************************************
148 * mozilla::AutoInputEventSuppresser
149 *****************************************************************************/
151 class MOZ_STACK_CLASS AutoInputEventSuppresser final
{
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
);
167 RefPtr
<TextEditor
> mTextEditor
;
168 bool mOuterTransaction
;
171 /*****************************************************************************
172 * mozilla::RestoreSelectionState
173 *****************************************************************************/
175 class RestoreSelectionState
: public Runnable
{
177 RestoreSelectionState(TextControlState
* aState
, nsTextControlFrame
* aFrame
)
178 : Runnable("RestoreSelectionState"),
180 mTextControlState(aState
) {}
182 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
183 if (!mTextControlState
) {
187 AutoHideSelectionChanges
hideSelectionChanges(
188 mFrame
->GetConstFrameSelection());
191 // EnsureEditorInitialized and SetSelectionRange leads to
192 // Selection::AddRangeAndSelectFramesAndNotifyListeners which flushes
193 // Layout - need to block script to avoid nested PrepareEditor calls (bug
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();
211 // Let the text editor tell us we're no longer relevant - avoids use of
215 mTextControlState
= nullptr;
219 nsTextControlFrame
* mFrame
;
220 TextControlState
* mTextControlState
;
223 /*****************************************************************************
224 * mozilla::AutoRestoreEditorState
225 *****************************************************************************/
227 class MOZ_RAII AutoRestoreEditorState final
{
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
);
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
{
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");
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()) {
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");
301 DebugOnly
<bool> disabledUndoRedo
= mTextEditor
->DisableUndoRedo();
302 NS_WARNING_ASSERTION(disabledUndoRedo
,
303 "Failed to disable undo/redo transactions");
308 TextEditor
* mTextEditor
;
309 int32_t mNumberOfMaximumTransactions
;
312 static bool SuppressEventHandlers(nsPresContext
* aPresContext
) {
313 bool suppressHandlers
= false;
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;
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
;
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
;
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
)
398 NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController
, mFrameSelection
)
400 TextInputSelectionController::TextInputSelectionController(
401 PresShell
* aPresShell
, nsIContent
* aLimiter
)
402 : mScrollFrame(nullptr) {
404 bool accessibleCaretEnabled
=
405 PresShell::AccessibleCaretEnabled(aLimiter
->OwnerDoc()->GetDocShell());
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
) {
427 return mFrameSelection
->GetSelection(aSelectionType
);
431 TextInputSelectionController::SetDisplaySelection(int16_t aToggle
) {
432 if (!mFrameSelection
) {
433 return NS_ERROR_NULL_POINTER
;
435 mFrameSelection
->SetDisplaySelection(aToggle
);
440 TextInputSelectionController::GetDisplaySelection(int16_t* aToggle
) {
441 if (!mFrameSelection
) {
442 return NS_ERROR_NULL_POINTER
;
444 *aToggle
= mFrameSelection
->GetDisplaySelection();
449 TextInputSelectionController::SetSelectionFlags(int16_t aToggle
) {
450 return NS_OK
; // stub this out. not used in input
454 TextInputSelectionController::GetSelectionFlags(int16_t* aOutEnable
) {
455 *aOutEnable
= nsISelectionDisplay::DISPLAY_TEXT
;
460 TextInputSelectionController::GetSelectionFromScript(
461 RawSelectionType aRawSelectionType
, Selection
** aSelection
) {
462 if (!mFrameSelection
) {
463 return NS_ERROR_NULL_POINTER
;
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
);
478 Selection
* TextInputSelectionController::GetSelection(
479 RawSelectionType aRawSelectionType
) {
480 return GetSelection(ToSelectionType(aRawSelectionType
));
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
);
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
);
514 TextInputSelectionController::SetCaretEnabled(bool enabled
) {
515 if (!mPresShellWeak
) {
516 return NS_ERROR_NOT_INITIALIZED
;
518 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
);
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
526 presShell
->SetCaretEnabled(enabled
);
532 TextInputSelectionController::SetCaretReadOnly(bool aReadOnly
) {
533 if (!mPresShellWeak
) {
534 return NS_ERROR_NOT_INITIALIZED
;
537 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
539 return NS_ERROR_FAILURE
;
541 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
543 return NS_ERROR_FAILURE
;
546 if (!mFrameSelection
) {
547 return NS_ERROR_FAILURE
;
550 Selection
* selection
= mFrameSelection
->GetSelection(SelectionType::eNormal
);
552 caret
->SetCaretReadOnly(aReadOnly
);
558 TextInputSelectionController::GetCaretEnabled(bool* _retval
) {
559 return GetCaretVisible(_retval
);
563 TextInputSelectionController::GetCaretVisible(bool* _retval
) {
564 if (!mPresShellWeak
) {
565 return NS_ERROR_NOT_INITIALIZED
;
568 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
570 return NS_ERROR_FAILURE
;
572 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
574 return NS_ERROR_FAILURE
;
576 *_retval
= caret
->IsVisible();
581 TextInputSelectionController::SetCaretVisibilityDuringSelection(
583 if (!mPresShellWeak
) {
584 return NS_ERROR_NOT_INITIALIZED
;
587 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
589 return NS_ERROR_FAILURE
;
591 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
593 return NS_ERROR_FAILURE
;
595 Selection
* selection
= mFrameSelection
->GetSelection(SelectionType::eNormal
);
597 caret
->SetVisibilityDuringSelection(aVisibility
);
603 TextInputSelectionController::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
605 if (!mFrameSelection
) {
606 return NS_ERROR_NULL_POINTER
;
608 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
609 return frameSelection
->PhysicalMove(aDirection
, aAmount
, aExtend
);
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
);
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
);
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
);
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
);
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.
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
671 return ScrollSelectionIntoView(
672 nsISelectionController::SELECTION_NORMAL
,
673 nsISelectionController::SELECTION_FOCUS_REGION
,
674 nsISelectionController::SCROLL_SYNCHRONOUS
|
675 nsISelectionController::SCROLL_FOR_CARET_MOVE
);
679 TextInputSelectionController::CompleteScroll(bool aForward
) {
681 return NS_ERROR_NOT_INITIALIZED
;
684 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1), ScrollUnit::WHOLE
,
685 ScrollMode::Instant
);
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();
699 return NS_ERROR_UNEXPECTED
;
702 // make the caret be either at the very beginning (0) or the very end
704 CaretAssociationHint hint
= CaretAssociationHint::Before
;
706 offset
= parentDIV
->GetChildCount();
708 // Prevent the caret from being placed after the last
709 // BR node in the content tree!
712 nsIContent
* child
= parentDIV
->GetLastChild();
714 if (child
->IsHTMLElement(nsGkAtoms::br
)) {
716 hint
= CaretAssociationHint::After
; // for Bug 106855
721 const RefPtr
<nsIContent
> pinnedParentDIV
{parentDIV
};
722 const nsFrameSelection::FocusMode focusMode
=
723 aExtend
? nsFrameSelection::FocusMode::kExtendSelection
724 : nsFrameSelection::FocusMode::kCollapseToNewPoint
;
725 frameSelection
->HandleClick(pinnedParentDIV
, offset
, offset
, focusMode
, hint
);
727 // if we got this far, attempt to scroll no matter what the above result is
728 return CompleteScroll(aForward
);
732 TextInputSelectionController::ScrollPage(bool aForward
) {
734 return NS_ERROR_NOT_INITIALIZED
;
737 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1), ScrollUnit::PAGES
,
743 TextInputSelectionController::ScrollLine(bool aForward
) {
745 return NS_ERROR_NOT_INITIALIZED
;
748 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1), ScrollUnit::LINES
,
754 TextInputSelectionController::ScrollCharacter(bool aRight
) {
756 return NS_ERROR_NOT_INITIALIZED
;
759 mScrollFrame
->ScrollBy(nsIntPoint(aRight
? 1 : -1, 0), ScrollUnit::LINES
,
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
)
786 mTxtCtrlElement(aTxtCtrlElement
),
787 mTextControlState(aTxtCtrlElement
? aTxtCtrlElement
->GetTextControlState()
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
)
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
,
815 if (!mListeningToSelectionChange
) {
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
) {
860 mSelectionWasCollapsed
= collapsed
;
862 if (!weakFrame
.IsAlive() || !mFrame
||
863 !nsContentUtils::IsFocusedContent(mFrame
->GetContent())) {
867 UpdateTextInputCommands(u
"select"_ns
);
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
);
878 input
->GetControllers(getter_AddRefs(controllers
));
880 HTMLTextAreaElement
* textArea
= HTMLTextAreaElement::FromNode(content
);
883 textArea
->GetControllers(getter_AddRefs(controllers
));
888 NS_WARNING("Could not get controllers");
892 const char* commandStr
= WidgetKeyboardEvent::GetCommandStr(aCommand
);
894 nsCOMPtr
<nsIController
> controller
;
895 controllers
->GetControllerForCommand(commandStr
, getter_AddRefs(controller
));
901 if (NS_WARN_IF(NS_FAILED(
902 controller
->IsCommandEnabled(commandStr
, &commandEnabled
)))) {
905 if (commandEnabled
) {
906 controller
->DoCommand(commandStr
);
910 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
911 TextInputListener::HandleEvent(Event
* aEvent
) {
912 if (aEvent
->DefaultPrevented()) {
916 if (!aEvent
->IsTrusted()) {
920 RefPtr
<KeyboardEvent
> keyEvent
= aEvent
->AsKeyboardEvent();
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>.
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
)) {
954 if (!handler
->KeyEventMatched(keyEvent
, 0, IgnoreModifierState())) {
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
)) {
968 auto ExecuteNativeKeyBindings
=
969 [&](TextControlElement
& aTextControlElement
)
970 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION
-> bool {
971 if (widgetKeyEvent
->mMessage
!= eKeyPress
) {
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
))) {
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();
1003 OwningNonNull
<TextControlElement
> textControlElement(*mTxtCtrlElement
);
1005 ui_key_textcontrol_prefer_native_key_bindings_over_builtin_shortcut_key_definitions()) {
1006 if (!ExecuteNativeKeyBindings(textControlElement
)) {
1007 ExecuteOurShortcutKeys(textControlElement
);
1010 if (!ExecuteOurShortcutKeys(textControlElement
)) {
1011 ExecuteNativeKeyBindings(textControlElement
);
1017 nsresult
TextInputListener::OnEditActionHandled(TextEditor
& aTextEditor
) {
1019 // XXX Do we still need this or can we just remove the mFrame and
1020 // frame.IsAlive() conditions below?
1021 AutoWeakFrame weakFrame
= mFrame
;
1023 // Update the undo / redo menus
1025 size_t numUndoItems
= aTextEditor
.NumberOfUndoItems();
1026 size_t numRedoItems
= aTextEditor
.NumberOfRedoItems();
1027 if ((numUndoItems
&& !mHadUndoItems
) || (!numUndoItems
&& mHadUndoItems
) ||
1028 (numRedoItems
&& !mHadRedoItems
) || (!numRedoItems
&& mHadRedoItems
)) {
1029 // Modify the menu if undo or redo items are different
1030 UpdateTextInputCommands(u
"undo"_ns
);
1032 mHadUndoItems
= numUndoItems
!= 0;
1033 mHadRedoItems
= numRedoItems
!= 0;
1036 if (weakFrame
.IsAlive()) {
1037 HandleValueChanged(aTextEditor
);
1041 return mTextControlState
? mTextControlState
->OnEditActionHandled() : NS_OK
;
1044 void TextInputListener::HandleValueChanged(TextEditor
& aTextEditor
) {
1045 // Make sure we know we were changed (do NOT set this to false if there are
1046 // no undo items; JS could change the value and we'd still need to save it)
1047 if (mSetValueChanged
) {
1048 mTxtCtrlElement
->SetValueChanged(true);
1051 if (!mSettingValue
) {
1052 // NOTE(emilio): execCommand might get here even though it might not be a
1053 // "proper" user-interactive change. Might be worth reconsidering which
1054 // ValueChangeKind are we passing down.
1055 mTxtCtrlElement
->OnValueChanged(ValueChangeKind::UserInteraction
,
1056 aTextEditor
.IsEmpty(), nullptr);
1057 if (mTextControlState
) {
1058 mTextControlState
->ClearLastInteractiveValue();
1063 nsresult
TextInputListener::UpdateTextInputCommands(
1064 const nsAString
& aCommandsToUpdate
) {
1065 nsIContent
* content
= mFrame
->GetContent();
1066 if (NS_WARN_IF(!content
)) {
1067 return NS_ERROR_FAILURE
;
1069 nsCOMPtr
<Document
> doc
= content
->GetComposedDoc();
1070 if (NS_WARN_IF(!doc
)) {
1071 return NS_ERROR_FAILURE
;
1073 nsPIDOMWindowOuter
* domWindow
= doc
->GetWindow();
1074 if (NS_WARN_IF(!domWindow
)) {
1075 return NS_ERROR_FAILURE
;
1077 domWindow
->UpdateCommands(aCommandsToUpdate
);
1081 /*****************************************************************************
1082 * mozilla::AutoTextControlHandlingState
1084 * This class is temporarily created in the stack and can manage nested
1085 * handling state of TextControlState. While this instance exists, lifetime of
1086 * TextControlState which created the instance is guaranteed. In other words,
1087 * you can use this class as "kungFuDeathGrip" for TextControlState.
1088 *****************************************************************************/
1090 enum class TextControlAction
{
1101 class MOZ_STACK_CLASS AutoTextControlHandlingState
{
1103 AutoTextControlHandlingState() = delete;
1104 explicit AutoTextControlHandlingState(const AutoTextControlHandlingState
&) =
1106 AutoTextControlHandlingState(AutoTextControlHandlingState
&&) = delete;
1107 void operator=(AutoTextControlHandlingState
&) = delete;
1108 void operator=(const AutoTextControlHandlingState
&) = delete;
1111 * Generic constructor. If TextControlAction does not require additional
1112 * data, must use this constructor.
1114 MOZ_CAN_RUN_SCRIPT
AutoTextControlHandlingState(
1115 TextControlState
& aTextControlState
, TextControlAction aTextControlAction
)
1116 : mParent(aTextControlState
.mHandlingState
),
1117 mTextControlState(aTextControlState
),
1118 mTextCtrlElement(aTextControlState
.mTextCtrlElement
),
1119 mTextInputListener(aTextControlState
.mTextListener
),
1120 mTextControlAction(aTextControlAction
) {
1121 MOZ_ASSERT(aTextControlAction
!= TextControlAction::SetValue
,
1122 "Use specific constructor");
1123 MOZ_DIAGNOSTIC_ASSERT_IF(
1124 !aTextControlState
.mTextListener
,
1125 !aTextControlState
.mBoundFrame
|| !aTextControlState
.mTextEditor
);
1126 mTextControlState
.mHandlingState
= this;
1127 if (Is(TextControlAction::CommitComposition
)) {
1128 MOZ_ASSERT(mParent
);
1129 MOZ_ASSERT(mParent
->Is(TextControlAction::SetValue
));
1130 // If we're trying to commit composition before handling SetValue,
1131 // the parent old values will be outdated so that we need to clear
1133 mParent
->InvalidateOldValue();
1138 * TextControlAction::SetValue specific constructor. Current setting value
1139 * must be specified and the creator should check whether we succeeded to
1140 * allocate memory for line breaker conversion.
1142 MOZ_CAN_RUN_SCRIPT
AutoTextControlHandlingState(
1143 TextControlState
& aTextControlState
, TextControlAction aTextControlAction
,
1144 const nsAString
& aSettingValue
, const nsAString
* aOldValue
,
1145 const ValueSetterOptions
& aOptions
, ErrorResult
& aRv
)
1146 : mParent(aTextControlState
.mHandlingState
),
1147 mTextControlState(aTextControlState
),
1148 mTextCtrlElement(aTextControlState
.mTextCtrlElement
),
1149 mTextInputListener(aTextControlState
.mTextListener
),
1150 mSettingValue(aSettingValue
),
1151 mOldValue(aOldValue
),
1152 mValueSetterOptions(aOptions
),
1153 mTextControlAction(aTextControlAction
) {
1154 MOZ_ASSERT(aTextControlAction
== TextControlAction::SetValue
,
1155 "Use generic constructor");
1156 MOZ_DIAGNOSTIC_ASSERT_IF(
1157 !aTextControlState
.mTextListener
,
1158 !aTextControlState
.mBoundFrame
|| !aTextControlState
.mTextEditor
);
1159 mTextControlState
.mHandlingState
= this;
1160 if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue
, fallible
)) {
1161 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1164 // Update all setting value's new value because older value shouldn't
1165 // overwrite newer value.
1167 // If SetValue is nested, parents cannot trust their old value anymore.
1168 // So, we need to clear them.
1169 mParent
->UpdateSettingValueAndInvalidateOldValue(mSettingValue
);
1173 MOZ_CAN_RUN_SCRIPT
~AutoTextControlHandlingState() {
1174 mTextControlState
.mHandlingState
= mParent
;
1175 if (!mParent
&& mTextControlStateDestroyed
) {
1176 mTextControlState
.DeleteOrCacheForReuse();
1178 if (!mTextControlStateDestroyed
&& mPrepareEditorLater
) {
1179 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
1180 MOZ_ASSERT(Is(TextControlAction::SetValue
));
1181 mTextControlState
.PrepareEditor(&mSettingValue
);
1185 void OnDestroyTextControlState() {
1186 if (IsHandling(TextControlAction::Destructor
)) {
1187 // Do nothing since mTextContrlState.DeleteOrCacheForReuse() has
1188 // already been called.
1191 mTextControlStateDestroyed
= true;
1193 mParent
->OnDestroyTextControlState();
1197 void PrepareEditorLater() {
1198 MOZ_ASSERT(IsHandling(TextControlAction::SetValue
));
1199 MOZ_ASSERT(!IsHandling(TextControlAction::PrepareEditor
));
1200 // Look for the top most SetValue.
1201 AutoTextControlHandlingState
* settingValue
= nullptr;
1202 for (AutoTextControlHandlingState
* handlingSomething
= this;
1203 handlingSomething
; handlingSomething
= handlingSomething
->mParent
) {
1204 if (handlingSomething
->Is(TextControlAction::SetValue
)) {
1205 settingValue
= handlingSomething
;
1208 settingValue
->mPrepareEditorLater
= true;
1212 * WillSetValueWithTextEditor() is called when TextControlState sets
1213 * value with its mTextEditor.
1215 void WillSetValueWithTextEditor() {
1216 MOZ_ASSERT(Is(TextControlAction::SetValue
));
1217 MOZ_ASSERT(mTextControlState
.mBoundFrame
);
1218 mTextControlFrame
= mTextControlState
.mBoundFrame
;
1219 // If we'reemulating user input, we don't need to manage mTextInputListener
1220 // by ourselves since everything should be handled by TextEditor as normal
1222 if (mValueSetterOptions
.contains(ValueSetterOption::BySetUserInputAPI
)) {
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
)) {
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
)
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
) {
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.
1294 AutoTextControlHandlingState
handlingSetValueWithoutEditor(
1295 mTextControlState
, TextControlAction::SetValue
, mSettingValue
,
1296 mOldValue
, mValueSetterOptions
& ValueSetterOption::SetValueChanged
,
1298 if (error
.Failed()) {
1299 MOZ_ASSERT(error
.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY
));
1300 error
.SuppressException();
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
) {
1325 return mParent
&& mParent
->IsHandling(aTextControlAction
);
1327 TextControlElement
* GetTextControlElement() const { return mTextCtrlElement
; }
1328 TextInputListener
* GetTextInputListener() const { return mTextInputListener
; }
1329 const ValueSetterOptions
& ValueSetterOptionsRef() const {
1330 MOZ_ASSERT(Is(TextControlAction::SetValue
));
1331 return mValueSetterOptions
;
1333 const nsAString
* GetOldValue() const {
1334 MOZ_ASSERT(Is(TextControlAction::SetValue
));
1337 const nsString
& GetSettingValue() const {
1338 MOZ_ASSERT(IsHandling(TextControlAction::SetValue
));
1339 if (mTextControlAction
== TextControlAction::SetValue
) {
1340 return mSettingValue
;
1342 return mParent
->GetSettingValue();
1346 void UpdateSettingValueAndInvalidateOldValue(const nsString
& aSettingValue
) {
1347 if (mTextControlAction
== TextControlAction::SetValue
) {
1348 mSettingValue
= aSettingValue
;
1350 mOldValue
= nullptr;
1352 mParent
->UpdateSettingValueAndInvalidateOldValue(aSettingValue
);
1355 void InvalidateOldValue() {
1356 mOldValue
= nullptr;
1358 mParent
->InvalidateOldValue();
1362 AutoTextControlHandlingState
* const mParent
;
1363 TextControlState
& mTextControlState
;
1364 // mTextControlFrame should be set immediately before calling methods
1365 // which may destroy the frame. Then, you can check whether the frame
1366 // was destroyed/replaced.
1367 AutoWeakFrame mTextControlFrame
;
1368 // mTextCtrlElement grabs TextControlState::mTextCtrlElement since
1369 // if the text control element releases mTextControlState, only this
1370 // can guarantee the instance of the text control element.
1371 RefPtr
<TextControlElement
> const mTextCtrlElement
;
1372 // mTextInputListener grabs TextControlState::mTextListener because if
1373 // TextControlState is unbind from the frame, it's released.
1374 RefPtr
<TextInputListener
> const mTextInputListener
;
1375 nsAutoString mSettingValue
;
1376 const nsAString
* mOldValue
= nullptr;
1377 ValueSetterOptions mValueSetterOptions
;
1378 TextControlAction
const mTextControlAction
;
1379 bool mTextControlStateDestroyed
= false;
1380 bool mEditActionHandled
= false;
1381 bool mPrepareEditorLater
= false;
1382 bool mBeforeInputEventHasBeenDispatched
= false;
1385 /*****************************************************************************
1386 * mozilla::TextControlState
1387 *****************************************************************************/
1390 * For avoiding allocation cost of the instance, we should reuse instances
1391 * as far as possible.
1393 * FYI: `25` is just a magic number considered without enough investigation,
1394 * but at least, this value must not make damage for footprint.
1395 * Feel free to change it if you find better number.
1397 static constexpr size_t kMaxCountOfCacheToReuse
= 25;
1398 static AutoTArray
<void*, kMaxCountOfCacheToReuse
>* sReleasedInstances
= nullptr;
1399 static bool sHasShutDown
= false;
1401 TextControlState::TextControlState(TextControlElement
* aOwningElement
)
1402 : mTextCtrlElement(aOwningElement
),
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
) {
1418 if (sReleasedInstances
&& !sReleasedInstances
->IsEmpty()) {
1419 mem
= sReleasedInstances
->PopLastElement();
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
);
1435 void TextControlState::Shutdown() {
1436 sHasShutDown
= true;
1437 if (sReleasedInstances
) {
1438 for (void* mem
: *sReleasedInstances
) {
1441 delete sReleasedInstances
;
1445 void TextControlState::Destroy() {
1446 // If we're handling something, we should be deleted later.
1447 if (mHandlingState
) {
1448 mHandlingState
->OnDestroyTextControlState();
1451 DeleteOrCacheForReuse();
1452 // Note that this instance may have already been deleted here. Don't touch
1456 void TextControlState::DeleteOrCacheForReuse() {
1457 MOZ_ASSERT(!IsBusy());
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
);
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
));
1494 mTextEditor
->SetTextInputListener(nullptr);
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
1503 UnbindFromFrame(mBoundFrame
);
1504 mTextEditor
= nullptr;
1506 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1509 MOZ_DIAGNOSTIC_ASSERT(!mBoundFrame
|| !mTextEditor
);
1511 mTextListener
= nullptr;
1514 void TextControlState::Unlink() {
1515 AutoTextControlHandlingState
handlingUnlink(*this, TextControlAction::Unlink
);
1519 void TextControlState::UnlinkInternal() {
1520 MOZ_ASSERT(mHandlingState
);
1521 MOZ_ASSERT(mHandlingState
->Is(TextControlAction::Unlink
));
1522 TextControlState
* tmp
= this;
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()))) {
1548 TextEditor
* TextControlState::GetTextEditorWithoutCreation() const {
1552 nsISelectionController
* TextControlState::GetSelectionController() const {
1556 // Helper class, used below in BindToFrame().
1557 class PrepareEditorEvent
: public Runnable
{
1559 PrepareEditorEvent(TextControlState
& aState
, nsIContent
* aOwnerContent
,
1560 const nsAString
& aCurrentValue
)
1561 : Runnable("PrepareEditorEvent"),
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;
1589 WeakPtr
<TextControlState
> mState
;
1590 nsCOMPtr
<nsIContent
> mOwnerContent
; // strong reference
1591 nsAutoString mCurrentValue
;
1594 nsresult
TextControlState::BindToFrame(nsTextControlFrame
* aFrame
) {
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");
1600 return NS_ERROR_INVALID_ARG
;
1603 NS_ASSERTION(!mBoundFrame
, "Cannot bind twice, need to unbind first");
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
;
1612 GetValue(currentValue
, true, /* aForDisplay = */ false);
1615 mBoundFrame
= aFrame
;
1617 Element
* rootNode
= aFrame
->GetRootNode();
1618 MOZ_ASSERT(rootNode
);
1620 PresShell
* presShell
= aFrame
->PresContext()->GetPresShell();
1621 MOZ_ASSERT(presShell
);
1623 // Create a SelectionController
1624 mSelCon
= new TextInputSelectionController(presShell
, rootNode
);
1625 MOZ_ASSERT(!mTextListener
, "Should not overwrite the object");
1626 mTextListener
= new TextInputListener(mTextCtrlElement
);
1628 mTextListener
->SetFrame(mBoundFrame
);
1630 // Editor will override this as needed from InitializeSelection.
1631 mSelCon
->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN
);
1633 // Get the caret and make it a selection listener.
1634 // FYI: It's safe to use raw pointer for calling
1635 // Selection::AddSelectionListner() because it only appends the listener
1636 // to its internal array.
1637 Selection
* selection
= mSelCon
->GetSelection(SelectionType::eNormal
);
1639 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
1641 selection
->AddSelectionListener(caret
);
1643 mTextListener
->StartToListenToSelectionChange();
1646 // If an editor exists from before, prepare it for usage
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);
1658 // otherwise, inherit the content node's direction
1661 nsContentUtils::AddScriptRunner(
1662 new PrepareEditorEvent(*this, mTextCtrlElement
, currentValue
));
1668 struct MOZ_STACK_CLASS PreDestroyer
{
1669 void Init(TextEditor
* aTextEditor
) { mTextEditor
= aTextEditor
; }
1672 // In this case, we don't need to restore the unmasked range of password
1674 UniquePtr
<PasswordMaskData
> passwordMaskData
= mTextEditor
->PreDestroy();
1677 void Swap(RefPtr
<TextEditor
>& aTextEditor
) {
1678 return mTextEditor
.swap(aTextEditor
);
1682 RefPtr
<TextEditor
> mTextEditor
;
1685 nsresult
TextControlState::PrepareEditor(const nsAString
* aValue
) {
1687 // Cannot create an editor without a bound frame.
1688 // Don't return a failure code, because js callers can't handle that.
1692 if (mEditorInitialized
) {
1693 // Do not initialize the editor multiple times.
1697 AutoHideSelectionChanges
hideSelectionChanges(GetConstFrameSelection());
1699 if (mHandlingState
) {
1700 // Don't attempt to initialize recursively!
1701 if (mHandlingState
->IsHandling(TextControlAction::PrepareEditor
)) {
1702 return NS_ERROR_NOT_INITIALIZED
;
1704 // Reschedule creating editor later if we're setting value.
1705 if (mHandlingState
->IsHandling(TextControlAction::SetValue
)) {
1706 mHandlingState
->PrepareEditorLater();
1707 return NS_ERROR_NOT_INITIALIZED
;
1711 MOZ_ASSERT(mTextCtrlElement
);
1713 AutoTextControlHandlingState
preparingEditor(
1714 *this, TextControlAction::PrepareEditor
);
1716 // Note that we don't check mTextEditor here, because we might already have
1717 // one around, in which case we don't create a new one, and we'll just tie
1718 // the required machinery to it.
1720 nsPresContext
* presContext
= mBoundFrame
->PresContext();
1721 PresShell
* presShell
= presContext
->GetPresShell();
1723 // Setup the editor flags
1725 // Spell check is diabled at creation time. It is enabled once
1726 // the editor comes into focus.
1727 uint32_t editorFlags
= nsIEditor::eEditorSkipSpellCheck
;
1729 if (IsSingleLineTextControl()) {
1730 editorFlags
|= nsIEditor::eEditorSingleLineMask
;
1732 if (IsPasswordTextControl()) {
1733 editorFlags
|= nsIEditor::eEditorPasswordMask
;
1736 bool shouldInitializeEditor
= false;
1737 RefPtr
<TextEditor
> newTextEditor
; // the editor that we might create
1738 PreDestroyer preDestroyer
;
1740 shouldInitializeEditor
= true;
1743 newTextEditor
= new TextEditor();
1744 preDestroyer
.Init(newTextEditor
);
1746 // Make sure we clear out the non-breaking space before we initialize the
1748 nsresult rv
= mBoundFrame
->UpdateValueDisplay(true, true);
1749 if (NS_FAILED(rv
)) {
1750 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
1754 if (aValue
|| !mEditorInitialized
) {
1755 // Set the correct value in the root node
1757 mBoundFrame
->UpdateValueDisplay(true, !mEditorInitialized
, aValue
);
1758 if (NS_FAILED(rv
)) {
1759 NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed");
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
;
1777 defaultValue
= *aValue
;
1779 GetValue(defaultValue
, true, /* aForDisplay = */ true);
1782 if (!mEditorInitialized
) {
1783 // Now initialize the editor.
1785 // NOTE: Conversion of '\n' to <BR> happens inside the
1786 // editor's Init() call.
1788 // Get the DOM document
1789 nsCOMPtr
<Document
> doc
= presShell
->GetDocument();
1790 if (NS_WARN_IF(!doc
)) {
1791 return NS_ERROR_FAILURE
;
1794 // What follows is a bit of a hack. The editor uses the public DOM APIs
1795 // for its content manipulations, and it causes it to fail some security
1796 // checks deep inside when initializing. So we explictly make it clear that
1797 // we're native code.
1798 // Note that any script that's directly trying to access our value
1799 // has to be going through some scriptable object to do that and that
1800 // already does the relevant security checks.
1801 AutoNoJSAPI nojsapi
;
1803 RefPtr
<Element
> anonymousDivElement
= GetRootNode();
1804 if (NS_WARN_IF(!anonymousDivElement
) || NS_WARN_IF(!mSelCon
)) {
1805 return NS_ERROR_FAILURE
;
1807 OwningNonNull
<TextInputSelectionController
> selectionController(*mSelCon
);
1808 UniquePtr
<PasswordMaskData
> passwordMaskData
;
1809 if (editorFlags
& nsIEditor::eEditorPasswordMask
) {
1810 if (mPasswordMaskData
) {
1811 passwordMaskData
= std::move(mPasswordMaskData
);
1813 passwordMaskData
= MakeUnique
<PasswordMaskData
>();
1816 mPasswordMaskData
= nullptr;
1819 newTextEditor
->Init(*doc
, *anonymousDivElement
, selectionController
,
1820 editorFlags
, std::move(passwordMaskData
));
1821 if (NS_FAILED(rv
)) {
1822 NS_WARNING("TextEditor::Init() failed");
1827 // Initialize the controller for the editor
1829 nsresult rv
= NS_OK
;
1830 if (!SuppressEventHandlers(presContext
)) {
1831 nsCOMPtr
<nsIControllers
> controllers
;
1832 if (auto* inputElement
= HTMLInputElement::FromNode(mTextCtrlElement
)) {
1833 nsresult rv
= inputElement
->GetControllers(getter_AddRefs(controllers
));
1834 if (NS_WARN_IF(NS_FAILED(rv
))) {
1838 auto* textAreaElement
= HTMLTextAreaElement::FromNode(mTextCtrlElement
);
1839 if (!textAreaElement
) {
1840 return NS_ERROR_FAILURE
;
1844 textAreaElement
->GetControllers(getter_AddRefs(controllers
));
1845 if (NS_WARN_IF(NS_FAILED(rv
))) {
1851 // XXX Oddly, nsresult value is overwritten in the following loop, and
1852 // only the last result or `found` decides the value.
1853 uint32_t numControllers
;
1855 rv
= controllers
->GetControllerCount(&numControllers
);
1856 for (uint32_t i
= 0; i
< numControllers
; i
++) {
1857 nsCOMPtr
<nsIController
> controller
;
1858 rv
= controllers
->GetControllerAt(i
, getter_AddRefs(controller
));
1859 if (NS_SUCCEEDED(rv
) && controller
) {
1860 nsCOMPtr
<nsIControllerContext
> editController
=
1861 do_QueryInterface(controller
);
1862 if (editController
) {
1863 editController
->SetCommandContext(
1864 static_cast<nsIEditor
*>(newTextEditor
));
1870 rv
= NS_ERROR_FAILURE
;
1875 // Initialize the plaintext editor
1876 if (shouldInitializeEditor
) {
1877 const int32_t wrapCols
= GetWrapCols();
1878 MOZ_ASSERT(wrapCols
>= 0);
1879 newTextEditor
->SetWrapColumn(wrapCols
);
1882 // Set max text field length
1883 newTextEditor
->SetMaxTextLength(mTextCtrlElement
->UsedMaxLength());
1885 editorFlags
= newTextEditor
->Flags();
1887 // Check if the readonly/disabled attributes are set.
1888 if (mTextCtrlElement
->IsDisabledOrReadOnly()) {
1889 editorFlags
|= nsIEditor::eEditorReadonlyMask
;
1892 SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1894 if (shouldInitializeEditor
) {
1895 // Hold on to the newly created editor
1896 preDestroyer
.Swap(mTextEditor
);
1899 // If we have a default value, insert it under the div we created
1900 // above, but be sure to use the editor so that '*' characters get
1901 // displayed for password fields, etc. SetValue() will call the
1904 if (!defaultValue
.IsEmpty()) {
1905 // XXX rv may store error code which indicates there is no controller.
1906 // However, we overwrite it only in this case.
1907 rv
= SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1908 if (NS_WARN_IF(NS_FAILED(rv
))) {
1912 // Now call SetValue() which will make the necessary editor calls to set
1913 // the default value. Make sure to turn off undo before setting the default
1914 // value, and turn it back on afterwards. This will make sure we can't undo
1915 // past the default value.
1916 // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
1919 if (NS_WARN_IF(!SetValue(defaultValue
, ValueSetterOption::ByInternalAPI
))) {
1920 return NS_ERROR_OUT_OF_MEMORY
;
1923 // Now restore the original editor flags.
1924 rv
= SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1925 if (NS_WARN_IF(NS_FAILED(rv
))) {
1929 // When the default value is empty, we don't call SetValue(). That means that
1930 // we have not notified IMEContentObserver of the empty value when the
1931 // <textarea> is not dirty (i.e., the default value is mirrored into the
1932 // anonymous subtree asynchronously) and the value was changed during a
1933 // reframe (i.e., while IMEContentObserver was not observing the mutation of
1934 // the anonymous subtree). Therefore, we notify IMEContentObserver here in
1936 else if (mTextCtrlElement
&& mTextCtrlElement
->IsTextArea() &&
1937 !mTextCtrlElement
->ValueChanged()) {
1938 MOZ_ASSERT(defaultValue
.IsEmpty());
1939 IMEContentObserver
* observer
= GetIMEContentObserver();
1940 if (observer
&& observer
->WasInitializedWith(*newTextEditor
)) {
1941 observer
->OnTextControlValueChangedWhileNotObservable(defaultValue
);
1945 DebugOnly
<bool> enabledUndoRedo
=
1946 newTextEditor
->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP
);
1947 NS_WARNING_ASSERTION(enabledUndoRedo
,
1948 "Failed to enable undo/redo transaction");
1950 if (!mEditorInitialized
) {
1951 newTextEditor
->PostCreate();
1953 mEditorInitialized
= true;
1956 if (mTextListener
) {
1957 newTextEditor
->SetTextInputListener(mTextListener
);
1960 // Restore our selection after being bound to a new frame
1961 if (mSelectionCached
) {
1962 if (mRestoringSelection
) { // paranoia
1963 mRestoringSelection
->Revoke();
1965 mRestoringSelection
= new RestoreSelectionState(this, mBoundFrame
);
1966 if (mRestoringSelection
) {
1967 nsContentUtils::AddScriptRunner(mRestoringSelection
);
1971 // The selection cache is no longer going to be valid.
1973 // XXXbz Shouldn't we do this at the point when we're actually about to
1974 // restore the properties or something? As things stand, if UnbindFromFrame
1975 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1976 // selection info, because we will think we don't have it cached and try to
1977 // read it from the selection controller, which will not have it yet.
1978 mSelectionCached
= false;
1980 return preparingEditor
.IsTextControlStateDestroyed()
1981 ? NS_ERROR_NOT_INITIALIZED
1985 void TextControlState::FinishedRestoringSelection() {
1986 mRestoringSelection
= nullptr;
1989 void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
1991 UnbindFromFrame(mBoundFrame
);
1995 void TextControlState::SetSelectionProperties(
1996 TextControlState::SelectionProperties
& aProps
) {
1998 mBoundFrame
->SetSelectionRange(aProps
.GetStart(), aProps
.GetEnd(),
1999 aProps
.GetDirection());
2000 // The instance may have already been deleted here.
2002 mSelectionProperties
= aProps
;
2006 void TextControlState::GetSelectionRange(uint32_t* aSelectionStart
,
2007 uint32_t* aSelectionEnd
,
2009 MOZ_ASSERT(aSelectionStart
);
2010 MOZ_ASSERT(aSelectionEnd
);
2011 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2012 "How can we not have a cached selection if we have no selection "
2015 // Note that we may have both IsSelectionCached() _and_
2016 // GetSelectionController() if we haven't initialized our editor yet.
2017 if (IsSelectionCached()) {
2018 const SelectionProperties
& props
= GetSelectionProperties();
2019 *aSelectionStart
= props
.GetStart();
2020 *aSelectionEnd
= props
.GetEnd();
2024 Selection
* sel
= mSelCon
->GetSelection(SelectionType::eNormal
);
2025 if (NS_WARN_IF(!sel
)) {
2026 aRv
.Throw(NS_ERROR_FAILURE
);
2030 Element
* root
= GetRootNode();
2031 if (NS_WARN_IF(!root
)) {
2032 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2035 nsContentUtils::GetSelectionInTextControl(sel
, root
, *aSelectionStart
,
2039 SelectionDirection
TextControlState::GetSelectionDirection(ErrorResult
& aRv
) {
2040 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
2041 "How can we not have a cached selection if we have no selection "
2044 // Note that we may have both IsSelectionCached() _and_
2045 // GetSelectionController() if we haven't initialized our editor yet.
2046 if (IsSelectionCached()) {
2047 return GetSelectionProperties().GetDirection();
2050 Selection
* sel
= mSelCon
->GetSelection(SelectionType::eNormal
);
2051 if (NS_WARN_IF(!sel
)) {
2052 aRv
.Throw(NS_ERROR_FAILURE
);
2053 return SelectionDirection::Forward
;
2056 nsDirection direction
= sel
->GetDirection();
2057 if (direction
== eDirNext
) {
2058 return SelectionDirection::Forward
;
2061 MOZ_ASSERT(direction
== eDirPrevious
);
2062 return SelectionDirection::Backward
;
2065 void TextControlState::SetSelectionRange(uint32_t aStart
, uint32_t aEnd
,
2066 SelectionDirection aDirection
,
2068 ScrollAfterSelection aScroll
) {
2069 MOZ_ASSERT(IsSelectionCached() || mBoundFrame
,
2070 "How can we have a non-cached selection but no frame?");
2072 AutoTextControlHandlingState
handlingSetSelectionRange(
2073 *this, TextControlAction::SetSelectionRange
);
2075 if (aStart
> aEnd
) {
2079 if (!IsSelectionCached()) {
2080 MOZ_ASSERT(mBoundFrame
, "Our frame should still be valid");
2081 aRv
= mBoundFrame
->SetSelectionRange(aStart
, aEnd
, aDirection
);
2083 handlingSetSelectionRange
.IsTextControlStateDestroyed()) {
2086 if (aScroll
== ScrollAfterSelection::Yes
&& mBoundFrame
) {
2087 // mBoundFrame could be gone if selection listeners flushed layout for
2089 mBoundFrame
->ScrollSelectionIntoViewAsync();
2094 SelectionProperties
& props
= GetSelectionProperties();
2095 if (!props
.HasMaxLength()) {
2096 // A clone without a dirty value flag may not have a max length yet
2098 GetValue(value
, false, /* aForDisplay = */ true);
2099 props
.SetMaxLength(value
.Length());
2102 bool changed
= props
.SetStart(aStart
);
2103 changed
|= props
.SetEnd(aEnd
);
2104 changed
|= props
.SetDirection(aDirection
);
2110 // It sure would be nice if we had an existing Element* or so to work with.
2111 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
2112 new AsyncEventDispatcher(mTextCtrlElement
, eFormSelect
, CanBubble::eYes
);
2113 asyncDispatcher
->PostDOMEvent();
2115 // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
2116 // XXX(krosylight): Shouldn't it fire before select event?
2117 // Currently Gecko and Blink both fire selectionchange after select.
2118 if (IsSelectionCached() &&
2119 StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled()) {
2120 asyncDispatcher
= new AsyncEventDispatcher(
2121 mTextCtrlElement
, eSelectionChange
, CanBubble::eYes
);
2122 asyncDispatcher
->PostDOMEvent();
2126 void TextControlState::SetSelectionStart(const Nullable
<uint32_t>& aStart
,
2129 if (!aStart
.IsNull()) {
2130 start
= aStart
.Value();
2133 uint32_t ignored
, end
;
2134 GetSelectionRange(&ignored
, &end
, aRv
);
2139 SelectionDirection dir
= GetSelectionDirection(aRv
);
2148 SetSelectionRange(start
, end
, dir
, aRv
);
2149 // The instance may have already been deleted here.
2152 void TextControlState::SetSelectionEnd(const Nullable
<uint32_t>& aEnd
,
2155 if (!aEnd
.IsNull()) {
2159 uint32_t start
, ignored
;
2160 GetSelectionRange(&start
, &ignored
, aRv
);
2165 SelectionDirection dir
= GetSelectionDirection(aRv
);
2170 SetSelectionRange(start
, end
, dir
, aRv
);
2171 // The instance may have already been deleted here.
2174 static void DirectionToName(SelectionDirection dir
, nsAString
& aDirection
) {
2176 case SelectionDirection::None
:
2177 // TODO(mbrodesser): this should be supported, see
2178 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
2179 NS_WARNING("We don't actually support this... how did we get it?");
2180 return aDirection
.AssignLiteral("none");
2181 case SelectionDirection::Forward
:
2182 return aDirection
.AssignLiteral("forward");
2183 case SelectionDirection::Backward
:
2184 return aDirection
.AssignLiteral("backward");
2186 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
2189 void TextControlState::GetSelectionDirectionString(nsAString
& aDirection
,
2191 SelectionDirection dir
= GetSelectionDirection(aRv
);
2195 DirectionToName(dir
, aDirection
);
2198 static SelectionDirection
DirectionStringToSelectionDirection(
2199 const nsAString
& aDirection
) {
2200 if (aDirection
.EqualsLiteral("backward")) {
2201 return SelectionDirection::Backward
;
2203 // We don't support directionless selections, see bug 1541454.
2204 return SelectionDirection::Forward
;
2207 void TextControlState::SetSelectionDirection(const nsAString
& aDirection
,
2209 SelectionDirection dir
= DirectionStringToSelectionDirection(aDirection
);
2211 uint32_t start
, end
;
2212 GetSelectionRange(&start
, &end
, aRv
);
2217 SetSelectionRange(start
, end
, dir
, aRv
);
2218 // The instance may have already been deleted here.
2221 static SelectionDirection
DirectionStringToSelectionDirection(
2222 const Optional
<nsAString
>& aDirection
) {
2223 if (!aDirection
.WasPassed()) {
2224 // We don't support directionless selections.
2225 return SelectionDirection::Forward
;
2228 return DirectionStringToSelectionDirection(aDirection
.Value());
2231 void TextControlState::SetSelectionRange(uint32_t aSelectionStart
,
2232 uint32_t aSelectionEnd
,
2233 const Optional
<nsAString
>& aDirection
,
2235 ScrollAfterSelection aScroll
) {
2236 SelectionDirection dir
= DirectionStringToSelectionDirection(aDirection
);
2238 SetSelectionRange(aSelectionStart
, aSelectionEnd
, dir
, aRv
, aScroll
);
2239 // The instance may have already been deleted here.
2242 void TextControlState::SetRangeText(const nsAString
& aReplacement
,
2244 uint32_t start
, end
;
2245 GetSelectionRange(&start
, &end
, aRv
);
2250 SetRangeText(aReplacement
, start
, end
, SelectionMode::Preserve
, aRv
,
2251 Some(start
), Some(end
));
2252 // The instance may have already been deleted here.
2255 void TextControlState::SetRangeText(const nsAString
& aReplacement
,
2256 uint32_t aStart
, uint32_t aEnd
,
2257 SelectionMode aSelectMode
, ErrorResult
& aRv
,
2258 const Maybe
<uint32_t>& aSelectionStart
,
2259 const Maybe
<uint32_t>& aSelectionEnd
) {
2260 if (aStart
> aEnd
) {
2261 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
2265 AutoTextControlHandlingState
handlingSetRangeText(
2266 *this, TextControlAction::SetRangeText
);
2269 mTextCtrlElement
->GetValueFromSetRangeText(value
);
2270 uint32_t inputValueLength
= value
.Length();
2272 if (aStart
> inputValueLength
) {
2273 aStart
= inputValueLength
;
2276 if (aEnd
> inputValueLength
) {
2277 aEnd
= inputValueLength
;
2280 uint32_t selectionStart
, selectionEnd
;
2281 if (!aSelectionStart
) {
2282 MOZ_ASSERT(!aSelectionEnd
);
2283 GetSelectionRange(&selectionStart
, &selectionEnd
, aRv
);
2288 MOZ_ASSERT(aSelectionEnd
);
2289 selectionStart
= *aSelectionStart
;
2290 selectionEnd
= *aSelectionEnd
;
2293 // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
2294 Selection
* selection
=
2295 mSelCon
? mSelCon
->GetSelection(SelectionType::eNormal
) : nullptr;
2296 SelectionBatcher
selectionBatcher(
2297 selection
, __FUNCTION__
,
2298 nsISelectionListener::JS_REASON
); // no-op if nullptr
2300 MOZ_ASSERT(aStart
<= aEnd
);
2301 value
.Replace(aStart
, aEnd
- aStart
, aReplacement
);
2303 MOZ_KnownLive(mTextCtrlElement
)->SetValueFromSetRangeText(value
);
2304 if (NS_FAILED(rv
)) {
2309 uint32_t newEnd
= aStart
+ aReplacement
.Length();
2310 int32_t delta
= aReplacement
.Length() - (aEnd
- aStart
);
2312 switch (aSelectMode
) {
2313 case SelectionMode::Select
:
2314 selectionStart
= aStart
;
2315 selectionEnd
= newEnd
;
2317 case SelectionMode::Start
:
2318 selectionStart
= selectionEnd
= aStart
;
2320 case SelectionMode::End
:
2321 selectionStart
= selectionEnd
= newEnd
;
2323 case SelectionMode::Preserve
:
2324 if (selectionStart
> aEnd
) {
2325 selectionStart
+= delta
;
2326 } else if (selectionStart
> aStart
) {
2327 selectionStart
= aStart
;
2330 if (selectionEnd
> aEnd
) {
2331 selectionEnd
+= delta
;
2332 } else if (selectionEnd
> aStart
) {
2333 selectionEnd
= newEnd
;
2337 MOZ_ASSERT_UNREACHABLE("Unknown mode!");
2340 SetSelectionRange(selectionStart
, selectionEnd
, Optional
<nsAString
>(), aRv
);
2341 if (IsSelectionCached()) {
2342 // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
2343 GetSelectionProperties().SetMaxLength(value
.Length());
2347 void TextControlState::DestroyEditor() {
2348 // notify the editor that we are going away
2349 if (mEditorInitialized
) {
2350 // FYI: TextEditor checks whether it's destroyed or not immediately after
2351 // changes the DOM tree or selection so that it's safe to call
2352 // PreDestroy() here even while we're handling actions with
2354 MOZ_ASSERT(!mPasswordMaskData
);
2355 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
2356 mPasswordMaskData
= textEditor
->PreDestroy();
2357 MOZ_ASSERT_IF(mPasswordMaskData
, !mPasswordMaskData
->mTimer
);
2358 mEditorInitialized
= false;
2362 void TextControlState::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
2363 if (NS_WARN_IF(!mBoundFrame
)) {
2367 // If it was, however, it should be unbounded from the same frame.
2368 MOZ_ASSERT(aFrame
== mBoundFrame
, "Unbinding from the wrong frame");
2369 if (aFrame
&& aFrame
!= mBoundFrame
) {
2373 AutoTextControlHandlingState
handlingUnbindFromFrame(
2374 *this, TextControlAction::UnbindFromFrame
);
2377 mSelCon
->SelectionWillLoseFocus();
2380 // We need to start storing the value outside of the editor if we're not
2381 // going to use it anymore, so retrieve it for now.
2383 GetValue(value
, true, /* aForDisplay = */ false);
2385 if (mRestoringSelection
) {
2386 mRestoringSelection
->Revoke();
2387 mRestoringSelection
= nullptr;
2390 // Save our selection state if needed.
2391 // Note that GetSelectionRange will attempt to work with our selection
2392 // controller, so we should make sure we do it before we start doing things
2393 // like destroying our editor (if we have one), tearing down the selection
2394 // controller, and so forth.
2395 if (!IsSelectionCached()) {
2396 // Go ahead and cache it now.
2397 uint32_t start
= 0, end
= 0;
2398 GetSelectionRange(&start
, &end
, IgnoreErrors());
2400 nsITextControlFrame::SelectionDirection direction
=
2401 GetSelectionDirection(IgnoreErrors());
2403 SelectionProperties
& props
= GetSelectionProperties();
2404 props
.SetMaxLength(value
.Length());
2405 props
.SetStart(start
);
2407 props
.SetDirection(direction
);
2408 mSelectionCached
= true;
2411 // Destroy our editor
2414 // Clean up the controller
2415 if (!SuppressEventHandlers(mBoundFrame
->PresContext())) {
2416 nsCOMPtr
<nsIControllers
> controllers
;
2417 if (auto* inputElement
= HTMLInputElement::FromNode(mTextCtrlElement
)) {
2418 inputElement
->GetControllers(getter_AddRefs(controllers
));
2420 auto* textAreaElement
= HTMLTextAreaElement::FromNode(mTextCtrlElement
);
2421 if (textAreaElement
) {
2422 textAreaElement
->GetControllers(getter_AddRefs(controllers
));
2427 uint32_t numControllers
;
2428 nsresult rv
= controllers
->GetControllerCount(&numControllers
);
2429 NS_ASSERTION((NS_SUCCEEDED(rv
)),
2430 "bad result in gfx text control destructor");
2431 for (uint32_t i
= 0; i
< numControllers
; i
++) {
2432 nsCOMPtr
<nsIController
> controller
;
2433 rv
= controllers
->GetControllerAt(i
, getter_AddRefs(controller
));
2434 if (NS_SUCCEEDED(rv
) && controller
) {
2435 nsCOMPtr
<nsIControllerContext
> editController
=
2436 do_QueryInterface(controller
);
2437 if (editController
) {
2438 editController
->SetCommandContext(nullptr);
2446 if (mTextListener
) {
2447 mTextListener
->EndListeningToSelectionChange();
2450 mSelCon
->SetScrollableFrame(nullptr);
2454 if (mTextListener
) {
2455 mTextListener
->SetFrame(nullptr);
2457 EventListenerManager
* manager
=
2458 mTextCtrlElement
->GetExistingListenerManager();
2460 manager
->RemoveEventListenerByType(mTextListener
, u
"keydown"_ns
,
2461 TrustedEventsAtSystemGroupBubble());
2462 manager
->RemoveEventListenerByType(mTextListener
, u
"keypress"_ns
,
2463 TrustedEventsAtSystemGroupBubble());
2464 manager
->RemoveEventListenerByType(mTextListener
, u
"keyup"_ns
,
2465 TrustedEventsAtSystemGroupBubble());
2468 mTextListener
= nullptr;
2471 mBoundFrame
= nullptr;
2473 // Now that we don't have a frame any more, store the value in the text
2474 // buffer. The only case where we don't do this is if a value transfer is in
2476 if (!mValueTransferInProgress
) {
2477 DebugOnly
<bool> ok
= SetValue(value
, ValueSetterOption::ByInternalAPI
);
2478 // TODO Find something better to do if this fails...
2479 NS_WARNING_ASSERTION(ok
, "SetValue() couldn't allocate memory");
2483 void TextControlState::GetValue(nsAString
& aValue
, bool aIgnoreWrap
,
2484 bool aForDisplay
) const {
2485 // While SetValue() is being called and requesting to commit composition to
2486 // IME, GetValue() may be called for appending text or something. Then, we
2487 // need to return the latest aValue of SetValue() since the value hasn't
2488 // been set to the editor yet.
2489 // XXX After implementing "beforeinput" event, this becomes wrong. The
2490 // value should be modified immediately after "beforeinput" event for
2491 // "insertReplacementText".
2492 if (mHandlingState
&&
2493 mHandlingState
->IsHandling(TextControlAction::CommitComposition
)) {
2494 aValue
= mHandlingState
->GetSettingValue();
2495 MOZ_ASSERT(aValue
.FindChar(u
'\r') == -1);
2499 if (mTextEditor
&& mBoundFrame
&&
2500 (mEditorInitialized
|| !IsSingleLineTextControl())) {
2501 if (aIgnoreWrap
&& !mBoundFrame
->CachedValue().IsVoid()) {
2502 aValue
= mBoundFrame
->CachedValue();
2503 MOZ_ASSERT(aValue
.FindChar(u
'\r') == -1);
2507 aValue
.Truncate(); // initialize out param
2509 uint32_t flags
= (nsIDocumentEncoder::OutputLFLineBreak
|
2510 nsIDocumentEncoder::OutputPreformatted
|
2511 nsIDocumentEncoder::OutputPersistNBSP
|
2512 nsIDocumentEncoder::OutputBodyOnly
);
2514 TextControlElement::nsHTMLTextWrap wrapProp
;
2515 if (mTextCtrlElement
&&
2516 TextControlElement::GetWrapPropertyEnum(mTextCtrlElement
, wrapProp
) &&
2517 wrapProp
== TextControlElement::eHTMLTextWrap_Hard
) {
2518 flags
|= nsIDocumentEncoder::OutputWrap
;
2522 // What follows is a bit of a hack. The problem is that we could be in
2523 // this method because we're being destroyed for whatever reason while
2524 // script is executing. If that happens, editor will run with the
2525 // privileges of the executing script, which means it may not be able to
2526 // access its own DOM nodes! Let's try to deal with that by pushing a null
2527 // JSContext on the JSContext stack to make it clear that we're native
2528 // code. Note that any script that's directly trying to access our value
2529 // has to be going through some scriptable object to do that and that
2530 // already does the relevant security checks.
2531 // XXXbz if we could just get the textContent of our anonymous content (eg
2532 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2534 { /* Scope for AutoNoJSAPI. */
2535 AutoNoJSAPI nojsapi
;
2537 DebugOnly
<nsresult
> rv
= mTextEditor
->ComputeTextValue(flags
, aValue
);
2538 MOZ_ASSERT(aValue
.FindChar(u
'\r') == -1);
2539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to get value");
2541 // Only when the result doesn't include line breaks caused by hard-wrap,
2542 // mCacheValue should cache the value.
2543 if (!(flags
& nsIDocumentEncoder::OutputWrap
)) {
2544 mBoundFrame
->CacheValue(aValue
);
2546 mBoundFrame
->ClearCachedValue();
2548 } else if (!mTextCtrlElement
->ValueChanged() || mValue
.IsVoid()) {
2549 // Use nsString to avoid copying string buffer at setting aValue.
2551 mTextCtrlElement
->GetDefaultValueFromContent(value
, aForDisplay
);
2552 // TODO: We should make default value not include \r.
2553 nsContentUtils::PlatformToDOMLineBreaks(value
);
2554 aValue
= std::move(value
);
2557 MOZ_ASSERT(aValue
.FindChar(u
'\r') == -1);
2561 bool TextControlState::ValueEquals(const nsAString
& aValue
) const {
2563 GetValue(value
, true, /* aForDisplay = */ true);
2564 return aValue
.Equals(value
);
2568 // @param aOptions TextControlState::ValueSetterOptions
2569 bool AreFlagsNotDemandingContradictingMovements(
2570 const ValueSetterOptions
& aOptions
) {
2571 return !aOptions
.contains(
2572 {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward
,
2573 ValueSetterOption::MoveCursorToEndIfValueChanged
});
2577 bool TextControlState::SetValue(const nsAString
& aValue
,
2578 const nsAString
* aOldValue
,
2579 const ValueSetterOptions
& aOptions
) {
2580 if (mHandlingState
&&
2581 mHandlingState
->IsHandling(TextControlAction::CommitComposition
)) {
2582 // GetValue doesn't return current text frame's content during committing.
2583 // So we cannot trust this old value
2584 aOldValue
= nullptr;
2587 if (mPasswordMaskData
) {
2588 if (mHandlingState
&&
2589 mHandlingState
->Is(TextControlAction::UnbindFromFrame
)) {
2590 // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
2592 // Otherwise, we should mask the new password, even if it's same value
2593 // since the same value may be one for different web app's.
2594 mPasswordMaskData
->Reset();
2598 const bool wasHandlingSetValue
=
2599 mHandlingState
&& mHandlingState
->IsHandling(TextControlAction::SetValue
);
2602 AutoTextControlHandlingState
handlingSetValue(
2603 *this, TextControlAction::SetValue
, aValue
, aOldValue
, aOptions
, error
);
2604 if (error
.Failed()) {
2605 MOZ_ASSERT(error
.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY
));
2606 error
.SuppressException();
2610 const auto changeKind
= [&] {
2611 if (aOptions
.contains(ValueSetterOption::ByInternalAPI
)) {
2612 return ValueChangeKind::Internal
;
2614 if (aOptions
.contains(ValueSetterOption::BySetUserInputAPI
)) {
2615 return ValueChangeKind::UserInteraction
;
2617 return ValueChangeKind::Script
;
2620 if (changeKind
== ValueChangeKind::Script
) {
2621 // This value change will not be interactive. If we're an input that was
2622 // interactively edited, save the last interactive value now before it goes
2624 if (auto* input
= HTMLInputElement::FromNode(mTextCtrlElement
)) {
2625 if (input
->LastValueChangeWasInteractive()) {
2626 GetValue(mLastInteractiveValue
, /* aIgnoreWrap = */ true,
2627 /* aForDisplay = */ true);
2632 // Note that if this may be called during reframe of the editor. In such
2633 // case, we shouldn't commit composition. Therefore, when this is called
2634 // for internal processing, we shouldn't commit the composition.
2635 // TODO: In strictly speaking, we should move committing composition into
2636 // editor because if "beforeinput" for this setting value is canceled,
2637 // we shouldn't commit composition. However, in Firefox, we never
2638 // call this via `setUserInput` during composition. Therefore, the
2639 // bug must not be reproducible actually.
2640 if (aOptions
.contains(ValueSetterOption::BySetUserInputAPI
) ||
2641 aOptions
.contains(ValueSetterOption::ByContentAPI
)) {
2642 if (EditorHasComposition()) {
2643 // When this is called recursively, there shouldn't be composition.
2644 if (handlingSetValue
.IsHandling(TextControlAction::CommitComposition
)) {
2645 // Don't request to commit composition again. But if it occurs,
2646 // we should skip to set the new value to the editor here. It should
2647 // be set later with the newest value.
2650 if (NS_WARN_IF(!mBoundFrame
)) {
2651 // We're not sure if this case is possible.
2653 // If setting value won't change current value, we shouldn't commit
2654 // composition for compatibility with the other browsers.
2655 MOZ_ASSERT(!aOldValue
|| ValueEquals(*aOldValue
));
2656 bool isSameAsCurrentValue
=
2657 aOldValue
? aOldValue
->Equals(handlingSetValue
.GetSettingValue())
2658 : ValueEquals(handlingSetValue
.GetSettingValue());
2659 if (isSameAsCurrentValue
) {
2660 // Note that in this case, we shouldn't fire any events with setting
2661 // value because event handlers may try to set value recursively but
2662 // we cannot commit composition at that time due to unsafe to run
2663 // script (see below).
2667 // If there is composition, need to commit composition first because
2668 // other browsers do that.
2669 // NOTE: We don't need to block nested calls of this because input nor
2670 // other events won't be fired by setting values and script blocker
2671 // is used during setting the value to the editor. IE also allows
2672 // to set the editor value on the input event which is caused by
2673 // forcibly committing composition.
2674 AutoTextControlHandlingState
handlingCommitComposition(
2675 *this, TextControlAction::CommitComposition
);
2676 if (nsContentUtils::IsSafeToRunScript()) {
2677 // WARNING: During this call, compositionupdate, compositionend, input
2678 // events will be fired. Therefore, everything can occur. E.g., the
2679 // document may be unloaded.
2680 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
2681 nsresult rv
= textEditor
->CommitComposition();
2682 if (handlingCommitComposition
.IsTextControlStateDestroyed()) {
2685 if (NS_FAILED(rv
)) {
2686 NS_WARNING("TextControlState failed to commit composition");
2689 // Note that if a composition event listener sets editor value again,
2690 // we should use the new value here. The new value is stored in
2691 // handlingSetValue right now.
2694 "SetValue() is called when there is composition but "
2695 "it's not safe to request to commit the composition");
2700 if (mTextEditor
&& mBoundFrame
) {
2701 if (!SetValueWithTextEditor(handlingSetValue
)) {
2704 } else if (!SetValueWithoutTextEditor(handlingSetValue
)) {
2708 // If we were handling SetValue() before, don't update the DOM state twice,
2709 // just let the outer call do so.
2710 if (!wasHandlingSetValue
) {
2711 handlingSetValue
.GetTextControlElement()->OnValueChanged(
2712 changeKind
, handlingSetValue
.GetSettingValue());
2717 bool TextControlState::SetValueWithTextEditor(
2718 AutoTextControlHandlingState
& aHandlingSetValue
) {
2719 MOZ_ASSERT(aHandlingSetValue
.Is(TextControlAction::SetValue
));
2720 MOZ_ASSERT(mTextEditor
);
2721 MOZ_ASSERT(mBoundFrame
);
2722 NS_WARNING_ASSERTION(!EditorHasComposition(),
2723 "Failed to commit composition before setting value. "
2724 "Investigate the cause!");
2727 if (IsSingleLineTextControl()) {
2728 NS_ASSERTION(mEditorInitialized
|| aHandlingSetValue
.IsHandling(
2729 TextControlAction::PrepareEditor
),
2730 "We should never try to use the editor if we're not "
2731 "initialized unless we're being initialized");
2735 MOZ_ASSERT(!aHandlingSetValue
.GetOldValue() ||
2736 ValueEquals(*aHandlingSetValue
.GetOldValue()));
2737 const bool isSameAsCurrentValue
=
2738 aHandlingSetValue
.GetOldValue()
2739 ? aHandlingSetValue
.GetOldValue()->Equals(
2740 aHandlingSetValue
.GetSettingValue())
2741 : ValueEquals(aHandlingSetValue
.GetSettingValue());
2743 // this is necessary to avoid infinite recursion
2744 if (isSameAsCurrentValue
) {
2748 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
2750 nsCOMPtr
<Document
> document
= textEditor
->GetDocument();
2751 if (NS_WARN_IF(!document
)) {
2755 // Time to mess with our security context... See comments in GetValue()
2756 // for why this is needed. Note that we have to do this up here, because
2757 // otherwise SelectAll() will fail.
2758 AutoNoJSAPI nojsapi
;
2760 // FYI: It's safe to use raw pointer for selection here because
2761 // SelectionBatcher will grab it with RefPtr.
2762 Selection
* selection
= mSelCon
->GetSelection(SelectionType::eNormal
);
2763 SelectionBatcher
selectionBatcher(selection
, __FUNCTION__
);
2765 // get the flags, remove readonly, disabled and max-length,
2766 // set the value, restore flags
2767 AutoRestoreEditorState
restoreState(textEditor
);
2769 aHandlingSetValue
.WillSetValueWithTextEditor();
2771 if (aHandlingSetValue
.ValueSetterOptionsRef().contains(
2772 ValueSetterOption::BySetUserInputAPI
)) {
2773 // If the caller inserts text as part of user input, for example,
2774 // autocomplete, we need to replace the text as "insert string"
2775 // because undo should cancel only this operation (i.e., previous
2776 // transactions typed by user shouldn't be merged with this).
2777 // In this case, we need to dispatch "input" event because
2778 // web apps may need to know the user's operation.
2779 // In this case, we need to dispatch "beforeinput" events since
2780 // we're emulating the user's input. Passing nullptr as
2781 // nsIPrincipal means that that may be user's input. So, let's
2783 nsresult rv
= textEditor
->ReplaceTextAsAction(
2784 aHandlingSetValue
.GetSettingValue(), nullptr,
2785 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2786 ? TextEditor::AllowBeforeInputEventCancelable::Yes
2787 : TextEditor::AllowBeforeInputEventCancelable::No
,
2789 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2790 "EditorBase::ReplaceTextAsAction() failed");
2791 return rv
!= NS_ERROR_OUT_OF_MEMORY
;
2794 // Don't dispatch "beforeinput" event nor "input" event for setting value
2796 AutoInputEventSuppresser
suppressInputEventDispatching(textEditor
);
2798 // On <input> or <textarea>, we shouldn't preserve existing undo
2799 // transactions because other browsers do not preserve them too
2800 // and not preserving transactions makes setting value faster.
2802 // (Except if chrome opts into this behavior).
2803 Maybe
<AutoDisableUndo
> disableUndo
;
2804 if (!aHandlingSetValue
.ValueSetterOptionsRef().contains(
2805 ValueSetterOption::PreserveUndoHistory
)) {
2806 disableUndo
.emplace(textEditor
);
2810 // Since we don't use undo transaction, we don't need to store
2811 // selection state. SetText will set selection to tail.
2812 IgnoredErrorResult ignoredError
;
2813 MOZ_KnownLive(selection
)->RemoveAllRanges(ignoredError
);
2814 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
2815 "Selection::RemoveAllRanges() failed, but ignored");
2818 // In this case, we makes the editor stop dispatching "input"
2819 // event so that passing nullptr as nsIPrincipal is safe for now.
2820 nsresult rv
= textEditor
->SetTextAsAction(
2821 aHandlingSetValue
.GetSettingValue(),
2822 aHandlingSetValue
.ValueSetterOptionsRef().contains(
2823 ValueSetterOption::BySetUserInputAPI
) &&
2824 !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2825 ? TextEditor::AllowBeforeInputEventCancelable::No
2826 : TextEditor::AllowBeforeInputEventCancelable::Yes
,
2828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2829 "TextEditor::SetTextAsAction() failed");
2831 // Call the listener's OnEditActionHandled() callback manually if
2832 // OnEditActionHandled() hasn't been called yet since TextEditor don't use
2833 // the transaction manager in this path and it could be that the editor
2834 // would bypass calling the listener for that reason.
2835 if (!aHandlingSetValue
.HasEditActionHandled()) {
2836 nsresult rvOnEditActionHandled
=
2837 MOZ_KnownLive(aHandlingSetValue
.GetTextInputListener())
2838 ->OnEditActionHandled(*textEditor
);
2839 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled
),
2840 "TextInputListener::OnEditActionHandled() failed");
2841 if (rv
!= NS_ERROR_OUT_OF_MEMORY
) {
2842 rv
= rvOnEditActionHandled
;
2846 // When the <textarea> is not dirty, the default value is mirrored into the
2847 // anonymous subtree asynchronously. This may occur during a reframe.
2848 // Therefore, if IMEContentObserver was initialized with our editor but our
2849 // editor is being initialized, it has not been observing the new anonymous
2850 // subtree. In this case, we need to notify IMEContentObserver of the default
2852 if (mTextCtrlElement
&& mTextCtrlElement
->IsTextArea() &&
2853 !mTextCtrlElement
->ValueChanged() && textEditor
->IsBeingInitialized() &&
2854 !textEditor
->Destroyed()) {
2855 IMEContentObserver
* observer
= GetIMEContentObserver();
2856 if (observer
&& observer
->WasInitializedWith(*textEditor
)) {
2857 nsAutoString currentValue
;
2858 textEditor
->ComputeTextValue(0, currentValue
);
2859 observer
->OnTextControlValueChangedWhileNotObservable(currentValue
);
2863 return rv
!= NS_ERROR_OUT_OF_MEMORY
;
2866 bool TextControlState::SetValueWithoutTextEditor(
2867 AutoTextControlHandlingState
& aHandlingSetValue
) {
2868 MOZ_ASSERT(aHandlingSetValue
.Is(TextControlAction::SetValue
));
2869 MOZ_ASSERT(!mTextEditor
|| !mBoundFrame
);
2870 NS_WARNING_ASSERTION(!EditorHasComposition(),
2871 "Failed to commit composition before setting value. "
2872 "Investigate the cause!");
2874 if (mValue
.IsVoid()) {
2875 mValue
.SetIsVoid(false);
2878 // We can't just early-return here, because OnValueChanged below still need to
2880 if (!mValue
.Equals(aHandlingSetValue
.GetSettingValue()) ||
2881 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2882 bool handleSettingValue
= true;
2883 // If `SetValue()` call is nested, `GetSettingValue()` result will be
2884 // modified. So, we need to store input event data value before
2885 // dispatching beforeinput event.
2886 nsString
inputEventData(aHandlingSetValue
.GetSettingValue());
2887 if (aHandlingSetValue
.ValueSetterOptionsRef().contains(
2888 ValueSetterOption::BySetUserInputAPI
) &&
2889 !aHandlingSetValue
.HasBeforeInputEventDispatched()) {
2890 // This probably occurs when session restorer sets the old value with
2891 // `setUserInput`. If so, we need to dispatch "beforeinput" event of
2892 // "insertReplacementText" for conforming to the spec. However, the
2893 // spec does NOT treat the session restoring case. Therefore, if this
2894 // breaks session restorere in a lot of web apps, we should probably
2895 // stop dispatching it or make it non-cancelable.
2896 MOZ_ASSERT(aHandlingSetValue
.GetTextControlElement());
2897 MOZ_ASSERT(!aHandlingSetValue
.GetSettingValue().IsVoid());
2898 aHandlingSetValue
.WillDispatchBeforeInputEvent();
2899 nsEventStatus status
= nsEventStatus_eIgnore
;
2900 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(
2901 MOZ_KnownLive(aHandlingSetValue
.GetTextControlElement()),
2902 eEditorBeforeInput
, EditorInputType::eInsertReplacementText
, nullptr,
2905 StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
2906 ? InputEventOptions::NeverCancelable::No
2907 : InputEventOptions::NeverCancelable::Yes
),
2909 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2910 "Failed to dispatch beforeinput event");
2911 if (status
== nsEventStatus_eConsumeNoDefault
) {
2912 return true; // "beforeinput" event was canceled.
2914 // If we were destroyed by "beforeinput" event listeners, probably, we
2915 // don't need to keep handling it.
2916 if (aHandlingSetValue
.IsTextControlStateDestroyed()) {
2919 // Even if "beforeinput" event was not canceled, its listeners may do
2920 // something. If it causes creating `TextEditor` and bind this to a
2921 // frame, we need to use the path, but `TextEditor` shouldn't fire
2922 // "beforeinput" event again. Therefore, we need to prevent editor
2924 if (mTextEditor
&& mBoundFrame
) {
2925 AutoInputEventSuppresser
suppressInputEvent(mTextEditor
);
2926 if (!SetValueWithTextEditor(aHandlingSetValue
)) {
2929 // If we were destroyed by "beforeinput" event listeners, probably, we
2930 // don't need to keep handling it.
2931 if (aHandlingSetValue
.IsTextControlStateDestroyed()) {
2934 handleSettingValue
= false;
2938 if (handleSettingValue
) {
2939 if (!mValue
.Assign(aHandlingSetValue
.GetSettingValue(), fallible
)) {
2943 // Since we have no editor we presumably have cached selection state.
2944 if (IsSelectionCached()) {
2945 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
2946 aHandlingSetValue
.ValueSetterOptionsRef()));
2948 SelectionProperties
& props
= GetSelectionProperties();
2949 // Setting a max length and thus capping selection range early prevents
2950 // selection change detection in setRangeText. Temporarily disable
2951 // capping here with UINT32_MAX, and set it later in ::SetRangeText().
2952 props
.SetMaxLength(aHandlingSetValue
.ValueSetterOptionsRef().contains(
2953 ValueSetterOption::BySetRangeTextAPI
)
2955 : aHandlingSetValue
.GetSettingValue().Length());
2956 if (aHandlingSetValue
.ValueSetterOptionsRef().contains(
2957 ValueSetterOption::MoveCursorToEndIfValueChanged
)) {
2958 props
.SetStart(aHandlingSetValue
.GetSettingValue().Length());
2959 props
.SetEnd(aHandlingSetValue
.GetSettingValue().Length());
2960 props
.SetDirection(SelectionDirection::Forward
);
2961 } else if (aHandlingSetValue
.ValueSetterOptionsRef().contains(
2963 MoveCursorToBeginSetSelectionDirectionForward
)) {
2966 props
.SetDirection(SelectionDirection::Forward
);
2970 // Update the frame display if needed
2972 mBoundFrame
->UpdateValueDisplay(true);
2975 // If the text control element has focus, IMEContentObserver is not
2976 // observing the content changes due to no bound frame or no TextEditor.
2977 // Therefore, we need to let IMEContentObserver know all values are being
2979 if (IMEContentObserver
* observer
= GetIMEContentObserver()) {
2980 observer
->OnTextControlValueChangedWhileNotObservable(mValue
);
2984 // If this is called as part of user input, we need to dispatch "input"
2985 // event with "insertReplacementText" since web apps may want to know
2986 // the user operation which changes editor value with a built-in function
2987 // like autocomplete, password manager, session restore, etc.
2988 // XXX Should we stop dispatching `input` event if the text control
2989 // element has already removed from the DOM tree by a `beforeinput`
2991 if (aHandlingSetValue
.ValueSetterOptionsRef().contains(
2992 ValueSetterOption::BySetUserInputAPI
)) {
2993 MOZ_ASSERT(aHandlingSetValue
.GetTextControlElement());
2995 // Update validity state before dispatching "input" event for its
2996 // listeners like `EditorBase::NotifyEditorObservers()`.
2997 aHandlingSetValue
.GetTextControlElement()->OnValueChanged(
2998 ValueChangeKind::UserInteraction
,
2999 aHandlingSetValue
.GetSettingValue());
3001 ClearLastInteractiveValue();
3003 MOZ_ASSERT(!aHandlingSetValue
.GetSettingValue().IsVoid());
3004 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(
3005 MOZ_KnownLive(aHandlingSetValue
.GetTextControlElement()),
3006 eEditorInput
, EditorInputType::eInsertReplacementText
, nullptr,
3007 InputEventOptions(inputEventData
,
3008 InputEventOptions::NeverCancelable::No
));
3009 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3010 "Failed to dispatch input event");
3013 // Even if our value is not actually changing, apparently we need to mark
3014 // our SelectionProperties dirty to make accessibility tests happy.
3015 // Probably because they depend on the SetSelectionRange() call we make on
3016 // our frame in RestoreSelectionState, but I have no idea why they do.
3017 if (IsSelectionCached()) {
3018 SelectionProperties
& props
= GetSelectionProperties();
3026 void TextControlState::InitializeKeyboardEventListeners() {
3027 // register key listeners
3028 EventListenerManager
* manager
=
3029 mTextCtrlElement
->GetOrCreateListenerManager();
3031 manager
->AddEventListenerByType(mTextListener
, u
"keydown"_ns
,
3032 TrustedEventsAtSystemGroupBubble());
3033 manager
->AddEventListenerByType(mTextListener
, u
"keypress"_ns
,
3034 TrustedEventsAtSystemGroupBubble());
3035 manager
->AddEventListenerByType(mTextListener
, u
"keyup"_ns
,
3036 TrustedEventsAtSystemGroupBubble());
3039 mSelCon
->SetScrollableFrame(mBoundFrame
->GetScrollTargetFrame());
3042 void TextControlState::SetPreviewText(const nsAString
& aValue
, bool aNotify
) {
3043 // If we don't have a preview div, there's nothing to do.
3044 Element
* previewDiv
= GetPreviewNode();
3049 nsAutoString
previewValue(aValue
);
3051 nsContentUtils::RemoveNewlines(previewValue
);
3052 MOZ_ASSERT(previewDiv
->GetFirstChild(), "preview div has no child");
3053 previewDiv
->GetFirstChild()->AsText()->SetText(previewValue
, aNotify
);
3056 void TextControlState::GetPreviewText(nsAString
& aValue
) {
3057 // If we don't have a preview div, there's nothing to do.
3058 Element
* previewDiv
= GetPreviewNode();
3063 MOZ_ASSERT(previewDiv
->GetFirstChild(), "preview div has no child");
3064 const nsTextFragment
* text
= previewDiv
->GetFirstChild()->GetText();
3067 text
->AppendTo(aValue
);
3070 bool TextControlState::EditorHasComposition() {
3071 return mTextEditor
&& mTextEditor
->IsIMEComposing();
3074 IMEContentObserver
* TextControlState::GetIMEContentObserver() const {
3075 if (NS_WARN_IF(!mTextCtrlElement
) ||
3076 mTextCtrlElement
!= IMEStateManager::GetFocusedElement()) {
3079 IMEContentObserver
* observer
= IMEStateManager::GetActiveContentObserver();
3080 // The text control element may be an editing host. In this case, the
3081 // observer does not observe the anonymous nodes under mTextCtrlElement.
3082 // So, it means that the observer is not for ours.
3083 return observer
&& observer
->EditorIsTextEditor() ? observer
: nullptr;
3086 } // namespace mozilla