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 "nsTextEditorState.h"
8 #include "mozilla/TextInputListener.h"
13 #include "nsLayoutCID.h"
14 #include "nsITextControlFrame.h"
15 #include "nsContentCreatorFunctions.h"
16 #include "nsTextControlFrame.h"
17 #include "nsIControllers.h"
18 #include "nsITransactionManager.h"
19 #include "nsIControllerContext.h"
20 #include "nsAttrValue.h"
21 #include "nsAttrValueInlines.h"
22 #include "nsGenericHTMLElement.h"
23 #include "nsIDOMEventListener.h"
24 #include "nsIEditorObserver.h"
25 #include "nsIWidget.h"
26 #include "nsIDocumentEncoder.h"
27 #include "nsPIDOMWindow.h"
28 #include "nsServiceManagerUtils.h"
29 #include "mozilla/dom/Selection.h"
30 #include "mozilla/TextEditRules.h"
31 #include "mozilla/EventListenerManager.h"
32 #include "nsContentUtils.h"
33 #include "mozilla/Preferences.h"
34 #include "nsTextNode.h"
35 #include "nsIController.h"
36 #include "mozilla/AutoRestore.h"
37 #include "mozilla/PresShell.h"
38 #include "mozilla/TextEvents.h"
39 #include "mozilla/dom/Event.h"
40 #include "mozilla/dom/ScriptSettings.h"
41 #include "mozilla/dom/HTMLInputElement.h"
42 #include "mozilla/dom/HTMLTextAreaElement.h"
43 #include "mozilla/dom/Text.h"
44 #include "mozilla/StaticPrefs.h"
45 #include "nsNumberControlFrame.h"
46 #include "nsFrameSelection.h"
47 #include "mozilla/ErrorResult.h"
48 #include "mozilla/Telemetry.h"
49 #include "mozilla/ShortcutKeys.h"
50 #include "nsXBLPrototypeHandler.h"
51 #include "mozilla/dom/KeyboardEvent.h"
53 using namespace mozilla
;
54 using namespace mozilla::dom
;
55 using ValueChangeKind
= nsITextControlElement::ValueChangeKind
;
57 inline nsresult
SetEditorFlagsIfNecessary(EditorBase
& aEditorBase
,
59 if (aEditorBase
.Flags() == aFlags
) {
62 return aEditorBase
.SetFlags(aFlags
);
65 class MOZ_STACK_CLASS AutoInputEventSuppresser final
{
67 explicit AutoInputEventSuppresser(TextEditor
* aTextEditor
)
68 : mTextEditor(aTextEditor
)
69 // To protect against a reentrant call to SetValue, we check whether
70 // another SetValue is already happening for this editor. If it is,
71 // we must wait until we unwind to re-enable oninput events.
73 mOuterTransaction(aTextEditor
->IsSuppressingDispatchingInputEvent()) {
74 MOZ_ASSERT(aTextEditor
);
76 ~AutoInputEventSuppresser() {
77 mTextEditor
->SuppressDispatchingInputEvent(mOuterTransaction
);
79 void Init() { mTextEditor
->SuppressDispatchingInputEvent(true); }
82 RefPtr
<TextEditor
> mTextEditor
;
83 bool mOuterTransaction
;
86 class RestoreSelectionState
: public Runnable
{
88 RestoreSelectionState(nsTextEditorState
* aState
, nsTextControlFrame
* aFrame
)
89 : mozilla::Runnable("RestoreSelectionState"),
91 mTextEditorState(aState
) {}
93 NS_IMETHOD
Run() override
{
94 if (!mTextEditorState
) {
98 AutoHideSelectionChanges
hideSelectionChanges(
99 mFrame
->GetConstFrameSelection());
102 // SetSelectionRange leads to Selection::AddRange which flushes Layout -
103 // need to block script to avoid nested PrepareEditor calls (bug 642800).
104 nsAutoScriptBlocker scriptBlocker
;
105 nsTextEditorState::SelectionProperties
& properties
=
106 mTextEditorState
->GetSelectionProperties();
107 if (properties
.IsDirty()) {
108 mFrame
->SetSelectionRange(properties
.GetStart(), properties
.GetEnd(),
109 properties
.GetDirection());
111 if (!mTextEditorState
->mSelectionRestoreEagerInit
) {
112 mTextEditorState
->HideSelectionIfBlurred();
114 mTextEditorState
->mSelectionRestoreEagerInit
= false;
117 if (mTextEditorState
) {
118 mTextEditorState
->FinishedRestoringSelection();
123 // Let the text editor tell us we're no longer relevant - avoids use of
127 mTextEditorState
= nullptr;
131 nsTextControlFrame
* mFrame
;
132 nsTextEditorState
* mTextEditorState
;
135 class MOZ_RAII AutoRestoreEditorState final
{
137 explicit AutoRestoreEditorState(
138 TextEditor
* aTextEditor MOZ_GUARD_OBJECT_NOTIFIER_PARAM
)
139 : mTextEditor(aTextEditor
),
140 mSavedFlags(mTextEditor
->Flags()),
141 mSavedMaxLength(mTextEditor
->MaxTextLength()) {
142 MOZ_GUARD_OBJECT_NOTIFIER_INIT
;
143 MOZ_ASSERT(mTextEditor
);
145 // EditorBase::SetFlags() is a virtual method. Even though it does nothing
146 // if new flags and current flags are same, the calling cost causes
147 // appearing the method in profile. So, this class should check if it's
148 // necessary to call.
149 uint32_t flags
= mSavedFlags
;
150 flags
&= ~(nsIPlaintextEditor::eEditorDisabledMask
);
151 flags
&= ~(nsIPlaintextEditor::eEditorReadonlyMask
);
152 flags
|= nsIPlaintextEditor::eEditorDontEchoPassword
;
153 if (mSavedFlags
!= flags
) {
154 mTextEditor
->SetFlags(flags
);
156 mTextEditor
->SetMaxTextLength(-1);
159 ~AutoRestoreEditorState() {
160 mTextEditor
->SetMaxTextLength(mSavedMaxLength
);
161 SetEditorFlagsIfNecessary(*mTextEditor
, mSavedFlags
);
165 TextEditor
* mTextEditor
;
166 uint32_t mSavedFlags
;
167 int32_t mSavedMaxLength
;
168 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
171 class MOZ_RAII AutoDisableUndo final
{
173 explicit AutoDisableUndo(
174 TextEditor
* aTextEditor MOZ_GUARD_OBJECT_NOTIFIER_PARAM
)
175 : mTextEditor(aTextEditor
), mNumberOfMaximumTransactions(0) {
176 MOZ_GUARD_OBJECT_NOTIFIER_INIT
;
177 MOZ_ASSERT(mTextEditor
);
179 mNumberOfMaximumTransactions
=
180 mTextEditor
? mTextEditor
->NumberOfMaximumTransactions() : 0;
181 DebugOnly
<bool> disabledUndoRedo
= mTextEditor
->DisableUndoRedo();
182 NS_WARNING_ASSERTION(disabledUndoRedo
,
183 "Failed to disable undo/redo transactions");
187 // Don't change enable/disable of undo/redo if it's enabled after
188 // it's disabled by the constructor because we shouldn't change
189 // the maximum undo/redo count to the old value.
190 if (mTextEditor
->IsUndoRedoEnabled()) {
193 // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
194 // than 0. Only when it's 0, it was disabled.
195 if (mNumberOfMaximumTransactions
) {
196 DebugOnly
<bool> enabledUndoRedo
=
197 mTextEditor
->EnableUndoRedo(mNumberOfMaximumTransactions
);
198 NS_WARNING_ASSERTION(enabledUndoRedo
,
199 "Failed to enable undo/redo transactions");
201 DebugOnly
<bool> disabledUndoRedo
= mTextEditor
->DisableUndoRedo();
202 NS_WARNING_ASSERTION(disabledUndoRedo
,
203 "Failed to disable undo/redo transactions");
208 TextEditor
* mTextEditor
;
209 int32_t mNumberOfMaximumTransactions
;
210 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
214 bool nsITextControlElement::GetWrapPropertyEnum(
215 nsIContent
* aContent
, nsITextControlElement::nsHTMLTextWrap
& aWrapProp
) {
216 // soft is the default; "physical" defaults to soft as well because all other
217 // browsers treat it that way and there is no real reason to maintain physical
218 // and virtual as separate entities if no one else does. Only hard and off
219 // do anything different.
220 aWrapProp
= eHTMLTextWrap_Soft
; // the default
223 if (aContent
->IsHTMLElement()) {
224 static Element::AttrValuesArray strings
[] = {nsGkAtoms::HARD
,
225 nsGkAtoms::OFF
, nullptr};
227 switch (aContent
->AsElement()->FindAttrValueIn(
228 kNameSpaceID_None
, nsGkAtoms::wrap
, strings
, eIgnoreCase
)) {
230 aWrapProp
= eHTMLTextWrap_Hard
;
233 aWrapProp
= eHTMLTextWrap_Off
;
244 already_AddRefed
<nsITextControlElement
>
245 nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent
* aHost
) {
250 nsCOMPtr
<nsITextControlElement
> parent
=
251 do_QueryInterface(aHost
->GetParent());
253 return parent
.forget();
256 static bool SuppressEventHandlers(nsPresContext
* aPresContext
) {
257 bool suppressHandlers
= false;
260 // Right now we only suppress event handlers and controller manipulation
261 // when in a print preview or print context!
263 // In the current implementation, we only paginate when
264 // printing or in print preview.
266 suppressHandlers
= aPresContext
->IsPaginated();
269 return suppressHandlers
;
272 class nsAnonDivObserver final
: public nsStubMutationObserver
{
274 explicit nsAnonDivObserver(nsTextEditorState
* aTextEditorState
)
275 : mTextEditorState(aTextEditorState
) {}
277 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
278 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
279 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
280 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
283 ~nsAnonDivObserver() {}
284 nsTextEditorState
* mTextEditorState
;
287 class nsTextInputSelectionImpl final
: public nsSupportsWeakReference
,
288 public nsISelectionController
{
289 ~nsTextInputSelectionImpl() {}
292 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
293 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl
,
294 nsISelectionController
)
296 nsTextInputSelectionImpl(nsFrameSelection
* aSel
, PresShell
* aPresShell
,
297 nsIContent
* aLimiter
);
299 void SetScrollableFrame(nsIScrollableFrame
* aScrollableFrame
);
300 nsFrameSelection
* GetConstFrameSelection() { return mFrameSelection
; }
301 // Will return null if !mFrameSelection.
302 Selection
* GetSelection(SelectionType aSelectionType
);
304 // NSISELECTIONCONTROLLER INTERFACES
305 NS_IMETHOD
SetDisplaySelection(int16_t toggle
) override
;
306 NS_IMETHOD
GetDisplaySelection(int16_t* _retval
) override
;
307 NS_IMETHOD
SetSelectionFlags(int16_t aInEnable
) override
;
308 NS_IMETHOD
GetSelectionFlags(int16_t* aOutEnable
) override
;
309 NS_IMETHOD
GetSelectionFromScript(RawSelectionType aRawSelectionType
,
310 Selection
** aSelection
) override
;
311 Selection
* GetSelection(RawSelectionType aRawSelectionType
) override
;
312 NS_IMETHOD
ScrollSelectionIntoView(RawSelectionType aRawSelectionType
,
313 int16_t aRegion
, int16_t aFlags
) override
;
314 NS_IMETHOD
RepaintSelection(RawSelectionType aRawSelectionType
) override
;
315 nsresult
RepaintSelection(nsPresContext
* aPresContext
,
316 SelectionType aSelectionType
);
317 NS_IMETHOD
SetCaretEnabled(bool enabled
) override
;
318 NS_IMETHOD
SetCaretReadOnly(bool aReadOnly
) override
;
319 NS_IMETHOD
GetCaretEnabled(bool* _retval
) override
;
320 NS_IMETHOD
GetCaretVisible(bool* _retval
) override
;
321 NS_IMETHOD
SetCaretVisibilityDuringSelection(bool aVisibility
) override
;
322 NS_IMETHOD
PhysicalMove(int16_t aDirection
, int16_t aAmount
,
323 bool aExtend
) override
;
324 NS_IMETHOD
CharacterMove(bool aForward
, bool aExtend
) override
;
325 NS_IMETHOD
CharacterExtendForDelete() override
;
326 NS_IMETHOD
CharacterExtendForBackspace() override
;
327 NS_IMETHOD
WordMove(bool aForward
, bool aExtend
) override
;
328 NS_IMETHOD
WordExtendForDelete(bool aForward
) override
;
329 NS_IMETHOD
LineMove(bool aForward
, bool aExtend
) override
;
330 NS_IMETHOD
IntraLineMove(bool aForward
, bool aExtend
) override
;
332 NS_IMETHOD
PageMove(bool aForward
, bool aExtend
) override
;
333 NS_IMETHOD
CompleteScroll(bool aForward
) override
;
334 NS_IMETHOD
CompleteMove(bool aForward
, bool aExtend
) override
;
335 NS_IMETHOD
ScrollPage(bool aForward
) override
;
336 NS_IMETHOD
ScrollLine(bool aForward
) override
;
337 NS_IMETHOD
ScrollCharacter(bool aRight
) override
;
338 NS_IMETHOD
SelectAll(void) override
;
339 NS_IMETHOD
CheckVisibility(nsINode
* node
, int16_t startOffset
,
340 int16_t EndOffset
, bool* _retval
) override
;
341 virtual nsresult
CheckVisibilityContent(nsIContent
* aNode
,
342 int16_t aStartOffset
,
344 bool* aRetval
) override
;
347 RefPtr
<nsFrameSelection
> mFrameSelection
;
348 nsCOMPtr
<nsIContent
> mLimiter
;
349 nsIScrollableFrame
* mScrollFrame
;
350 nsWeakPtr mPresShellWeak
;
353 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl
)
354 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl
)
355 NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl
)
356 NS_INTERFACE_TABLE(nsTextInputSelectionImpl
, nsISelectionController
,
357 nsISelectionDisplay
, nsISupportsWeakReference
)
358 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl
)
361 NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl
, mFrameSelection
, mLimiter
)
363 // BEGIN nsTextInputSelectionImpl
365 nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection
* aSel
,
366 PresShell
* aPresShell
,
367 nsIContent
* aLimiter
)
368 : mScrollFrame(nullptr) {
369 if (aSel
&& aPresShell
) {
370 mFrameSelection
= aSel
; // we are the owner now!
372 bool accessibleCaretEnabled
=
373 PresShell::AccessibleCaretEnabled(aLimiter
->OwnerDoc()->GetDocShell());
374 mFrameSelection
->Init(aPresShell
, mLimiter
, accessibleCaretEnabled
);
375 mPresShellWeak
= do_GetWeakReference(aPresShell
);
379 void nsTextInputSelectionImpl::SetScrollableFrame(
380 nsIScrollableFrame
* aScrollableFrame
) {
381 mScrollFrame
= aScrollableFrame
;
382 if (!mScrollFrame
&& mFrameSelection
) {
383 mFrameSelection
->DisconnectFromPresShell();
384 mFrameSelection
= nullptr;
388 Selection
* nsTextInputSelectionImpl::GetSelection(
389 SelectionType aSelectionType
) {
390 if (!mFrameSelection
) {
394 return mFrameSelection
->GetSelection(aSelectionType
);
398 nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle
) {
399 if (!mFrameSelection
) return NS_ERROR_NULL_POINTER
;
401 mFrameSelection
->SetDisplaySelection(aToggle
);
406 nsTextInputSelectionImpl::GetDisplaySelection(int16_t* aToggle
) {
407 if (!mFrameSelection
) return NS_ERROR_NULL_POINTER
;
409 *aToggle
= mFrameSelection
->GetDisplaySelection();
414 nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle
) {
415 return NS_OK
; // stub this out. not used in input
419 nsTextInputSelectionImpl::GetSelectionFlags(int16_t* aOutEnable
) {
420 *aOutEnable
= nsISelectionDisplay::DISPLAY_TEXT
;
425 nsTextInputSelectionImpl::GetSelectionFromScript(
426 RawSelectionType aRawSelectionType
, Selection
** aSelection
) {
427 if (!mFrameSelection
) return NS_ERROR_NULL_POINTER
;
430 mFrameSelection
->GetSelection(ToSelectionType(aRawSelectionType
));
432 // GetSelection() fails only when aRawSelectionType is invalid value.
433 if (!(*aSelection
)) {
434 return NS_ERROR_INVALID_ARG
;
437 NS_ADDREF(*aSelection
);
441 Selection
* nsTextInputSelectionImpl::GetSelection(
442 RawSelectionType aRawSelectionType
) {
443 return GetSelection(ToSelectionType(aRawSelectionType
));
447 nsTextInputSelectionImpl::ScrollSelectionIntoView(
448 RawSelectionType aRawSelectionType
, int16_t aRegion
, int16_t aFlags
) {
449 if (!mFrameSelection
) return NS_ERROR_FAILURE
;
451 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
452 return frameSelection
->ScrollSelectionIntoView(
453 ToSelectionType(aRawSelectionType
), aRegion
, aFlags
);
457 nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType
) {
458 if (!mFrameSelection
) return NS_ERROR_FAILURE
;
460 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
461 return frameSelection
->RepaintSelection(ToSelectionType(aRawSelectionType
));
464 nsresult
nsTextInputSelectionImpl::RepaintSelection(
465 nsPresContext
* aPresContext
, SelectionType aSelectionType
) {
466 if (!mFrameSelection
) return NS_ERROR_FAILURE
;
468 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
469 return frameSelection
->RepaintSelection(aSelectionType
);
473 nsTextInputSelectionImpl::SetCaretEnabled(bool enabled
) {
474 if (!mPresShellWeak
) {
475 return NS_ERROR_NOT_INITIALIZED
;
477 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
);
479 return NS_ERROR_FAILURE
;
482 // tell the pres shell to enable the caret, rather than settings its
483 // visibility directly. this way the presShell's idea of caret visibility is
485 presShell
->SetCaretEnabled(enabled
);
491 nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly
) {
492 if (!mPresShellWeak
) {
493 return NS_ERROR_NOT_INITIALIZED
;
496 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
498 return NS_ERROR_FAILURE
;
500 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
502 return NS_ERROR_FAILURE
;
504 Selection
* selection
= mFrameSelection
->GetSelection(SelectionType::eNormal
);
506 caret
->SetCaretReadOnly(aReadOnly
);
512 nsTextInputSelectionImpl::GetCaretEnabled(bool* _retval
) {
513 return GetCaretVisible(_retval
);
517 nsTextInputSelectionImpl::GetCaretVisible(bool* _retval
) {
518 if (!mPresShellWeak
) {
519 return NS_ERROR_NOT_INITIALIZED
;
522 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
524 return NS_ERROR_FAILURE
;
526 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
528 return NS_ERROR_FAILURE
;
530 *_retval
= caret
->IsVisible();
535 nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility
) {
536 if (!mPresShellWeak
) {
537 return NS_ERROR_NOT_INITIALIZED
;
540 RefPtr
<PresShell
> presShell
= do_QueryReferent(mPresShellWeak
, &rv
);
542 return NS_ERROR_FAILURE
;
544 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
546 return NS_ERROR_FAILURE
;
548 Selection
* selection
= mFrameSelection
->GetSelection(SelectionType::eNormal
);
550 caret
->SetVisibilityDuringSelection(aVisibility
);
556 nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection
, int16_t aAmount
,
558 if (mFrameSelection
) {
559 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
560 return frameSelection
->PhysicalMove(aDirection
, aAmount
, aExtend
);
562 return NS_ERROR_NULL_POINTER
;
566 nsTextInputSelectionImpl::CharacterMove(bool aForward
, bool aExtend
) {
567 if (mFrameSelection
) {
568 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
569 return frameSelection
->CharacterMove(aForward
, aExtend
);
571 return NS_ERROR_NULL_POINTER
;
575 nsTextInputSelectionImpl::CharacterExtendForDelete() {
576 if (mFrameSelection
) {
577 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
578 return frameSelection
->CharacterExtendForDelete();
580 return NS_ERROR_NULL_POINTER
;
584 nsTextInputSelectionImpl::CharacterExtendForBackspace() {
585 if (mFrameSelection
) {
586 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
587 return frameSelection
->CharacterExtendForBackspace();
589 return NS_ERROR_NULL_POINTER
;
593 nsTextInputSelectionImpl::WordMove(bool aForward
, bool aExtend
) {
594 if (mFrameSelection
) {
595 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
596 return frameSelection
->WordMove(aForward
, aExtend
);
598 return NS_ERROR_NULL_POINTER
;
602 nsTextInputSelectionImpl::WordExtendForDelete(bool aForward
) {
603 if (mFrameSelection
) {
604 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
605 return frameSelection
->WordExtendForDelete(aForward
);
607 return NS_ERROR_NULL_POINTER
;
611 nsTextInputSelectionImpl::LineMove(bool aForward
, bool aExtend
) {
612 if (mFrameSelection
) {
613 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
614 nsresult result
= frameSelection
->LineMove(aForward
, aExtend
);
615 if (NS_FAILED(result
)) result
= CompleteMove(aForward
, aExtend
);
618 return NS_ERROR_NULL_POINTER
;
622 nsTextInputSelectionImpl::IntraLineMove(bool aForward
, bool aExtend
) {
623 if (mFrameSelection
) {
624 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
625 return frameSelection
->IntraLineMove(aForward
, aExtend
);
627 return NS_ERROR_NULL_POINTER
;
631 nsTextInputSelectionImpl::PageMove(bool aForward
, bool aExtend
) {
632 // expected behavior for PageMove is to scroll AND move the caret
633 // and to remain relative position of the caret in view. see Bug 4302.
635 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
636 nsIFrame
* scrollFrame
= do_QueryFrame(mScrollFrame
);
637 frameSelection
->CommonPageMove(aForward
, aExtend
, scrollFrame
);
639 // After ScrollSelectionIntoView(), the pending notifications might be
640 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
641 return ScrollSelectionIntoView(
642 nsISelectionController::SELECTION_NORMAL
,
643 nsISelectionController::SELECTION_FOCUS_REGION
,
644 nsISelectionController::SCROLL_SYNCHRONOUS
|
645 nsISelectionController::SCROLL_FOR_CARET_MOVE
);
649 nsTextInputSelectionImpl::CompleteScroll(bool aForward
) {
650 if (!mScrollFrame
) return NS_ERROR_NOT_INITIALIZED
;
652 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
653 nsIScrollableFrame::WHOLE
, ScrollMode::Instant
);
658 nsTextInputSelectionImpl::CompleteMove(bool aForward
, bool aExtend
) {
659 NS_ENSURE_STATE(mFrameSelection
);
660 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
662 // grab the parent / root DIV for this text widget
663 nsIContent
* parentDIV
= frameSelection
->GetLimiter();
664 if (!parentDIV
) return NS_ERROR_UNEXPECTED
;
666 // make the caret be either at the very beginning (0) or the very end
668 CaretAssociationHint hint
= CARET_ASSOCIATE_BEFORE
;
670 offset
= parentDIV
->GetChildCount();
672 // Prevent the caret from being placed after the last
673 // BR node in the content tree!
676 nsIContent
* child
= parentDIV
->GetLastChild();
678 if (child
->IsHTMLElement(nsGkAtoms::br
)) {
680 hint
= CARET_ASSOCIATE_AFTER
; // for Bug 106855
685 frameSelection
->HandleClick(parentDIV
, offset
, offset
, aExtend
, false, hint
);
687 // if we got this far, attempt to scroll no matter what the above result is
688 return CompleteScroll(aForward
);
692 nsTextInputSelectionImpl::ScrollPage(bool aForward
) {
693 if (!mScrollFrame
) return NS_ERROR_NOT_INITIALIZED
;
695 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
696 nsIScrollableFrame::PAGES
, ScrollMode::Smooth
);
701 nsTextInputSelectionImpl::ScrollLine(bool aForward
) {
702 if (!mScrollFrame
) return NS_ERROR_NOT_INITIALIZED
;
704 mScrollFrame
->ScrollBy(nsIntPoint(0, aForward
? 1 : -1),
705 nsIScrollableFrame::LINES
, ScrollMode::Smooth
);
710 nsTextInputSelectionImpl::ScrollCharacter(bool aRight
) {
711 if (!mScrollFrame
) return NS_ERROR_NOT_INITIALIZED
;
713 mScrollFrame
->ScrollBy(nsIntPoint(aRight
? 1 : -1, 0),
714 nsIScrollableFrame::LINES
, ScrollMode::Smooth
);
719 nsTextInputSelectionImpl::SelectAll() {
720 if (mFrameSelection
) {
721 RefPtr
<nsFrameSelection
> frameSelection
= mFrameSelection
;
722 return frameSelection
->SelectAll();
724 return NS_ERROR_NULL_POINTER
;
728 nsTextInputSelectionImpl::CheckVisibility(nsINode
* node
, int16_t startOffset
,
729 int16_t EndOffset
, bool* _retval
) {
730 if (!mPresShellWeak
) return NS_ERROR_NOT_INITIALIZED
;
732 nsCOMPtr
<nsISelectionController
> shell
=
733 do_QueryReferent(mPresShellWeak
, &result
);
735 return shell
->CheckVisibility(node
, startOffset
, EndOffset
, _retval
);
737 return NS_ERROR_FAILURE
;
740 nsresult
nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent
* aNode
,
741 int16_t aStartOffset
,
744 if (!mPresShellWeak
) {
745 return NS_ERROR_NOT_INITIALIZED
;
748 nsCOMPtr
<nsISelectionController
> shell
= do_QueryReferent(mPresShellWeak
);
749 NS_ENSURE_TRUE(shell
, NS_ERROR_FAILURE
);
751 return shell
->CheckVisibilityContent(aNode
, aStartOffset
, aEndOffset
,
756 * mozilla::TextInputListener implementation
759 TextInputListener::TextInputListener(nsITextControlElement
* aTxtCtrlElement
)
761 mTxtCtrlElement(aTxtCtrlElement
),
762 mSelectionWasCollapsed(true),
763 mHadUndoItems(false),
764 mHadRedoItems(false),
765 mSettingValue(false),
766 mSetValueChanged(true),
767 mListeningToSelectionChange(false) {}
769 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener
)
770 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener
)
772 NS_INTERFACE_MAP_BEGIN(TextInputListener
)
773 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
774 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
775 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIDOMEventListener
)
776 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener
)
779 NS_IMPL_CYCLE_COLLECTION_0(TextInputListener
)
781 void TextInputListener::OnSelectionChange(Selection
& aSelection
,
783 if (!mListeningToSelectionChange
) {
787 AutoWeakFrame weakFrame
= mFrame
;
789 // Fire the select event
790 // The specs don't exactly say when we should fire the select event.
791 // IE: Whenever you add/remove a character to/from the selection. Also
792 // each time for select all. Also if you get to the end of the text
793 // field you will get new event for each keypress or a continuous
794 // stream of events if you use the mouse. IE will fire select event
795 // when the selection collapses to nothing if you are holding down
796 // the shift or mouse button.
797 // Mozilla: If we have non-empty selection we will fire a new event for each
798 // keypress (or mouseup) if the selection changed. Mozilla will also
799 // create the event each time select all is called, even if
800 // everything was previously selected, becase technically select all
801 // will first collapse and then extend. Mozilla will never create an
802 // event if the selection collapses to nothing.
803 bool collapsed
= aSelection
.IsCollapsed();
804 if (!collapsed
&& (aReason
& (nsISelectionListener::MOUSEUP_REASON
|
805 nsISelectionListener::KEYPRESS_REASON
|
806 nsISelectionListener::SELECTALL_REASON
))) {
807 if (nsCOMPtr
<nsIContent
> content
= mFrame
->GetContent()) {
808 if (nsCOMPtr
<Document
> doc
= content
->GetComposedDoc()) {
809 if (RefPtr
<PresShell
> presShell
= doc
->GetPresShell()) {
810 nsEventStatus status
= nsEventStatus_eIgnore
;
811 WidgetEvent
event(true, eFormSelect
);
813 presShell
->HandleEventWithTarget(&event
, mFrame
, content
, &status
);
819 // if the collapsed state did not change, don't fire notifications
820 if (collapsed
== mSelectionWasCollapsed
) {
824 mSelectionWasCollapsed
= collapsed
;
826 if (!weakFrame
.IsAlive() || !mFrame
||
827 !nsContentUtils::IsFocusedContent(mFrame
->GetContent())) {
831 UpdateTextInputCommands(NS_LITERAL_STRING("select"), &aSelection
, aReason
);
835 static void DoCommandCallback(Command aCommand
, void* aData
) {
836 nsTextControlFrame
* frame
= static_cast<nsTextControlFrame
*>(aData
);
837 nsIContent
* content
= frame
->GetContent();
839 nsCOMPtr
<nsIControllers
> controllers
;
840 HTMLInputElement
* input
= HTMLInputElement::FromNode(content
);
842 input
->GetControllers(getter_AddRefs(controllers
));
844 HTMLTextAreaElement
* textArea
= HTMLTextAreaElement::FromNode(content
);
847 textArea
->GetControllers(getter_AddRefs(controllers
));
852 NS_WARNING("Could not get controllers");
856 const char* commandStr
= WidgetKeyboardEvent::GetCommandStr(aCommand
);
858 nsCOMPtr
<nsIController
> controller
;
859 controllers
->GetControllerForCommand(commandStr
, getter_AddRefs(controller
));
865 nsresult rv
= controller
->IsCommandEnabled(commandStr
, &commandEnabled
);
866 NS_ENSURE_SUCCESS_VOID(rv
);
867 if (commandEnabled
) {
868 controller
->DoCommand(commandStr
);
873 TextInputListener::HandleEvent(Event
* aEvent
) {
874 if (aEvent
->DefaultPrevented()) {
878 if (!aEvent
->IsTrusted()) {
882 RefPtr
<KeyboardEvent
> keyEvent
= aEvent
->AsKeyboardEvent();
884 return NS_ERROR_UNEXPECTED
;
887 WidgetKeyboardEvent
* widgetKeyEvent
=
888 aEvent
->WidgetEventPtr()->AsKeyboardEvent();
890 return NS_ERROR_UNEXPECTED
;
893 nsXBLPrototypeHandler
* keyHandlers
= ShortcutKeys::GetHandlers(
894 mTxtCtrlElement
->IsTextArea() ? HandlerType::eTextArea
895 : HandlerType::eInput
);
897 RefPtr
<nsAtom
> eventTypeAtom
=
898 ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent
);
899 for (nsXBLPrototypeHandler
* handler
= keyHandlers
; handler
;
900 handler
= handler
->GetNextHandler()) {
901 if (!handler
->EventTypeEquals(eventTypeAtom
)) {
905 if (!handler
->KeyEventMatched(keyEvent
, 0, IgnoreModifierState())) {
909 // XXX Do we execute only one handler even if the handler neither stops
910 // propagation nor prevents default of the event?
911 nsCOMPtr
<EventTarget
> target
= do_QueryInterface(mTxtCtrlElement
);
912 nsresult rv
= handler
->ExecuteHandler(target
, aEvent
);
913 if (NS_SUCCEEDED(rv
)) {
918 if (widgetKeyEvent
->mMessage
!= eKeyPress
) {
922 nsIWidget::NativeKeyBindingsType nativeKeyBindingsType
=
923 mTxtCtrlElement
->IsTextArea()
924 ? nsIWidget::NativeKeyBindingsForMultiLineEditor
925 : nsIWidget::NativeKeyBindingsForSingleLineEditor
;
927 nsIWidget
* widget
= widgetKeyEvent
->mWidget
;
928 // If the event is created by chrome script, the widget is nullptr.
930 widget
= mFrame
->GetNearestWidget();
931 NS_ENSURE_TRUE(widget
, NS_OK
);
934 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
935 // If the event is created by chrome script, it is nullptr but we need to
936 // execute native key bindings. Therefore, we need to set widget to
937 // WidgetEvent::mWidget temporarily.
938 AutoRestore
<nsCOMPtr
<nsIWidget
>> saveWidget(widgetKeyEvent
->mWidget
);
939 widgetKeyEvent
->mWidget
= widget
;
940 if (widgetKeyEvent
->ExecuteEditCommands(nativeKeyBindingsType
,
941 DoCommandCallback
, mFrame
)) {
942 aEvent
->PreventDefault();
947 void TextInputListener::OnEditActionHandled() {
949 // We've been disconnected from the nsTextEditorState object, nothing to do
954 AutoWeakFrame weakFrame
= mFrame
;
956 nsITextControlFrame
* frameBase
= do_QueryFrame(mFrame
);
957 nsTextControlFrame
* frame
= static_cast<nsTextControlFrame
*>(frameBase
);
958 NS_ASSERTION(frame
, "Where is our frame?");
960 // Update the undo / redo menus
962 RefPtr
<TextEditor
> textEditor
= frame
->GetTextEditor();
964 // Get the number of undo / redo items
965 size_t numUndoItems
= textEditor
->NumberOfUndoItems();
966 size_t numRedoItems
= textEditor
->NumberOfRedoItems();
967 if ((numUndoItems
&& !mHadUndoItems
) || (!numUndoItems
&& mHadUndoItems
) ||
968 (numRedoItems
&& !mHadRedoItems
) || (!numRedoItems
&& mHadRedoItems
)) {
969 // Modify the menu if undo or redo items are different
970 UpdateTextInputCommands(NS_LITERAL_STRING("undo"));
972 mHadUndoItems
= numUndoItems
!= 0;
973 mHadRedoItems
= numRedoItems
!= 0;
976 if (!weakFrame
.IsAlive()) {
980 HandleValueChanged(frame
);
983 void TextInputListener::HandleValueChanged(nsTextControlFrame
* aFrame
) {
984 // Make sure we know we were changed (do NOT set this to false if there are
985 // no undo items; JS could change the value and we'd still need to save it)
986 if (mSetValueChanged
) {
988 nsITextControlFrame
* frameBase
= do_QueryFrame(mFrame
);
989 aFrame
= static_cast<nsTextControlFrame
*>(frameBase
);
990 NS_ASSERTION(aFrame
, "Where is our frame?");
992 aFrame
->SetValueChanged(true);
995 if (!mSettingValue
) {
996 mTxtCtrlElement
->OnValueChanged(/* aNotify = */ true,
997 ValueChangeKind::UserInteraction
);
1001 nsresult
TextInputListener::UpdateTextInputCommands(
1002 const nsAString
& aCommandsToUpdate
, Selection
* aSelection
,
1004 nsIContent
* content
= mFrame
->GetContent();
1005 NS_ENSURE_TRUE(content
, NS_ERROR_FAILURE
);
1007 nsCOMPtr
<Document
> doc
= content
->GetComposedDoc();
1008 NS_ENSURE_TRUE(doc
, NS_ERROR_FAILURE
);
1010 nsPIDOMWindowOuter
* domWindow
= doc
->GetWindow();
1011 NS_ENSURE_TRUE(domWindow
, NS_ERROR_FAILURE
);
1013 domWindow
->UpdateCommands(aCommandsToUpdate
, aSelection
, aReason
);
1017 // END mozilla::TextInputListener
1019 // nsTextEditorState
1021 nsTextEditorState::nsTextEditorState(nsITextControlElement
* aOwningElement
)
1022 : mTextCtrlElement(aOwningElement
),
1023 mBoundFrame(nullptr),
1025 mEditorInitialized(false),
1026 mInitializing(false),
1027 mValueTransferInProgress(false),
1028 mSelectionCached(true),
1029 mSelectionRestoreEagerInit(false),
1030 mPlaceholderVisibility(false),
1031 mPreviewVisibility(false),
1032 mIsCommittingComposition(false)
1033 // When adding more member variable initializations here, add the same
1034 // also to ::Construct.
1036 MOZ_COUNT_CTOR(nsTextEditorState
);
1039 nsTextEditorState
* nsTextEditorState::Construct(
1040 nsITextControlElement
* aOwningElement
, nsTextEditorState
** aReusedState
) {
1041 if (*aReusedState
) {
1042 nsTextEditorState
* state
= *aReusedState
;
1043 *aReusedState
= nullptr;
1044 state
->mTextCtrlElement
= aOwningElement
;
1045 state
->mBoundFrame
= nullptr;
1046 state
->mSelectionProperties
= SelectionProperties();
1047 state
->mEverInited
= false;
1048 state
->mEditorInitialized
= false;
1049 state
->mInitializing
= false;
1050 state
->mValueTransferInProgress
= false;
1051 state
->mSelectionCached
= true;
1052 state
->mSelectionRestoreEagerInit
= false;
1053 state
->mPlaceholderVisibility
= false;
1054 state
->mPreviewVisibility
= false;
1055 state
->mIsCommittingComposition
= false;
1056 // When adding more member variable initializations here, add the same
1057 // also to the constructor.
1061 return new nsTextEditorState(aOwningElement
);
1064 nsTextEditorState::~nsTextEditorState() {
1065 MOZ_COUNT_DTOR(nsTextEditorState
);
1069 Element
* nsTextEditorState::GetRootNode() {
1070 return mBoundFrame
? mBoundFrame
->GetRootNode() : nullptr;
1073 Element
* nsTextEditorState::GetPreviewNode() {
1074 return mBoundFrame
? mBoundFrame
->GetPreviewNode() : nullptr;
1077 void nsTextEditorState::Clear() {
1079 mTextEditor
->SetTextInputListener(nullptr);
1083 // Oops, we still have a frame!
1084 // This should happen when the type of a text input control is being changed
1085 // to something which is not a text control. In this case, we should
1086 // pretend that a frame is being destroyed, and clean up after ourselves
1088 UnbindFromFrame(mBoundFrame
);
1089 mTextEditor
= nullptr;
1091 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1095 mTextListener
= nullptr;
1098 void nsTextEditorState::Unlink() {
1099 nsTextEditorState
* tmp
= this;
1101 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon
)
1102 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor
)
1105 void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback
& cb
) {
1106 nsTextEditorState
* tmp
= this;
1107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon
)
1108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor
)
1111 nsFrameSelection
* nsTextEditorState::GetConstFrameSelection() {
1112 if (mSelCon
) return mSelCon
->GetConstFrameSelection();
1116 TextEditor
* nsTextEditorState::GetTextEditor() {
1118 nsresult rv
= PrepareEditor();
1119 NS_ENSURE_SUCCESS(rv
, nullptr);
1124 TextEditor
* nsTextEditorState::GetTextEditorWithoutCreation() {
1128 nsISelectionController
* nsTextEditorState::GetSelectionController() const {
1132 // Helper class, used below in BindToFrame().
1133 class PrepareEditorEvent
: public Runnable
{
1135 PrepareEditorEvent(nsTextEditorState
& aState
, nsIContent
* aOwnerContent
,
1136 const nsAString
& aCurrentValue
)
1137 : mozilla::Runnable("PrepareEditorEvent"),
1139 mOwnerContent(aOwnerContent
),
1140 mCurrentValue(aCurrentValue
) {
1141 aState
.mValueTransferInProgress
= true;
1144 NS_IMETHOD
Run() override
{
1145 NS_ENSURE_TRUE(mState
, NS_ERROR_NULL_POINTER
);
1147 // Transfer the saved value to the editor if we have one
1148 const nsAString
* value
= nullptr;
1149 if (!mCurrentValue
.IsEmpty()) {
1150 value
= &mCurrentValue
;
1153 nsAutoScriptBlocker scriptBlocker
;
1155 mState
->PrepareEditor(value
);
1157 mState
->mValueTransferInProgress
= false;
1163 WeakPtr
<nsTextEditorState
> mState
;
1164 nsCOMPtr
<nsIContent
> mOwnerContent
; // strong reference
1165 nsAutoString mCurrentValue
;
1168 nsresult
nsTextEditorState::BindToFrame(nsTextControlFrame
* aFrame
) {
1169 NS_ASSERTION(aFrame
, "The frame to bind to should be valid");
1170 NS_ENSURE_ARG_POINTER(aFrame
);
1172 NS_ASSERTION(!mBoundFrame
, "Cannot bind twice, need to unbind first");
1173 NS_ENSURE_TRUE(!mBoundFrame
, NS_ERROR_FAILURE
);
1175 // If we'll need to transfer our current value to the editor, save it before
1176 // binding to the frame.
1177 nsAutoString currentValue
;
1179 GetValue(currentValue
, true);
1182 mBoundFrame
= aFrame
;
1184 Element
* rootNode
= aFrame
->GetRootNode();
1185 MOZ_ASSERT(rootNode
);
1187 PresShell
* presShell
= aFrame
->PresContext()->GetPresShell();
1188 MOZ_ASSERT(presShell
);
1191 RefPtr
<nsFrameSelection
> frameSel
= new nsFrameSelection();
1193 // Create a SelectionController
1194 mSelCon
= new nsTextInputSelectionImpl(frameSel
, presShell
, rootNode
);
1195 MOZ_ASSERT(!mTextListener
, "Should not overwrite the object");
1196 mTextListener
= new TextInputListener(mTextCtrlElement
);
1198 mTextListener
->SetFrame(mBoundFrame
);
1199 mSelCon
->SetDisplaySelection(nsISelectionController::SELECTION_ON
);
1201 // Get the caret and make it a selection listener.
1202 // FYI: It's safe to use raw pointer for calling
1203 // Selection::AddSelectionListner() because it only appends the listener
1204 // to its internal array.
1205 Selection
* selection
= mSelCon
->GetSelection(SelectionType::eNormal
);
1207 RefPtr
<nsCaret
> caret
= presShell
->GetCaret();
1209 selection
->AddSelectionListener(caret
);
1211 mTextListener
->StartToListenToSelectionChange();
1214 // If an editor exists from before, prepare it for usage
1216 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
1217 NS_ENSURE_TRUE(content
, NS_ERROR_FAILURE
);
1219 // Set the correct direction on the newly created root node
1220 if (mTextEditor
->IsRightToLeft()) {
1221 rootNode
->SetAttr(kNameSpaceID_None
, nsGkAtoms::dir
,
1222 NS_LITERAL_STRING("rtl"), false);
1223 } else if (mTextEditor
->IsLeftToRight()) {
1224 rootNode
->SetAttr(kNameSpaceID_None
, nsGkAtoms::dir
,
1225 NS_LITERAL_STRING("ltr"), false);
1227 // otherwise, inherit the content node's direction
1230 nsContentUtils::AddScriptRunner(
1231 new PrepareEditorEvent(*this, content
, currentValue
));
1237 struct MOZ_STACK_CLASS PreDestroyer
{
1238 void Init(TextEditor
* aTextEditor
) { mTextEditor
= aTextEditor
; }
1239 MOZ_CAN_RUN_SCRIPT
~PreDestroyer() {
1241 MOZ_KnownLive(mTextEditor
)->PreDestroy(true);
1244 void Swap(RefPtr
<TextEditor
>& aTextEditor
) {
1245 return mTextEditor
.swap(aTextEditor
);
1249 RefPtr
<TextEditor
> mTextEditor
;
1252 nsresult
nsTextEditorState::PrepareEditor(const nsAString
* aValue
) {
1254 // Cannot create an editor without a bound frame.
1255 // Don't return a failure code, because js callers can't handle that.
1259 if (mEditorInitialized
) {
1260 // Do not initialize the editor multiple times.
1264 AutoHideSelectionChanges
hideSelectionChanges(GetConstFrameSelection());
1266 // Don't attempt to initialize recursively!
1267 InitializationGuard
guard(*this);
1268 if (guard
.IsInitializingRecursively()) {
1269 return NS_ERROR_NOT_INITIALIZED
;
1272 // Note that we don't check mTextEditor here, because we might already have
1273 // one around, in which case we don't create a new one, and we'll just tie
1274 // the required machinery to it.
1276 nsPresContext
* presContext
= mBoundFrame
->PresContext();
1277 PresShell
* presShell
= presContext
->GetPresShell();
1279 // Setup the editor flags
1280 uint32_t editorFlags
= nsIPlaintextEditor::eEditorPlaintextMask
;
1281 if (IsSingleLineTextControl())
1282 editorFlags
|= nsIPlaintextEditor::eEditorSingleLineMask
;
1283 if (IsPasswordTextControl())
1284 editorFlags
|= nsIPlaintextEditor::eEditorPasswordMask
;
1286 // All nsTextControlFrames are widgets
1287 editorFlags
|= nsIPlaintextEditor::eEditorWidgetMask
;
1289 // Spell check is diabled at creation time. It is enabled once
1290 // the editor comes into focus.
1291 editorFlags
|= nsIPlaintextEditor::eEditorSkipSpellCheck
;
1293 bool shouldInitializeEditor
= false;
1294 RefPtr
<TextEditor
> newTextEditor
; // the editor that we might create
1295 nsresult rv
= NS_OK
;
1296 PreDestroyer preDestroyer
;
1298 shouldInitializeEditor
= true;
1301 newTextEditor
= new TextEditor();
1302 preDestroyer
.Init(newTextEditor
);
1304 // Make sure we clear out the non-breaking space before we initialize the
1306 rv
= mBoundFrame
->UpdateValueDisplay(true, true);
1307 NS_ENSURE_SUCCESS(rv
, rv
);
1309 if (aValue
|| !mEditorInitialized
) {
1310 // Set the correct value in the root node
1311 rv
= mBoundFrame
->UpdateValueDisplay(true, !mEditorInitialized
, aValue
);
1312 NS_ENSURE_SUCCESS(rv
, rv
);
1315 newTextEditor
= mTextEditor
; // just pretend that we have a new editor!
1317 // Don't lose application flags in the process.
1318 if (newTextEditor
->IsMailEditor()) {
1319 editorFlags
|= nsIPlaintextEditor::eEditorMailMask
;
1323 // Get the current value of the textfield from the content.
1324 // Note that if we've created a new editor, mTextEditor is null at this stage,
1325 // so we will get the real value from the content.
1326 nsAutoString defaultValue
;
1328 defaultValue
= *aValue
;
1330 GetValue(defaultValue
, true);
1333 if (!mEditorInitialized
) {
1334 // Now initialize the editor.
1336 // NOTE: Conversion of '\n' to <BR> happens inside the
1337 // editor's Init() call.
1339 // Get the DOM document
1340 nsCOMPtr
<Document
> doc
= presShell
->GetDocument();
1341 if (NS_WARN_IF(!doc
)) {
1342 return NS_ERROR_FAILURE
;
1345 // What follows is a bit of a hack. The editor uses the public DOM APIs
1346 // for its content manipulations, and it causes it to fail some security
1347 // checks deep inside when initializing. So we explictly make it clear that
1348 // we're native code.
1349 // Note that any script that's directly trying to access our value
1350 // has to be going through some scriptable object to do that and that
1351 // already does the relevant security checks.
1352 AutoNoJSAPI nojsapi
;
1354 RefPtr
<Element
> rootElement
= GetRootNode();
1355 RefPtr
<nsTextInputSelectionImpl
> selectionController
= mSelCon
;
1356 rv
= newTextEditor
->Init(*doc
, rootElement
, selectionController
,
1357 editorFlags
, defaultValue
);
1358 NS_ENSURE_SUCCESS(rv
, rv
);
1361 // Initialize the controller for the editor
1363 if (!SuppressEventHandlers(presContext
)) {
1364 nsCOMPtr
<nsIControllers
> controllers
;
1365 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
1366 HTMLInputElement
* inputElement
= HTMLInputElement::FromNodeOrNull(content
);
1368 rv
= inputElement
->GetControllers(getter_AddRefs(controllers
));
1370 HTMLTextAreaElement
* textAreaElement
=
1371 HTMLTextAreaElement::FromNodeOrNull(content
);
1373 if (!textAreaElement
) return NS_ERROR_FAILURE
;
1375 rv
= textAreaElement
->GetControllers(getter_AddRefs(controllers
));
1378 NS_ENSURE_SUCCESS(rv
, rv
);
1381 uint32_t numControllers
;
1383 rv
= controllers
->GetControllerCount(&numControllers
);
1384 for (uint32_t i
= 0; i
< numControllers
; i
++) {
1385 nsCOMPtr
<nsIController
> controller
;
1386 rv
= controllers
->GetControllerAt(i
, getter_AddRefs(controller
));
1387 if (NS_SUCCEEDED(rv
) && controller
) {
1388 nsCOMPtr
<nsIControllerContext
> editController
=
1389 do_QueryInterface(controller
);
1390 if (editController
) {
1391 editController
->SetCommandContext(
1392 static_cast<nsIEditor
*>(newTextEditor
));
1397 if (!found
) rv
= NS_ERROR_FAILURE
;
1401 // Initialize the plaintext editor
1402 if (shouldInitializeEditor
) {
1404 newTextEditor
->SetWrapColumn(GetWrapCols());
1407 // Set max text field length
1408 newTextEditor
->SetMaxTextLength(GetMaxLength());
1410 if (nsCOMPtr
<Element
> element
= do_QueryInterface(mTextCtrlElement
)) {
1411 editorFlags
= newTextEditor
->Flags();
1413 // Check if the readonly attribute is set.
1414 if (element
->HasAttr(kNameSpaceID_None
, nsGkAtoms::readonly
))
1415 editorFlags
|= nsIPlaintextEditor::eEditorReadonlyMask
;
1417 // Check if the disabled attribute is set.
1418 // TODO: call IsDisabled() here!
1419 if (element
->HasAttr(kNameSpaceID_None
, nsGkAtoms::disabled
))
1420 editorFlags
|= nsIPlaintextEditor::eEditorDisabledMask
;
1422 // Disable the selection if necessary.
1423 if (newTextEditor
->IsDisabled()) {
1424 mSelCon
->SetDisplaySelection(nsISelectionController::SELECTION_OFF
);
1427 SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1430 if (shouldInitializeEditor
) {
1431 // Hold on to the newly created editor
1432 preDestroyer
.Swap(mTextEditor
);
1435 // If we have a default value, insert it under the div we created
1436 // above, but be sure to use the editor so that '*' characters get
1437 // displayed for password fields, etc. SetValue() will call the
1440 if (!defaultValue
.IsEmpty()) {
1441 rv
= SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1442 if (NS_WARN_IF(NS_FAILED(rv
))) {
1446 // Now call SetValue() which will make the necessary editor calls to set
1447 // the default value. Make sure to turn off undo before setting the default
1448 // value, and turn it back on afterwards. This will make sure we can't undo
1449 // past the default value.
1450 // So, we use eSetValue_Internal flag only that it will turn off undo.
1452 bool success
= SetValue(defaultValue
, eSetValue_Internal
);
1453 NS_ENSURE_TRUE(success
, NS_ERROR_OUT_OF_MEMORY
);
1455 // Now restore the original editor flags.
1456 rv
= SetEditorFlagsIfNecessary(*newTextEditor
, editorFlags
);
1457 if (NS_WARN_IF(NS_FAILED(rv
))) {
1462 if (IsPasswordTextControl()) {
1463 // Disable undo for <input type="password">. Note that we want to do this
1464 // at the very end of InitEditor(), so the calls to EnableUndoRedo() when
1465 // setting the default value don't screw us up. Since changing the
1466 // control type does a reframe, we don't have to worry about dynamic type
1468 DebugOnly
<bool> disabledUndoRedo
= newTextEditor
->DisableUndoRedo();
1469 NS_WARNING_ASSERTION(disabledUndoRedo
,
1470 "Failed to disable undo/redo transaction");
1472 DebugOnly
<bool> enabledUndoRedo
=
1473 newTextEditor
->EnableUndoRedo(nsITextControlElement::DEFAULT_UNDO_CAP
);
1474 NS_WARNING_ASSERTION(enabledUndoRedo
,
1475 "Failed to enable undo/redo transaction");
1478 if (!mEditorInitialized
) {
1479 newTextEditor
->PostCreate();
1481 mEditorInitialized
= true;
1484 if (mTextListener
) {
1485 newTextEditor
->SetTextInputListener(mTextListener
);
1488 // Restore our selection after being bound to a new frame
1489 HTMLInputElement
* number
= GetParentNumberControl(mBoundFrame
);
1490 if (number
? number
->IsSelectionCached() : mSelectionCached
) {
1491 if (mRestoringSelection
) // paranoia
1492 mRestoringSelection
->Revoke();
1493 mRestoringSelection
= new RestoreSelectionState(this, mBoundFrame
);
1494 if (mRestoringSelection
) {
1495 nsContentUtils::AddScriptRunner(mRestoringSelection
);
1499 // The selection cache is no longer going to be valid.
1501 // XXXbz Shouldn't we do this at the point when we're actually about to
1502 // restore the properties or something? As things stand, if UnbindFromFrame
1503 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1504 // selection info, because we will think we don't have it cached and try to
1505 // read it from the selection controller, which will not have it yet.
1507 number
->ClearSelectionCached();
1509 mSelectionCached
= false;
1515 void nsTextEditorState::FinishedRestoringSelection() {
1516 mRestoringSelection
= nullptr;
1519 bool nsTextEditorState::IsSelectionCached() const {
1521 HTMLInputElement
* number
= GetParentNumberControl(mBoundFrame
);
1523 return number
->IsSelectionCached();
1526 return mSelectionCached
;
1529 nsTextEditorState::SelectionProperties
&
1530 nsTextEditorState::GetSelectionProperties() {
1532 HTMLInputElement
* number
= GetParentNumberControl(mBoundFrame
);
1534 return number
->GetSelectionProperties();
1537 return mSelectionProperties
;
1540 void nsTextEditorState::SyncUpSelectionPropertiesBeforeDestruction() {
1542 UnbindFromFrame(mBoundFrame
);
1546 void nsTextEditorState::SetSelectionProperties(
1547 nsTextEditorState::SelectionProperties
& aProps
) {
1549 mBoundFrame
->SetSelectionRange(aProps
.GetStart(), aProps
.GetEnd(),
1550 aProps
.GetDirection());
1552 mSelectionProperties
= aProps
;
1556 void nsTextEditorState::GetSelectionRange(uint32_t* aSelectionStart
,
1557 uint32_t* aSelectionEnd
,
1559 MOZ_ASSERT(aSelectionStart
);
1560 MOZ_ASSERT(aSelectionEnd
);
1561 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1562 "How can we not have a cached selection if we have no selection "
1565 // Note that we may have both IsSelectionCached() _and_
1566 // GetSelectionController() if we haven't initialized our editor yet.
1567 if (IsSelectionCached()) {
1568 const SelectionProperties
& props
= GetSelectionProperties();
1569 *aSelectionStart
= props
.GetStart();
1570 *aSelectionEnd
= props
.GetEnd();
1574 Selection
* sel
= mSelCon
->GetSelection(SelectionType::eNormal
);
1575 if (NS_WARN_IF(!sel
)) {
1576 aRv
.Throw(NS_ERROR_FAILURE
);
1580 mozilla::dom::Element
* root
= GetRootNode();
1581 if (NS_WARN_IF(!root
)) {
1582 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1585 nsContentUtils::GetSelectionInTextControl(sel
, root
, *aSelectionStart
,
1589 nsITextControlFrame::SelectionDirection
1590 nsTextEditorState::GetSelectionDirection(ErrorResult
& aRv
) {
1591 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1592 "How can we not have a cached selection if we have no selection "
1595 // Note that we may have both IsSelectionCached() _and_
1596 // GetSelectionController() if we haven't initialized our editor yet.
1597 if (IsSelectionCached()) {
1598 return GetSelectionProperties().GetDirection();
1601 Selection
* sel
= mSelCon
->GetSelection(SelectionType::eNormal
);
1602 if (NS_WARN_IF(!sel
)) {
1603 aRv
.Throw(NS_ERROR_FAILURE
);
1604 return nsITextControlFrame::eForward
; // Doesn't really matter
1607 nsDirection direction
= sel
->GetDirection();
1608 if (direction
== eDirNext
) {
1609 return nsITextControlFrame::eForward
;
1612 MOZ_ASSERT(direction
== eDirPrevious
);
1613 return nsITextControlFrame::eBackward
;
1616 void nsTextEditorState::SetSelectionRange(
1617 uint32_t aStart
, uint32_t aEnd
,
1618 nsITextControlFrame::SelectionDirection aDirection
, ErrorResult
& aRv
) {
1619 MOZ_ASSERT(IsSelectionCached() || mBoundFrame
,
1620 "How can we have a non-cached selection but no frame?");
1622 if (aStart
> aEnd
) {
1626 bool changed
= false;
1627 nsresult rv
= NS_OK
; // For the ScrollSelectionIntoView() return value.
1628 if (IsSelectionCached()) {
1630 // XXXbz is "false" the right thing to pass here? Hard to tell, given the
1631 // various mismatches between our impl and the spec.
1632 GetValue(value
, false);
1633 uint32_t length
= value
.Length();
1634 if (aStart
> length
) {
1637 if (aEnd
> length
) {
1640 SelectionProperties
& props
= GetSelectionProperties();
1641 changed
= props
.GetStart() != aStart
|| props
.GetEnd() != aEnd
||
1642 props
.GetDirection() != aDirection
;
1643 props
.SetStart(aStart
);
1645 props
.SetDirection(aDirection
);
1647 MOZ_ASSERT(mBoundFrame
, "Our frame should still be valid");
1648 WeakPtr
<nsTextEditorState
> self(this);
1649 aRv
= mBoundFrame
->SetSelectionRange(aStart
, aEnd
, aDirection
);
1650 if (aRv
.Failed() || !self
.get()) {
1654 rv
= mBoundFrame
->ScrollSelectionIntoView();
1656 // Press on to firing the event even if that failed, like our old code did.
1657 // But is that really what we want? Firing the event _and_ throwing from
1658 // here is weird. Maybe we should just ignore ScrollSelectionIntoView
1661 // XXXbz This is preserving our current behavior of firing a "select" event
1662 // on all mutations when we have an editor, but we should really consider
1668 // It sure would be nice if we had an existing Element* or so to work with.
1669 nsCOMPtr
<nsINode
> node
= do_QueryInterface(mTextCtrlElement
);
1670 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
1671 new AsyncEventDispatcher(node
, NS_LITERAL_STRING("select"),
1672 CanBubble::eYes
, ChromeOnlyDispatch::eNo
);
1673 asyncDispatcher
->PostDOMEvent();
1676 if (NS_FAILED(rv
)) {
1681 void nsTextEditorState::SetSelectionStart(const Nullable
<uint32_t>& aStart
,
1684 if (!aStart
.IsNull()) {
1685 start
= aStart
.Value();
1688 uint32_t ignored
, end
;
1689 GetSelectionRange(&ignored
, &end
, aRv
);
1694 nsITextControlFrame::SelectionDirection dir
= GetSelectionDirection(aRv
);
1703 SetSelectionRange(start
, end
, dir
, aRv
);
1706 void nsTextEditorState::SetSelectionEnd(const Nullable
<uint32_t>& aEnd
,
1709 if (!aEnd
.IsNull()) {
1713 uint32_t start
, ignored
;
1714 GetSelectionRange(&start
, &ignored
, aRv
);
1719 nsITextControlFrame::SelectionDirection dir
= GetSelectionDirection(aRv
);
1724 SetSelectionRange(start
, end
, dir
, aRv
);
1727 static void DirectionToName(nsITextControlFrame::SelectionDirection dir
,
1728 nsAString
& aDirection
) {
1729 if (dir
== nsITextControlFrame::eNone
) {
1730 // TODO(mbrodesser): this should be supported, see
1731 // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
1732 NS_WARNING("We don't actually support this... how did we get it?");
1733 aDirection
.AssignLiteral("none");
1734 } else if (dir
== nsITextControlFrame::eForward
) {
1735 aDirection
.AssignLiteral("forward");
1736 } else if (dir
== nsITextControlFrame::eBackward
) {
1737 aDirection
.AssignLiteral("backward");
1739 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
1743 void nsTextEditorState::GetSelectionDirectionString(nsAString
& aDirection
,
1745 nsITextControlFrame::SelectionDirection dir
= GetSelectionDirection(aRv
);
1749 DirectionToName(dir
, aDirection
);
1752 static nsITextControlFrame::SelectionDirection
1753 DirectionStringToSelectionDirection(const nsAString
& aDirection
) {
1754 if (aDirection
.EqualsLiteral("backward")) {
1755 return nsITextControlFrame::eBackward
;
1758 // We don't support directionless selections.
1759 return nsITextControlFrame::eForward
;
1762 void nsTextEditorState::SetSelectionDirection(const nsAString
& aDirection
,
1764 nsITextControlFrame::SelectionDirection dir
=
1765 DirectionStringToSelectionDirection(aDirection
);
1767 if (IsSelectionCached()) {
1768 GetSelectionProperties().SetDirection(dir
);
1772 uint32_t start
, end
;
1773 GetSelectionRange(&start
, &end
, aRv
);
1778 SetSelectionRange(start
, end
, dir
, aRv
);
1781 static nsITextControlFrame::SelectionDirection
1782 DirectionStringToSelectionDirection(const Optional
<nsAString
>& aDirection
) {
1783 if (!aDirection
.WasPassed()) {
1784 // We don't support directionless selections.
1785 return nsITextControlFrame::eForward
;
1788 return DirectionStringToSelectionDirection(aDirection
.Value());
1791 void nsTextEditorState::SetSelectionRange(uint32_t aSelectionStart
,
1792 uint32_t aSelectionEnd
,
1793 const Optional
<nsAString
>& aDirection
,
1795 nsITextControlFrame::SelectionDirection dir
=
1796 DirectionStringToSelectionDirection(aDirection
);
1798 SetSelectionRange(aSelectionStart
, aSelectionEnd
, dir
, aRv
);
1801 void nsTextEditorState::SetRangeText(const nsAString
& aReplacement
,
1803 uint32_t start
, end
;
1804 GetSelectionRange(&start
, &end
, aRv
);
1809 SetRangeText(aReplacement
, start
, end
, SelectionMode::Preserve
, aRv
,
1810 Some(start
), Some(end
));
1813 void nsTextEditorState::SetRangeText(const nsAString
& aReplacement
,
1814 uint32_t aStart
, uint32_t aEnd
,
1815 SelectionMode aSelectMode
,
1817 const Maybe
<uint32_t>& aSelectionStart
,
1818 const Maybe
<uint32_t>& aSelectionEnd
) {
1819 if (aStart
> aEnd
) {
1820 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
1825 mTextCtrlElement
->GetValueFromSetRangeText(value
);
1826 uint32_t inputValueLength
= value
.Length();
1828 if (aStart
> inputValueLength
) {
1829 aStart
= inputValueLength
;
1832 if (aEnd
> inputValueLength
) {
1833 aEnd
= inputValueLength
;
1836 uint32_t selectionStart
, selectionEnd
;
1837 if (!aSelectionStart
) {
1838 MOZ_ASSERT(!aSelectionEnd
);
1839 GetSelectionRange(&selectionStart
, &selectionEnd
, aRv
);
1844 MOZ_ASSERT(aSelectionEnd
);
1845 selectionStart
= *aSelectionStart
;
1846 selectionEnd
= *aSelectionEnd
;
1849 MOZ_ASSERT(aStart
<= aEnd
);
1850 value
.Replace(aStart
, aEnd
- aStart
, aReplacement
);
1851 nsresult rv
= mTextCtrlElement
->SetValueFromSetRangeText(value
);
1852 if (NS_FAILED(rv
)) {
1857 uint32_t newEnd
= aStart
+ aReplacement
.Length();
1858 int32_t delta
= aReplacement
.Length() - (aEnd
- aStart
);
1860 switch (aSelectMode
) {
1861 case mozilla::dom::SelectionMode::Select
: {
1862 selectionStart
= aStart
;
1863 selectionEnd
= newEnd
;
1865 case mozilla::dom::SelectionMode::Start
: {
1866 selectionStart
= selectionEnd
= aStart
;
1868 case mozilla::dom::SelectionMode::End
: {
1869 selectionStart
= selectionEnd
= newEnd
;
1871 case mozilla::dom::SelectionMode::Preserve
: {
1872 if (selectionStart
> aEnd
) {
1873 selectionStart
+= delta
;
1874 } else if (selectionStart
> aStart
) {
1875 selectionStart
= aStart
;
1878 if (selectionEnd
> aEnd
) {
1879 selectionEnd
+= delta
;
1880 } else if (selectionEnd
> aStart
) {
1881 selectionEnd
= newEnd
;
1885 MOZ_CRASH("Unknown mode!");
1888 SetSelectionRange(selectionStart
, selectionEnd
, Optional
<nsAString
>(), aRv
);
1891 HTMLInputElement
* nsTextEditorState::GetParentNumberControl(
1892 nsFrame
* aFrame
) const {
1894 nsIContent
* content
= aFrame
->GetContent();
1895 MOZ_ASSERT(content
);
1896 nsIContent
* parent
= content
->GetParent();
1900 nsIContent
* parentOfParent
= parent
->GetParent();
1901 if (!parentOfParent
) {
1904 HTMLInputElement
* input
= HTMLInputElement::FromNode(parentOfParent
);
1906 // This function might be called during frame reconstruction as a result
1907 // of changing the input control's type from number to something else. In
1908 // that situation, the type of the control has changed, but its frame has
1909 // not been reconstructed yet. So we need to check the type of the input
1910 // control in addition to the type of the frame.
1911 return (input
->ControlType() == NS_FORM_INPUT_NUMBER
) ? input
: nullptr;
1917 void nsTextEditorState::DestroyEditor() {
1918 // notify the editor that we are going away
1919 if (mEditorInitialized
) {
1920 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
1921 textEditor
->PreDestroy(true);
1922 mEditorInitialized
= false;
1926 void nsTextEditorState::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
1927 NS_ENSURE_TRUE_VOID(mBoundFrame
);
1929 // If it was, however, it should be unbounded from the same frame.
1930 MOZ_ASSERT(aFrame
== mBoundFrame
, "Unbinding from the wrong frame");
1931 NS_ENSURE_TRUE_VOID(!aFrame
|| aFrame
== mBoundFrame
);
1933 // We need to start storing the value outside of the editor if we're not
1934 // going to use it anymore, so retrieve it for now.
1936 GetValue(value
, true);
1938 if (mRestoringSelection
) {
1939 mRestoringSelection
->Revoke();
1940 mRestoringSelection
= nullptr;
1943 // Save our selection state if needed.
1944 // Note that GetSelectionRange will attempt to work with our selection
1945 // controller, so we should make sure we do it before we start doing things
1946 // like destroying our editor (if we have one), tearing down the selection
1947 // controller, and so forth.
1948 if (!IsSelectionCached()) {
1949 // Go ahead and cache it now.
1950 uint32_t start
= 0, end
= 0;
1951 GetSelectionRange(&start
, &end
, IgnoreErrors());
1953 nsITextControlFrame::SelectionDirection direction
=
1954 GetSelectionDirection(IgnoreErrors());
1956 SelectionProperties
& props
= GetSelectionProperties();
1957 props
.SetStart(start
);
1959 props
.SetDirection(direction
);
1960 HTMLInputElement
* number
= GetParentNumberControl(aFrame
);
1962 // If we are inside a number control, cache the selection on the
1963 // parent control, because this text editor state will be destroyed
1964 // together with the native anonymous text control.
1965 number
->SetSelectionCached();
1967 mSelectionCached
= true;
1971 // Destroy our editor
1974 // Clean up the controller
1975 if (!SuppressEventHandlers(mBoundFrame
->PresContext())) {
1976 nsCOMPtr
<nsIControllers
> controllers
;
1977 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
1978 HTMLInputElement
* inputElement
= HTMLInputElement::FromNodeOrNull(content
);
1980 inputElement
->GetControllers(getter_AddRefs(controllers
));
1982 HTMLTextAreaElement
* textAreaElement
=
1983 HTMLTextAreaElement::FromNodeOrNull(content
);
1984 if (textAreaElement
) {
1985 textAreaElement
->GetControllers(getter_AddRefs(controllers
));
1990 uint32_t numControllers
;
1991 nsresult rv
= controllers
->GetControllerCount(&numControllers
);
1992 NS_ASSERTION((NS_SUCCEEDED(rv
)),
1993 "bad result in gfx text control destructor");
1994 for (uint32_t i
= 0; i
< numControllers
; i
++) {
1995 nsCOMPtr
<nsIController
> controller
;
1996 rv
= controllers
->GetControllerAt(i
, getter_AddRefs(controller
));
1997 if (NS_SUCCEEDED(rv
) && controller
) {
1998 nsCOMPtr
<nsIControllerContext
> editController
=
1999 do_QueryInterface(controller
);
2000 if (editController
) {
2001 editController
->SetCommandContext(nullptr);
2009 if (mTextListener
) {
2010 mTextListener
->EndListeningToSelectionChange();
2013 mSelCon
->SetScrollableFrame(nullptr);
2017 if (mTextListener
) {
2018 mTextListener
->SetFrame(nullptr);
2020 nsCOMPtr
<EventTarget
> target
= do_QueryInterface(mTextCtrlElement
);
2021 EventListenerManager
* manager
= target
->GetExistingListenerManager();
2023 manager
->RemoveEventListenerByType(mTextListener
,
2024 NS_LITERAL_STRING("keydown"),
2025 TrustedEventsAtSystemGroupBubble());
2026 manager
->RemoveEventListenerByType(mTextListener
,
2027 NS_LITERAL_STRING("keypress"),
2028 TrustedEventsAtSystemGroupBubble());
2029 manager
->RemoveEventListenerByType(mTextListener
,
2030 NS_LITERAL_STRING("keyup"),
2031 TrustedEventsAtSystemGroupBubble());
2034 mTextListener
= nullptr;
2037 mBoundFrame
= nullptr;
2039 // Now that we don't have a frame any more, store the value in the text
2040 // buffer. The only case where we don't do this is if a value transfer is in
2042 if (!mValueTransferInProgress
) {
2043 bool success
= SetValue(value
, eSetValue_Internal
);
2044 // TODO Find something better to do if this fails...
2045 NS_ENSURE_TRUE_VOID(success
);
2049 int32_t nsTextEditorState::GetMaxLength() {
2050 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
2051 nsGenericHTMLElement
* element
= nsGenericHTMLElement::FromNodeOrNull(content
);
2052 if (NS_WARN_IF(!element
)) {
2056 const nsAttrValue
* attr
= element
->GetParsedAttr(nsGkAtoms::maxlength
);
2057 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
2058 return attr
->GetIntegerValue();
2064 void nsTextEditorState::GetValue(nsAString
& aValue
, bool aIgnoreWrap
) const {
2065 // While SetValue() is being called and requesting to commit composition to
2066 // IME, GetValue() may be called for appending text or something. Then, we
2067 // need to return the latest aValue of SetValue() since the value hasn't
2068 // been set to the editor yet.
2069 if (mIsCommittingComposition
) {
2070 aValue
= mValueBeingSet
;
2074 if (mTextEditor
&& mBoundFrame
&&
2075 (mEditorInitialized
|| !IsSingleLineTextControl())) {
2076 if (aIgnoreWrap
&& !mBoundFrame
->CachedValue().IsVoid()) {
2077 aValue
= mBoundFrame
->CachedValue();
2081 aValue
.Truncate(); // initialize out param
2083 uint32_t flags
= (nsIDocumentEncoder::OutputLFLineBreak
|
2084 nsIDocumentEncoder::OutputPreformatted
|
2085 nsIDocumentEncoder::OutputPersistNBSP
|
2086 nsIDocumentEncoder::OutputBodyOnly
);
2088 nsITextControlElement::nsHTMLTextWrap wrapProp
;
2089 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
2091 nsITextControlElement::GetWrapPropertyEnum(content
, wrapProp
) &&
2092 wrapProp
== nsITextControlElement::eHTMLTextWrap_Hard
) {
2093 flags
|= nsIDocumentEncoder::OutputWrap
;
2097 // What follows is a bit of a hack. The problem is that we could be in
2098 // this method because we're being destroyed for whatever reason while
2099 // script is executing. If that happens, editor will run with the
2100 // privileges of the executing script, which means it may not be able to
2101 // access its own DOM nodes! Let's try to deal with that by pushing a null
2102 // JSContext on the JSContext stack to make it clear that we're native
2103 // code. Note that any script that's directly trying to access our value
2104 // has to be going through some scriptable object to do that and that
2105 // already does the relevant security checks.
2106 // XXXbz if we could just get the textContent of our anonymous content (eg
2107 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2109 { /* Scope for AutoNoJSAPI. */
2110 AutoNoJSAPI nojsapi
;
2112 DebugOnly
<nsresult
> rv
= mTextEditor
->ComputeTextValue(flags
, aValue
);
2113 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to get value");
2115 // Only when the result doesn't include line breaks caused by hard-wrap,
2116 // mCacheValue should cache the value.
2117 if (!(flags
& nsIDocumentEncoder::OutputWrap
)) {
2118 mBoundFrame
->CacheValue(aValue
);
2120 mBoundFrame
->ClearCachedValue();
2123 if (!mTextCtrlElement
->ValueChanged() || !mValue
) {
2124 mTextCtrlElement
->GetDefaultValueFromContent(aValue
);
2133 // @param aFlags nsTextEditorState::SetValueFlags
2134 bool AreFlagsNotDemandingContradictingMovements(uint32_t aFlags
) {
2138 eSetValue_MoveCursorToBeginSetSelectionDirectionForward
) &&
2139 !!(aFlags
& nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged
));
2141 } // anonymous namespace
2144 bool nsTextEditorState::SetValue(const nsAString
& aValue
,
2145 const nsAString
* aOldValue
, uint32_t aFlags
) {
2146 nsAutoString
newValue(aValue
);
2148 // While mIsCommittingComposition is true (that means that some event
2149 // handlers which are fired during committing composition are the caller of
2150 // this method), GetValue() uses mValueBeingSet for its result because the
2151 // first calls of this methods hasn't set the value yet. So, when it's true,
2152 // we need to modify mValueBeingSet. In this case, we will back to the first
2153 // call of this method, then, mValueBeingSet will be truncated when
2154 // mIsCommittingComposition is set false. See below.
2155 if (mIsCommittingComposition
) {
2156 mValueBeingSet
= aValue
;
2157 // GetValue doesn't return current text frame's content during committing.
2158 // So we cannot trust this old value
2159 aOldValue
= nullptr;
2162 // Note that if this may be called during reframe of the editor. In such
2163 // case, we shouldn't commit composition. Therefore, when this is called
2164 // for internal processing, we shouldn't commit the composition.
2165 if (aFlags
& (eSetValue_BySetUserInput
| eSetValue_ByContent
)) {
2166 if (EditorHasComposition()) {
2167 // When this is called recursively, there shouldn't be composition.
2168 if (NS_WARN_IF(mIsCommittingComposition
)) {
2169 // Don't request to commit composition again. But if it occurs,
2170 // we should skip to set the new value to the editor here. It should
2171 // be set later with the updated mValueBeingSet.
2174 if (NS_WARN_IF(!mBoundFrame
)) {
2175 // We're not sure if this case is possible.
2177 // If setting value won't change current value, we shouldn't commit
2178 // composition for compatibility with the other browsers.
2179 nsAutoString currentValue
;
2182 mBoundFrame
->GetText(currentValue
);
2183 MOZ_ASSERT(currentValue
.Equals(*aOldValue
));
2185 currentValue
.Assign(*aOldValue
);
2187 mBoundFrame
->GetText(currentValue
);
2189 if (newValue
== currentValue
) {
2190 // Note that in this case, we shouldn't fire any events with setting
2191 // value because event handlers may try to set value recursively but
2192 // we cannot commit composition at that time due to unsafe to run
2193 // script (see below).
2196 // IME might commit composition, then change value, so we cannot
2197 // trust old value from parameter.
2198 aOldValue
= nullptr;
2200 // If there is composition, need to commit composition first because
2201 // other browsers do that.
2202 // NOTE: We don't need to block nested calls of this because input nor
2203 // other events won't be fired by setting values and script blocker
2204 // is used during setting the value to the editor. IE also allows
2205 // to set the editor value on the input event which is caused by
2206 // forcibly committing composition.
2207 if (nsContentUtils::IsSafeToRunScript()) {
2208 WeakPtr
<nsTextEditorState
> self(this);
2209 // WARNING: During this call, compositionupdate, compositionend, input
2210 // events will be fired. Therefore, everything can occur. E.g., the
2211 // document may be unloaded.
2212 mValueBeingSet
= aValue
;
2213 mIsCommittingComposition
= true;
2214 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
2215 nsresult rv
= textEditor
->CommitComposition();
2219 mIsCommittingComposition
= false;
2220 // If this is called recursively during committing composition and
2221 // some of them may be skipped above. Therefore, we need to set
2222 // value to the editor with the aValue of the latest call.
2223 newValue
= mValueBeingSet
;
2224 // When mIsCommittingComposition is false, mValueBeingSet won't be
2225 // used. Therefore, let's clear it.
2226 mValueBeingSet
.Truncate();
2227 if (NS_FAILED(rv
)) {
2228 NS_WARNING("nsTextEditorState failed to commit composition");
2233 "SetValue() is called when there is composition but "
2234 "it's not safe to request to commit the composition");
2239 // \r is an illegal character in the dom, but people use them,
2240 // so convert windows and mac platform linebreaks to \n:
2241 if (!nsContentUtils::PlatformToDOMLineBreaks(newValue
, fallible
)) {
2245 // mTextCtrlElement may be cleared when we dispatch an event so that
2246 // we should keep grabbing it with local variable.
2247 nsCOMPtr
<nsITextControlElement
> textControlElement(mTextCtrlElement
);
2249 if (mTextEditor
&& mBoundFrame
) {
2250 // The InsertText call below might flush pending notifications, which
2251 // could lead into a scheduled PrepareEditor to be called. That will
2252 // lead to crashes (or worse) because we'd be initializing the editor
2253 // before InsertText returns. This script blocker makes sure that
2254 // PrepareEditor cannot be called prematurely.
2255 nsAutoScriptBlocker scriptBlocker
;
2258 if (IsSingleLineTextControl()) {
2259 NS_ASSERTION(mEditorInitialized
|| mInitializing
,
2260 "We should never try to use the editor if we're not "
2261 "initialized unless we're being initialized");
2265 nsAutoString currentValue
;
2268 mBoundFrame
->GetText(currentValue
);
2269 MOZ_ASSERT(currentValue
.Equals(*aOldValue
));
2271 currentValue
.Assign(*aOldValue
);
2273 mBoundFrame
->GetText(currentValue
);
2276 AutoWeakFrame
weakFrame(mBoundFrame
);
2278 // this is necessary to avoid infinite recursion
2279 if (!currentValue
.Equals(newValue
)) {
2280 RefPtr
<TextEditor
> textEditor
= mTextEditor
;
2281 AutoInputEventSuppresser
suppressInputEventDispatching(textEditor
);
2283 nsCOMPtr
<Document
> document
= textEditor
->GetDocument();
2284 if (NS_WARN_IF(!document
)) {
2288 // Time to mess with our security context... See comments in GetValue()
2289 // for why this is needed. Note that we have to do this up here, because
2290 // otherwise SelectAll() will fail.
2292 AutoNoJSAPI nojsapi
;
2294 // FYI: It's safe to use raw pointer for selection here because
2295 // SelectionBatcher will grab it with RefPtr.
2296 Selection
* selection
= mSelCon
->GetSelection(SelectionType::eNormal
);
2297 SelectionBatcher
selectionBatcher(selection
);
2299 if (NS_WARN_IF(!weakFrame
.IsAlive())) {
2303 // get the flags, remove readonly, disabled and max-length,
2304 // set the value, restore flags
2306 AutoRestoreEditorState
restoreState(textEditor
);
2308 mTextListener
->SettingValue(true);
2309 bool notifyValueChanged
= !!(aFlags
& eSetValue_Notify
);
2310 mTextListener
->SetValueChanged(notifyValueChanged
);
2312 if (aFlags
& eSetValue_BySetUserInput
) {
2313 // If the caller inserts text as part of user input, for example,
2314 // autocomplete, we need to replace the text as "insert string"
2315 // because undo should cancel only this operation (i.e., previous
2316 // transactions typed by user shouldn't be merged with this).
2317 // In this case, we need to dispatch "input" event because
2318 // web apps may need to know the user's operation.
2319 RefPtr
<nsRange
> range
; // See bug 1506439
2320 // In this case, we need to dispatch "beforeinput" events since
2321 // we're emulating the user's input. Passing nullptr as
2322 // nsIPrincipal means that that may be user's input. So, let's
2324 DebugOnly
<nsresult
> rv
=
2325 textEditor
->ReplaceTextAsAction(newValue
, range
, nullptr);
2326 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2327 "Failed to set the new value");
2328 } else if (aFlags
& eSetValue_ForXUL
) {
2329 // When setting value of XUL <textbox>, we shouldn't dispatch
2331 suppressInputEventDispatching
.Init();
2333 // On XUL <textbox> element, we need to preserve existing undo
2335 // XXX Do we really need to do such complicated optimization?
2336 // This was landed for web pages which set <textarea> value
2337 // per line (bug 518122). For example:
2339 // textarea.value += oneLineText + "\n";
2341 // However, this path won't be used in web content anymore.
2342 nsCOMPtr
<nsISelectionController
> kungFuDeathGrip
= mSelCon
.get();
2343 uint32_t currentLength
= currentValue
.Length();
2344 uint32_t newlength
= newValue
.Length();
2345 if (!currentLength
|| !StringBeginsWith(newValue
, currentValue
)) {
2346 // Replace the whole text.
2348 kungFuDeathGrip
->SelectAll();
2350 // Collapse selection to the end so that we can append data.
2351 mBoundFrame
->SelectAllOrCollapseToEndOfText(false);
2353 const nsAString
& insertValue
=
2354 StringTail(newValue
, newlength
- currentLength
);
2356 if (insertValue
.IsEmpty()) {
2357 // In this case, we makes the editor stop dispatching "input"
2358 // event so that passing nullptr as nsIPrincipal is safe for
2360 DebugOnly
<nsresult
> rv
= textEditor
->DeleteSelectionAsAction(
2361 nsIEditor::eNone
, nsIEditor::eStrip
, nullptr);
2362 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2363 "Failed to remove the text");
2365 // In this case, we makes the editor stop dispatching "input"
2366 // event so that passing nullptr as nsIPrincipal is safe for
2368 DebugOnly
<nsresult
> rv
=
2369 textEditor
->InsertTextAsAction(insertValue
, nullptr);
2370 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2371 "Failed to insert the new value");
2374 // When setting value of <input>, we shouldn't dispatch "input"
2376 suppressInputEventDispatching
.Init();
2378 // On <input> or <textarea>, we shouldn't preserve existing undo
2379 // transactions because other browsers do not preserve them too
2380 // and not preserving transactions makes setting value faster.
2381 AutoDisableUndo
disableUndo(textEditor
);
2383 // Since we don't use undo transaction, we don't need to store
2384 // selection state. SetText will set selection to tail.
2385 // Note that textEditor will collapse selection to the end.
2386 // Therefore, it's safe to use RemoveAllRangesTemporarily() here.
2387 selection
->RemoveAllRangesTemporarily();
2390 // In this case, we makes the editor stop dispatching "input" event
2391 // so that passing nullptr as nsIPrincipal is safe for now.
2392 textEditor
->SetTextAsAction(newValue
, nullptr);
2394 // Call the listener's HandleValueChanged() callback manually, since
2395 // we don't use the transaction manager in this path and it could be
2396 // that the editor would bypass calling the listener for that
2398 mTextListener
->HandleValueChanged();
2401 mTextListener
->SetValueChanged(true);
2402 mTextListener
->SettingValue(false);
2404 if (!notifyValueChanged
) {
2405 // Listener doesn't update frame, but it is required for placeholder
2406 ValueWasChanged(true);
2410 if (!weakFrame
.IsAlive()) {
2411 // If the frame was destroyed because of a flush somewhere inside
2412 // InsertText, mBoundFrame here will be false. But it's also possible
2413 // for the frame to go away because of another reason (such as
2414 // deleting the existing selection -- see bug 574558), in which case
2415 // we don't need to reset the value here.
2417 return SetValue(newValue
, aFlags
& eSetValue_Notify
);
2422 // The new value never includes line breaks caused by hard-wrap.
2423 // So, mCachedValue can always cache the new value.
2424 if (!mBoundFrame
->CacheValue(newValue
, fallible
)) {
2434 // We can't just early-return here if mValue->Equals(newValue), because
2435 // ValueWasChanged and OnValueChanged below still need to be called.
2436 if (!mValue
->Equals(newValue
) ||
2437 !StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
2438 if (!mValue
->Assign(newValue
, fallible
)) {
2442 // Since we have no editor we presumably have cached selection state.
2443 if (IsSelectionCached()) {
2444 MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(aFlags
));
2446 SelectionProperties
& props
= GetSelectionProperties();
2447 if (aFlags
& eSetValue_MoveCursorToEndIfValueChanged
) {
2448 props
.SetStart(newValue
.Length());
2449 props
.SetEnd(newValue
.Length());
2450 props
.SetDirection(nsITextControlFrame::eForward
);
2452 eSetValue_MoveCursorToBeginSetSelectionDirectionForward
) {
2455 props
.SetDirection(nsITextControlFrame::eForward
);
2457 // Make sure our cached selection position is not outside the new
2459 props
.SetStart(std::min(props
.GetStart(), newValue
.Length()));
2460 props
.SetEnd(std::min(props
.GetEnd(), newValue
.Length()));
2464 // Update the frame display if needed
2466 mBoundFrame
->UpdateValueDisplay(true);
2469 // If this is called as part of user input, we need to dispatch "input"
2470 // event with "insertReplacementText" since web apps may want to know
2471 // the user operation which changes editor value with a built-in function
2472 // like autocomplete, password manager, session restore, etc.
2473 if (aFlags
& eSetValue_BySetUserInput
) {
2474 nsCOMPtr
<Element
> element
= do_QueryInterface(textControlElement
);
2475 MOZ_ASSERT(element
);
2476 MOZ_ASSERT(!newValue
.IsVoid());
2477 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(
2478 element
, EditorInputType::eInsertReplacementText
, nullptr,
2479 nsContentUtils::InputEventOptions(newValue
));
2480 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2481 "Failed to dispatch input event");
2484 // Even if our value is not actually changing, apparently we need to mark
2485 // our SelectionProperties dirty to make accessibility tests happy.
2486 // Probably because they depend on the SetSelectionRange() call we make on
2487 // our frame in RestoreSelectionState, but I have no idea why they do.
2488 if (IsSelectionCached()) {
2489 SelectionProperties
& props
= GetSelectionProperties();
2494 // If we've reached the point where the root node has been created, we
2495 // can assume that it's safe to notify.
2496 ValueWasChanged(!!mBoundFrame
);
2499 // TODO(emilio): It seems wrong to pass ValueChangeKind::Script if
2500 // BySetUserInput is in aFlags.
2501 auto changeKind
= (aFlags
& eSetValue_Internal
) ? ValueChangeKind::Internal
2502 : ValueChangeKind::Script
;
2504 // XXX Should we stop notifying "value changed" if mTextCtrlElement has
2506 textControlElement
->OnValueChanged(/* aNotify = */ !!mBoundFrame
, changeKind
);
2510 bool nsTextEditorState::HasNonEmptyValue() {
2511 if (mTextEditor
&& mBoundFrame
&& mEditorInitialized
&&
2512 !mIsCommittingComposition
) {
2514 nsresult rv
= mTextEditor
->IsEmpty(&empty
);
2515 if (NS_SUCCEEDED(rv
)) {
2521 GetValue(value
, true);
2522 return !value
.IsEmpty();
2525 void nsTextEditorState::InitializeKeyboardEventListeners() {
2526 // register key listeners
2527 nsCOMPtr
<EventTarget
> target
= do_QueryInterface(mTextCtrlElement
);
2528 EventListenerManager
* manager
= target
->GetOrCreateListenerManager();
2530 manager
->AddEventListenerByType(mTextListener
, NS_LITERAL_STRING("keydown"),
2531 TrustedEventsAtSystemGroupBubble());
2532 manager
->AddEventListenerByType(mTextListener
,
2533 NS_LITERAL_STRING("keypress"),
2534 TrustedEventsAtSystemGroupBubble());
2535 manager
->AddEventListenerByType(mTextListener
, NS_LITERAL_STRING("keyup"),
2536 TrustedEventsAtSystemGroupBubble());
2539 mSelCon
->SetScrollableFrame(
2540 do_QueryFrame(mBoundFrame
->PrincipalChildList().FirstChild()));
2543 void nsTextEditorState::ValueWasChanged(bool aNotify
) {
2544 UpdateOverlayTextVisibility(aNotify
);
2547 void nsTextEditorState::SetPreviewText(const nsAString
& aValue
, bool aNotify
) {
2548 // If we don't have a preview div, there's nothing to do.
2549 Element
* previewDiv
= GetPreviewNode();
2550 if (!previewDiv
) return;
2552 nsAutoString
previewValue(aValue
);
2554 nsContentUtils::RemoveNewlines(previewValue
);
2555 MOZ_ASSERT(previewDiv
->GetFirstChild(), "preview div has no child");
2556 previewDiv
->GetFirstChild()->AsText()->SetText(previewValue
, aNotify
);
2558 UpdateOverlayTextVisibility(aNotify
);
2561 void nsTextEditorState::GetPreviewText(nsAString
& aValue
) {
2562 // If we don't have a preview div, there's nothing to do.
2563 Element
* previewDiv
= GetPreviewNode();
2564 if (!previewDiv
) return;
2566 MOZ_ASSERT(previewDiv
->GetFirstChild(), "preview div has no child");
2567 const nsTextFragment
* text
= previewDiv
->GetFirstChild()->GetText();
2570 text
->AppendTo(aValue
);
2573 void nsTextEditorState::UpdateOverlayTextVisibility(bool aNotify
) {
2574 nsAutoString value
, previewValue
;
2575 bool valueIsEmpty
= !HasNonEmptyValue();
2576 GetPreviewText(previewValue
);
2578 mPreviewVisibility
= valueIsEmpty
&& !previewValue
.IsEmpty();
2579 mPlaceholderVisibility
= valueIsEmpty
&& previewValue
.IsEmpty();
2581 if (mPlaceholderVisibility
&& !StaticPrefs::dom_placeholder_show_on_focus()) {
2582 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
2583 mPlaceholderVisibility
= !nsContentUtils::IsFocusedContent(content
);
2586 if (mBoundFrame
&& aNotify
) {
2587 mBoundFrame
->InvalidateFrame();
2591 void nsTextEditorState::HideSelectionIfBlurred() {
2592 MOZ_ASSERT(mSelCon
, "Should have a selection controller if we have a frame!");
2593 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mTextCtrlElement
);
2594 if (!nsContentUtils::IsFocusedContent(content
)) {
2595 mSelCon
->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN
);
2599 bool nsTextEditorState::EditorHasComposition() {
2600 return mTextEditor
&& mTextEditor
->IsIMEComposing();