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