Bug 1494333 - index crons just like artifacts r=Callek
[gecko.git] / dom / html / nsTextEditorState.cpp
blobde603b8e102638eabf574234b539d7a94a80fba4
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"
10 #include "nsCOMPtr.h"
11 #include "nsIPresShell.h"
12 #include "nsView.h"
13 #include "nsCaret.h"
14 #include "nsLayoutCID.h"
15 #include "nsITextControlFrame.h"
16 #include "nsContentCreatorFunctions.h"
17 #include "nsTextControlFrame.h"
18 #include "nsIControllers.h"
19 #include "nsITransactionManager.h"
20 #include "nsIControllerContext.h"
21 #include "nsAttrValue.h"
22 #include "nsAttrValueInlines.h"
23 #include "nsGenericHTMLElement.h"
24 #include "nsIDOMEventListener.h"
25 #include "nsIEditorObserver.h"
26 #include "nsIWidget.h"
27 #include "nsIDocumentEncoder.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsServiceManagerUtils.h"
30 #include "mozilla/dom/Selection.h"
31 #include "mozilla/TextEditRules.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 "mozilla/AutoRestore.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 "nsNumberControlFrame.h"
45 #include "nsFrameSelection.h"
46 #include "mozilla/ErrorResult.h"
47 #include "mozilla/Telemetry.h"
49 using namespace mozilla;
50 using namespace mozilla::dom;
52 inline nsresult
53 SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
55 if (aEditorBase.Flags() == aFlags) {
56 return NS_OK;
58 return aEditorBase.SetFlags(aFlags);
61 class MOZ_STACK_CLASS ValueSetter
63 public:
64 explicit ValueSetter(TextEditor* aTextEditor)
65 : mTextEditor(aTextEditor)
66 // To protect against a reentrant call to SetValue, we check whether
67 // another SetValue is already happening for this editor. If it is,
68 // we must wait until we unwind to re-enable oninput events.
69 , mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent())
71 MOZ_ASSERT(aTextEditor);
73 ~ValueSetter()
75 mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
77 void Init()
79 mTextEditor->SuppressDispatchingInputEvent(true);
82 private:
83 RefPtr<TextEditor> mTextEditor;
84 bool mOuterTransaction;
87 class RestoreSelectionState : public Runnable {
88 public:
89 RestoreSelectionState(nsTextEditorState* aState, nsTextControlFrame* aFrame)
90 : mozilla::Runnable("RestoreSelectionState")
91 , mFrame(aFrame)
92 , mTextEditorState(aState)
96 NS_IMETHOD Run() override {
97 if (!mTextEditorState) {
98 return NS_OK;
101 AutoHideSelectionChanges hideSelectionChanges
102 (mFrame->GetConstFrameSelection());
104 if (mFrame) {
105 // SetSelectionRange leads to Selection::AddRange which flushes Layout -
106 // need to block script to avoid nested PrepareEditor calls (bug 642800).
107 nsAutoScriptBlocker scriptBlocker;
108 nsTextEditorState::SelectionProperties& properties =
109 mTextEditorState->GetSelectionProperties();
110 if (properties.IsDirty()) {
111 mFrame->SetSelectionRange(properties.GetStart(),
112 properties.GetEnd(),
113 properties.GetDirection());
115 if (!mTextEditorState->mSelectionRestoreEagerInit) {
116 mTextEditorState->HideSelectionIfBlurred();
118 mTextEditorState->mSelectionRestoreEagerInit = false;
121 if (mTextEditorState) {
122 mTextEditorState->FinishedRestoringSelection();
124 return NS_OK;
127 // Let the text editor tell us we're no longer relevant - avoids use of AutoWeakFrame
128 void Revoke() {
129 mFrame = nullptr;
130 mTextEditorState = nullptr;
133 private:
134 nsTextControlFrame* mFrame;
135 nsTextEditorState* mTextEditorState;
138 class MOZ_RAII AutoRestoreEditorState final
140 public:
141 explicit AutoRestoreEditorState(TextEditor* aTextEditor
142 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
143 : mTextEditor(aTextEditor)
144 , mSavedFlags(mTextEditor->Flags())
145 , mSavedMaxLength(mTextEditor->MaxTextLength())
147 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
148 MOZ_ASSERT(mTextEditor);
150 // EditorBase::SetFlags() is a virtual method. Even though it does nothing
151 // if new flags and current flags are same, the calling cost causes
152 // appearing the method in profile. So, this class should check if it's
153 // necessary to call.
154 uint32_t flags = mSavedFlags;
155 flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
156 flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
157 flags |= nsIPlaintextEditor::eEditorDontEchoPassword;
158 if (mSavedFlags != flags) {
159 mTextEditor->SetFlags(flags);
161 mTextEditor->SetMaxTextLength(-1);
164 ~AutoRestoreEditorState()
166 mTextEditor->SetMaxTextLength(mSavedMaxLength);
167 SetEditorFlagsIfNecessary(*mTextEditor, mSavedFlags);
170 private:
171 TextEditor* mTextEditor;
172 uint32_t mSavedFlags;
173 int32_t mSavedMaxLength;
174 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
177 class MOZ_RAII AutoDisableUndo final
179 public:
180 explicit AutoDisableUndo(TextEditor* aTextEditor
181 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
182 : mTextEditor(aTextEditor)
183 , mNumberOfMaximumTransactions(0)
185 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
186 MOZ_ASSERT(mTextEditor);
188 mNumberOfMaximumTransactions =
189 mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
190 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
191 NS_WARNING_ASSERTION(disabledUndoRedo,
192 "Failed to disable undo/redo transactions");
195 ~AutoDisableUndo()
197 // Don't change enable/disable of undo/redo if it's enabled after
198 // it's disabled by the constructor because we shouldn't change
199 // the maximum undo/redo count to the old value.
200 if (mTextEditor->IsUndoRedoEnabled()) {
201 return;
203 // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
204 // than 0. Only when it's 0, it was disabled.
205 if (mNumberOfMaximumTransactions) {
206 DebugOnly<bool> enabledUndoRedo =
207 mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
208 NS_WARNING_ASSERTION(enabledUndoRedo,
209 "Failed to enable undo/redo transactions");
210 } else {
211 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
212 NS_WARNING_ASSERTION(disabledUndoRedo,
213 "Failed to disable undo/redo transactions");
217 private:
218 TextEditor* mTextEditor;
219 int32_t mNumberOfMaximumTransactions;
220 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
223 /*static*/
224 bool
225 nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent,
226 nsITextControlElement::nsHTMLTextWrap& aWrapProp)
228 // soft is the default; "physical" defaults to soft as well because all other
229 // browsers treat it that way and there is no real reason to maintain physical
230 // and virtual as separate entities if no one else does. Only hard and off
231 // do anything different.
232 aWrapProp = eHTMLTextWrap_Soft; // the default
234 nsAutoString wrap;
235 if (aContent->IsHTMLElement()) {
236 static Element::AttrValuesArray strings[] =
237 {nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
239 switch (aContent->AsElement()->FindAttrValueIn(kNameSpaceID_None,
240 nsGkAtoms::wrap, strings,
241 eIgnoreCase)) {
242 case 0: aWrapProp = eHTMLTextWrap_Hard; break;
243 case 1: aWrapProp = eHTMLTextWrap_Off; break;
246 return true;
249 return false;
252 /*static*/
253 already_AddRefed<nsITextControlElement>
254 nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost)
256 if (!aHost) {
257 return nullptr;
260 nsCOMPtr<nsITextControlElement> parent =
261 do_QueryInterface(aHost->GetParent());
263 return parent.forget();
266 static bool
267 SuppressEventHandlers(nsPresContext* aPresContext)
269 bool suppressHandlers = false;
271 if (aPresContext)
273 // Right now we only suppress event handlers and controller manipulation
274 // when in a print preview or print context!
276 // In the current implementation, we only paginate when
277 // printing or in print preview.
279 suppressHandlers = aPresContext->IsPaginated();
282 return suppressHandlers;
285 class nsAnonDivObserver final : public nsStubMutationObserver
287 public:
288 explicit nsAnonDivObserver(nsTextEditorState* aTextEditorState)
289 : mTextEditorState(aTextEditorState) {}
290 NS_DECL_ISUPPORTS
291 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
292 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
293 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
294 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
296 private:
297 ~nsAnonDivObserver() {}
298 nsTextEditorState* mTextEditorState;
301 class nsTextInputSelectionImpl final : public nsSupportsWeakReference
302 , public nsISelectionController
304 ~nsTextInputSelectionImpl(){}
306 public:
307 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
308 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController)
310 nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter);
312 void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame);
313 nsFrameSelection* GetConstFrameSelection()
314 { return mFrameSelection; }
315 // Will return null if !mFrameSelection.
316 Selection* GetSelection(SelectionType aSelectionType);
318 //NSISELECTIONCONTROLLER INTERFACES
319 NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
320 NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
321 NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
322 NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override;
323 NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
324 Selection** aSelection) override;
325 Selection* GetSelection(RawSelectionType aRawSelectionType) override;
326 NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
327 int16_t aRegion, int16_t aFlags) override;
328 NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
329 nsresult RepaintSelection(nsPresContext* aPresContext,
330 SelectionType aSelectionType);
331 NS_IMETHOD SetCaretEnabled(bool enabled) override;
332 NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
333 NS_IMETHOD GetCaretEnabled(bool* _retval) override;
334 NS_IMETHOD GetCaretVisible(bool* _retval) override;
335 NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
336 NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override;
337 NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
338 NS_IMETHOD CharacterExtendForDelete() override;
339 NS_IMETHOD CharacterExtendForBackspace() override;
340 NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
341 NS_IMETHOD WordExtendForDelete(bool aForward) override;
342 NS_IMETHOD LineMove(bool aForward, bool aExtend) override;
343 NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
344 NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
345 NS_IMETHOD CompleteScroll(bool aForward) override;
346 NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
347 NS_IMETHOD ScrollPage(bool aForward) override;
348 NS_IMETHOD ScrollLine(bool aForward) override;
349 NS_IMETHOD ScrollCharacter(bool aRight) override;
350 NS_IMETHOD SelectAll(void) override;
351 NS_IMETHOD CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override;
352 virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override;
354 private:
355 RefPtr<nsFrameSelection> mFrameSelection;
356 nsCOMPtr<nsIContent> mLimiter;
357 nsIScrollableFrame *mScrollFrame;
358 nsWeakPtr mPresShellWeak;
361 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl)
362 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl)
363 NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl)
364 NS_INTERFACE_TABLE(nsTextInputSelectionImpl,
365 nsISelectionController,
366 nsISelectionDisplay,
367 nsISupportsWeakReference)
368 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl)
369 NS_INTERFACE_MAP_END
371 NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl, mFrameSelection, mLimiter)
374 // BEGIN nsTextInputSelectionImpl
376 nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel,
377 nsIPresShell *aShell,
378 nsIContent *aLimiter)
379 : mScrollFrame(nullptr)
381 if (aSel && aShell)
383 mFrameSelection = aSel;//we are the owner now!
384 mLimiter = aLimiter;
385 bool accessibleCaretEnabled =
386 PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
387 mFrameSelection->Init(aShell, mLimiter, accessibleCaretEnabled);
388 mPresShellWeak = do_GetWeakReference(aShell);
392 void
393 nsTextInputSelectionImpl::SetScrollableFrame(nsIScrollableFrame *aScrollableFrame)
395 mScrollFrame = aScrollableFrame;
396 if (!mScrollFrame && mFrameSelection) {
397 mFrameSelection->DisconnectFromPresShell();
398 mFrameSelection = nullptr;
402 Selection*
403 nsTextInputSelectionImpl::GetSelection(SelectionType aSelectionType)
405 if (!mFrameSelection) {
406 return nullptr;
409 return mFrameSelection->GetSelection(aSelectionType);
412 NS_IMETHODIMP
413 nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle)
415 if (!mFrameSelection)
416 return NS_ERROR_NULL_POINTER;
418 mFrameSelection->SetDisplaySelection(aToggle);
419 return NS_OK;
422 NS_IMETHODIMP
423 nsTextInputSelectionImpl::GetDisplaySelection(int16_t *aToggle)
425 if (!mFrameSelection)
426 return NS_ERROR_NULL_POINTER;
428 *aToggle = mFrameSelection->GetDisplaySelection();
429 return NS_OK;
432 NS_IMETHODIMP
433 nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle)
435 return NS_OK;//stub this out. not used in input
438 NS_IMETHODIMP
439 nsTextInputSelectionImpl::GetSelectionFlags(int16_t *aOutEnable)
441 *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
442 return NS_OK;
445 NS_IMETHODIMP
446 nsTextInputSelectionImpl::GetSelectionFromScript(RawSelectionType aRawSelectionType,
447 Selection** aSelection)
449 if (!mFrameSelection)
450 return NS_ERROR_NULL_POINTER;
452 *aSelection =
453 mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
455 // GetSelection() fails only when aRawSelectionType is invalid value.
456 if (!(*aSelection)) {
457 return NS_ERROR_INVALID_ARG;
460 NS_ADDREF(*aSelection);
461 return NS_OK;
464 Selection*
465 nsTextInputSelectionImpl::GetSelection(RawSelectionType aRawSelectionType)
467 return GetSelection(ToSelectionType(aRawSelectionType));
470 NS_IMETHODIMP
471 nsTextInputSelectionImpl::ScrollSelectionIntoView(
472 RawSelectionType aRawSelectionType,
473 int16_t aRegion,
474 int16_t aFlags)
476 if (!mFrameSelection)
477 return NS_ERROR_FAILURE;
479 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
480 return frameSelection->ScrollSelectionIntoView(
481 ToSelectionType(aRawSelectionType),
482 aRegion, aFlags);
485 NS_IMETHODIMP
486 nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType)
488 if (!mFrameSelection)
489 return NS_ERROR_FAILURE;
491 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
492 return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
495 nsresult
496 nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext,
497 SelectionType aSelectionType)
499 if (!mFrameSelection)
500 return NS_ERROR_FAILURE;
502 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
503 return frameSelection->RepaintSelection(aSelectionType);
506 NS_IMETHODIMP
507 nsTextInputSelectionImpl::SetCaretEnabled(bool enabled)
509 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
511 nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak);
512 if (!shell) return NS_ERROR_FAILURE;
514 // tell the pres shell to enable the caret, rather than settings its visibility directly.
515 // this way the presShell's idea of caret visibility is maintained.
516 nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(shell);
517 if (!selCon) return NS_ERROR_NO_INTERFACE;
518 selCon->SetCaretEnabled(enabled);
520 return NS_OK;
523 NS_IMETHODIMP
524 nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly)
526 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
527 nsresult result;
528 nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
529 if (shell)
531 RefPtr<nsCaret> caret = shell->GetCaret();
532 if (caret) {
533 Selection* selection =
534 mFrameSelection->GetSelection(SelectionType::eNormal);
535 if (selection) {
536 caret->SetCaretReadOnly(aReadOnly);
538 return NS_OK;
541 return NS_ERROR_FAILURE;
544 NS_IMETHODIMP
545 nsTextInputSelectionImpl::GetCaretEnabled(bool *_retval)
547 return GetCaretVisible(_retval);
550 NS_IMETHODIMP
551 nsTextInputSelectionImpl::GetCaretVisible(bool *_retval)
553 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
554 nsresult result;
555 nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
556 if (shell)
558 RefPtr<nsCaret> caret = shell->GetCaret();
559 if (caret) {
560 *_retval = caret->IsVisible();
561 return NS_OK;
564 return NS_ERROR_FAILURE;
567 NS_IMETHODIMP
568 nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility)
570 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
571 nsresult result;
572 nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
573 if (shell)
575 RefPtr<nsCaret> caret = shell->GetCaret();
576 if (caret) {
577 Selection* selection =
578 mFrameSelection->GetSelection(SelectionType::eNormal);
579 if (selection) {
580 caret->SetVisibilityDuringSelection(aVisibility);
582 return NS_OK;
585 return NS_ERROR_FAILURE;
588 NS_IMETHODIMP
589 nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount,
590 bool aExtend)
592 if (mFrameSelection) {
593 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
594 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
596 return NS_ERROR_NULL_POINTER;
599 NS_IMETHODIMP
600 nsTextInputSelectionImpl::CharacterMove(bool aForward, bool aExtend)
602 if (mFrameSelection) {
603 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
604 return frameSelection->CharacterMove(aForward, aExtend);
606 return NS_ERROR_NULL_POINTER;
609 NS_IMETHODIMP
610 nsTextInputSelectionImpl::CharacterExtendForDelete()
612 if (mFrameSelection) {
613 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
614 return frameSelection->CharacterExtendForDelete();
616 return NS_ERROR_NULL_POINTER;
619 NS_IMETHODIMP
620 nsTextInputSelectionImpl::CharacterExtendForBackspace()
622 if (mFrameSelection) {
623 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
624 return frameSelection->CharacterExtendForBackspace();
626 return NS_ERROR_NULL_POINTER;
629 NS_IMETHODIMP
630 nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend)
632 if (mFrameSelection) {
633 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
634 return frameSelection->WordMove(aForward, aExtend);
636 return NS_ERROR_NULL_POINTER;
639 NS_IMETHODIMP
640 nsTextInputSelectionImpl::WordExtendForDelete(bool aForward)
642 if (mFrameSelection) {
643 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
644 return frameSelection->WordExtendForDelete(aForward);
646 return NS_ERROR_NULL_POINTER;
649 NS_IMETHODIMP
650 nsTextInputSelectionImpl::LineMove(bool aForward, bool aExtend)
652 if (mFrameSelection)
654 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
655 nsresult result = frameSelection->LineMove(aForward, aExtend);
656 if (NS_FAILED(result))
657 result = CompleteMove(aForward,aExtend);
658 return result;
660 return NS_ERROR_NULL_POINTER;
664 NS_IMETHODIMP
665 nsTextInputSelectionImpl::IntraLineMove(bool aForward, bool aExtend)
667 if (mFrameSelection) {
668 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
669 return frameSelection->IntraLineMove(aForward, aExtend);
671 return NS_ERROR_NULL_POINTER;
675 NS_IMETHODIMP
676 nsTextInputSelectionImpl::PageMove(bool aForward, bool aExtend)
678 // expected behavior for PageMove is to scroll AND move the caret
679 // and to remain relative position of the caret in view. see Bug 4302.
680 if (mScrollFrame)
682 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
683 frameSelection->CommonPageMove(aForward, aExtend, mScrollFrame);
685 // After ScrollSelectionIntoView(), the pending notifications might be
686 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
687 return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
688 nsISelectionController::SELECTION_FOCUS_REGION,
689 nsISelectionController::SCROLL_SYNCHRONOUS |
690 nsISelectionController::SCROLL_FOR_CARET_MOVE);
693 NS_IMETHODIMP
694 nsTextInputSelectionImpl::CompleteScroll(bool aForward)
696 if (!mScrollFrame)
697 return NS_ERROR_NOT_INITIALIZED;
699 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
700 nsIScrollableFrame::WHOLE,
701 nsIScrollableFrame::INSTANT);
702 return NS_OK;
705 NS_IMETHODIMP
706 nsTextInputSelectionImpl::CompleteMove(bool aForward, bool aExtend)
708 NS_ENSURE_STATE(mFrameSelection);
709 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
711 // grab the parent / root DIV for this text widget
712 nsIContent* parentDIV = frameSelection->GetLimiter();
713 if (!parentDIV)
714 return NS_ERROR_UNEXPECTED;
716 // make the caret be either at the very beginning (0) or the very end
717 int32_t offset = 0;
718 CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE;
719 if (aForward)
721 offset = parentDIV->GetChildCount();
723 // Prevent the caret from being placed after the last
724 // BR node in the content tree!
726 if (offset > 0)
728 nsIContent *child = parentDIV->GetLastChild();
730 if (child->IsHTMLElement(nsGkAtoms::br))
732 --offset;
733 hint = CARET_ASSOCIATE_AFTER; // for Bug 106855
738 frameSelection->HandleClick(parentDIV, offset, offset, aExtend,
739 false, hint);
741 // if we got this far, attempt to scroll no matter what the above result is
742 return CompleteScroll(aForward);
745 NS_IMETHODIMP
746 nsTextInputSelectionImpl::ScrollPage(bool aForward)
748 if (!mScrollFrame)
749 return NS_ERROR_NOT_INITIALIZED;
751 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
752 nsIScrollableFrame::PAGES,
753 nsIScrollableFrame::SMOOTH);
754 return NS_OK;
757 NS_IMETHODIMP
758 nsTextInputSelectionImpl::ScrollLine(bool aForward)
760 if (!mScrollFrame)
761 return NS_ERROR_NOT_INITIALIZED;
763 mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
764 nsIScrollableFrame::LINES,
765 nsIScrollableFrame::SMOOTH);
766 return NS_OK;
769 NS_IMETHODIMP
770 nsTextInputSelectionImpl::ScrollCharacter(bool aRight)
772 if (!mScrollFrame)
773 return NS_ERROR_NOT_INITIALIZED;
775 mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0),
776 nsIScrollableFrame::LINES,
777 nsIScrollableFrame::SMOOTH);
778 return NS_OK;
781 NS_IMETHODIMP
782 nsTextInputSelectionImpl::SelectAll()
784 if (mFrameSelection) {
785 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
786 return frameSelection->SelectAll();
788 return NS_ERROR_NULL_POINTER;
791 NS_IMETHODIMP
792 nsTextInputSelectionImpl::CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool *_retval)
794 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
795 nsresult result;
796 nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result);
797 if (shell)
799 return shell->CheckVisibility(node,startOffset,EndOffset, _retval);
801 return NS_ERROR_FAILURE;
805 nsresult
806 nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode,
807 int16_t aStartOffset,
808 int16_t aEndOffset,
809 bool* aRetval)
811 if (!mPresShellWeak) {
812 return NS_ERROR_NOT_INITIALIZED;
815 nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak);
816 NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
818 return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval);
822 * mozilla::TextInputListener implementation
825 TextInputListener::TextInputListener(nsITextControlElement* aTxtCtrlElement)
826 : mFrame(nullptr)
827 , mTxtCtrlElement(aTxtCtrlElement)
828 , mSelectionWasCollapsed(true)
829 , mHadUndoItems(false)
830 , mHadRedoItems(false)
831 , mSettingValue(false)
832 , mSetValueChanged(true)
833 , mListeningToSelectionChange(false)
837 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
838 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
840 NS_INTERFACE_MAP_BEGIN(TextInputListener)
841 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
842 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
843 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
844 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
845 NS_INTERFACE_MAP_END
847 NS_IMPL_CYCLE_COLLECTION_0(TextInputListener)
849 void
850 TextInputListener::OnSelectionChange(Selection& aSelection,
851 int16_t aReason)
853 if (!mListeningToSelectionChange) {
854 return;
857 AutoWeakFrame weakFrame = mFrame;
859 // Fire the select event
860 // The specs don't exactly say when we should fire the select event.
861 // IE: Whenever you add/remove a character to/from the selection. Also
862 // each time for select all. Also if you get to the end of the text
863 // field you will get new event for each keypress or a continuous
864 // stream of events if you use the mouse. IE will fire select event
865 // when the selection collapses to nothing if you are holding down
866 // the shift or mouse button.
867 // Mozilla: If we have non-empty selection we will fire a new event for each
868 // keypress (or mouseup) if the selection changed. Mozilla will also
869 // create the event each time select all is called, even if everything
870 // was previously selected, becase technically select all will first collapse
871 // and then extend. Mozilla will never create an event if the selection
872 // collapses to nothing.
873 bool collapsed = aSelection.IsCollapsed();
874 if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
875 nsISelectionListener::KEYPRESS_REASON |
876 nsISelectionListener::SELECTALL_REASON))) {
877 nsIContent* content = mFrame->GetContent();
878 if (content) {
879 nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
880 if (doc) {
881 nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
882 if (presShell) {
883 nsEventStatus status = nsEventStatus_eIgnore;
884 WidgetEvent event(true, eFormSelect);
886 presShell->HandleEventWithTarget(&event, mFrame, content, &status);
892 // if the collapsed state did not change, don't fire notifications
893 if (collapsed == mSelectionWasCollapsed) {
894 return;
897 mSelectionWasCollapsed = collapsed;
899 if (!weakFrame.IsAlive() || !mFrame ||
900 !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
901 return;
904 UpdateTextInputCommands(NS_LITERAL_STRING("select"),
905 &aSelection, aReason);
908 static void
909 DoCommandCallback(Command aCommand, void* aData)
911 nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData);
912 nsIContent *content = frame->GetContent();
914 nsCOMPtr<nsIControllers> controllers;
915 HTMLInputElement* input = HTMLInputElement::FromNode(content);
916 if (input) {
917 input->GetControllers(getter_AddRefs(controllers));
918 } else {
919 HTMLTextAreaElement* textArea =
920 HTMLTextAreaElement::FromNode(content);
922 if (textArea) {
923 textArea->GetControllers(getter_AddRefs(controllers));
927 if (!controllers) {
928 NS_WARNING("Could not get controllers");
929 return;
932 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
934 nsCOMPtr<nsIController> controller;
935 controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
936 if (!controller) {
937 return;
940 bool commandEnabled;
941 nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
942 NS_ENSURE_SUCCESS_VOID(rv);
943 if (commandEnabled) {
944 controller->DoCommand(commandStr);
948 NS_IMETHODIMP
949 TextInputListener::HandleEvent(Event* aEvent)
951 if (aEvent->DefaultPrevented()) {
952 return NS_OK;
955 if (!aEvent->IsTrusted()) {
956 return NS_OK;
959 WidgetKeyboardEvent* keyEvent =
960 aEvent->WidgetEventPtr()->AsKeyboardEvent();
961 if (!keyEvent) {
962 return NS_ERROR_UNEXPECTED;
965 if (keyEvent->mMessage != eKeyPress) {
966 return NS_OK;
969 nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
970 mTxtCtrlElement->IsTextArea() ?
971 nsIWidget::NativeKeyBindingsForMultiLineEditor :
972 nsIWidget::NativeKeyBindingsForSingleLineEditor;
974 nsIWidget* widget = keyEvent->mWidget;
975 // If the event is created by chrome script, the widget is nullptr.
976 if (!widget) {
977 widget = mFrame->GetNearestWidget();
978 NS_ENSURE_TRUE(widget, NS_OK);
981 // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
982 // If the event is created by chrome script, it is nullptr but we need to
983 // execute native key bindings. Therefore, we need to set widget to
984 // WidgetEvent::mWidget temporarily.
985 AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget);
986 keyEvent->mWidget = widget;
987 if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType,
988 DoCommandCallback, mFrame)) {
989 aEvent->PreventDefault();
991 return NS_OK;
994 void
995 TextInputListener::OnEditActionHandled()
997 if (!mFrame) {
998 // We've been disconnected from the nsTextEditorState object, nothing to do
999 // here.
1000 return;
1003 AutoWeakFrame weakFrame = mFrame;
1005 nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
1006 nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase);
1007 NS_ASSERTION(frame, "Where is our frame?");
1009 // Update the undo / redo menus
1011 RefPtr<TextEditor> textEditor = frame->GetTextEditor();
1013 // Get the number of undo / redo items
1014 size_t numUndoItems = textEditor->NumberOfUndoItems();
1015 size_t numRedoItems = textEditor->NumberOfRedoItems();
1016 if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
1017 (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
1018 // Modify the menu if undo or redo items are different
1019 UpdateTextInputCommands(NS_LITERAL_STRING("undo"));
1021 mHadUndoItems = numUndoItems != 0;
1022 mHadRedoItems = numRedoItems != 0;
1025 if (!weakFrame.IsAlive()) {
1026 return;
1029 HandleValueChanged(frame);
1032 void
1033 TextInputListener::HandleValueChanged(nsTextControlFrame* aFrame)
1035 // Make sure we know we were changed (do NOT set this to false if there are
1036 // no undo items; JS could change the value and we'd still need to save it)
1037 if (mSetValueChanged) {
1038 if (!aFrame) {
1039 nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
1040 aFrame = static_cast<nsTextControlFrame*> (frameBase);
1041 NS_ASSERTION(aFrame, "Where is our frame?");
1043 aFrame->SetValueChanged(true);
1046 if (!mSettingValue) {
1047 mTxtCtrlElement->OnValueChanged(/* aNotify = */ true,
1048 /* aWasInteractiveUserChange = */ true);
1052 nsresult
1053 TextInputListener::UpdateTextInputCommands(const nsAString& aCommandsToUpdate,
1054 Selection* aSelection,
1055 int16_t aReason)
1057 nsIContent* content = mFrame->GetContent();
1058 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1060 nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
1061 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1063 nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1064 NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
1066 domWindow->UpdateCommands(aCommandsToUpdate, aSelection, aReason);
1067 return NS_OK;
1070 // END mozilla::TextInputListener
1072 // nsTextEditorState
1074 nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
1075 : mTextCtrlElement(aOwningElement)
1076 , mBoundFrame(nullptr)
1077 , mEverInited(false)
1078 , mEditorInitialized(false)
1079 , mInitializing(false)
1080 , mValueTransferInProgress(false)
1081 , mSelectionCached(true)
1082 , mSelectionRestoreEagerInit(false)
1083 , mPlaceholderVisibility(false)
1084 , mPreviewVisibility(false)
1085 , mIsCommittingComposition(false)
1086 // When adding more member variable initializations here, add the same
1087 // also to ::Construct.
1089 MOZ_COUNT_CTOR(nsTextEditorState);
1092 nsTextEditorState*
1093 nsTextEditorState::Construct(nsITextControlElement* aOwningElement,
1094 nsTextEditorState** aReusedState)
1096 if (*aReusedState) {
1097 nsTextEditorState* state = *aReusedState;
1098 *aReusedState = nullptr;
1099 state->mTextCtrlElement = aOwningElement;
1100 state->mBoundFrame = nullptr;
1101 state->mSelectionProperties = SelectionProperties();
1102 state->mEverInited = false;
1103 state->mEditorInitialized = false;
1104 state->mInitializing = false;
1105 state->mValueTransferInProgress = false;
1106 state->mSelectionCached = true;
1107 state->mSelectionRestoreEagerInit = false;
1108 state->mPlaceholderVisibility = false;
1109 state->mPreviewVisibility = false;
1110 state->mIsCommittingComposition = false;
1111 // When adding more member variable initializations here, add the same
1112 // also to the constructor.
1113 return state;
1116 return new nsTextEditorState(aOwningElement);
1119 nsTextEditorState::~nsTextEditorState()
1121 MOZ_COUNT_DTOR(nsTextEditorState);
1122 Clear();
1125 Element*
1126 nsTextEditorState::GetRootNode()
1128 return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1131 Element*
1132 nsTextEditorState::GetPreviewNode()
1134 return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1137 void
1138 nsTextEditorState::Clear()
1140 if (mTextEditor) {
1141 mTextEditor->SetTextInputListener(nullptr);
1144 if (mBoundFrame) {
1145 // Oops, we still have a frame!
1146 // This should happen when the type of a text input control is being changed
1147 // to something which is not a text control. In this case, we should pretend
1148 // that a frame is being destroyed, and clean up after ourselves properly.
1149 UnbindFromFrame(mBoundFrame);
1150 mTextEditor = nullptr;
1151 } else {
1152 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1153 // for us.
1154 DestroyEditor();
1156 mTextListener = nullptr;
1159 void nsTextEditorState::Unlink()
1161 nsTextEditorState* tmp = this;
1162 tmp->Clear();
1163 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
1164 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
1167 void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb)
1169 nsTextEditorState* tmp = this;
1170 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
1171 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
1174 nsFrameSelection*
1175 nsTextEditorState::GetConstFrameSelection() {
1176 if (mSelCon)
1177 return mSelCon->GetConstFrameSelection();
1178 return nullptr;
1181 TextEditor*
1182 nsTextEditorState::GetTextEditor()
1184 if (!mTextEditor) {
1185 nsresult rv = PrepareEditor();
1186 NS_ENSURE_SUCCESS(rv, nullptr);
1188 return mTextEditor;
1191 nsISelectionController*
1192 nsTextEditorState::GetSelectionController() const
1194 return mSelCon;
1197 // Helper class, used below in BindToFrame().
1198 class PrepareEditorEvent : public Runnable {
1199 public:
1200 PrepareEditorEvent(nsTextEditorState& aState,
1201 nsIContent* aOwnerContent,
1202 const nsAString& aCurrentValue)
1203 : mozilla::Runnable("PrepareEditorEvent")
1204 , mState(&aState)
1205 , mOwnerContent(aOwnerContent)
1206 , mCurrentValue(aCurrentValue)
1208 aState.mValueTransferInProgress = true;
1211 NS_IMETHOD Run() override {
1212 NS_ENSURE_TRUE(mState, NS_ERROR_NULL_POINTER);
1214 // Transfer the saved value to the editor if we have one
1215 const nsAString *value = nullptr;
1216 if (!mCurrentValue.IsEmpty()) {
1217 value = &mCurrentValue;
1220 nsAutoScriptBlocker scriptBlocker;
1222 mState->PrepareEditor(value);
1224 mState->mValueTransferInProgress = false;
1226 return NS_OK;
1229 private:
1230 WeakPtr<nsTextEditorState> mState;
1231 nsCOMPtr<nsIContent> mOwnerContent; // strong reference
1232 nsAutoString mCurrentValue;
1235 nsresult
1236 nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame)
1238 NS_ASSERTION(aFrame, "The frame to bind to should be valid");
1239 NS_ENSURE_ARG_POINTER(aFrame);
1241 NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
1242 NS_ENSURE_TRUE(!mBoundFrame, NS_ERROR_FAILURE);
1244 // If we'll need to transfer our current value to the editor, save it before
1245 // binding to the frame.
1246 nsAutoString currentValue;
1247 if (mTextEditor) {
1248 GetValue(currentValue, true);
1251 mBoundFrame = aFrame;
1253 Element* rootNode = aFrame->GetRootNode();
1254 MOZ_ASSERT(rootNode);
1256 nsIPresShell* shell = aFrame->PresContext()->GetPresShell();
1257 MOZ_ASSERT(shell);
1259 // Create selection
1260 RefPtr<nsFrameSelection> frameSel = new nsFrameSelection();
1262 // Create a SelectionController
1263 mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode);
1264 MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
1265 mTextListener = new TextInputListener(mTextCtrlElement);
1267 mTextListener->SetFrame(mBoundFrame);
1268 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
1270 // Get the caret and make it a selection listener.
1271 // FYI: It's safe to use raw pointer for calling
1272 // Selection::AddSelectionListner() because it only appends the listener
1273 // to its internal array.
1274 Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
1275 if (selection) {
1276 RefPtr<nsCaret> caret = shell->GetCaret();
1277 if (caret) {
1278 selection->AddSelectionListener(caret);
1280 mTextListener->StartToListenToSelectionChange();
1283 // If an editor exists from before, prepare it for usage
1284 if (mTextEditor) {
1285 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
1286 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1288 // Set the correct direction on the newly created root node
1289 if (mTextEditor->IsRightToLeft()) {
1290 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), false);
1291 } else if (mTextEditor->IsLeftToRight()) {
1292 rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), false);
1293 } else {
1294 // otherwise, inherit the content node's direction
1297 nsContentUtils::AddScriptRunner(
1298 new PrepareEditorEvent(*this, content, currentValue));
1301 return NS_OK;
1304 struct PreDestroyer
1306 void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
1307 ~PreDestroyer()
1309 if (mTextEditor) {
1310 mTextEditor->PreDestroy(true);
1313 void Swap(RefPtr<TextEditor>& aTextEditor)
1315 return mTextEditor.swap(aTextEditor);
1317 private:
1318 RefPtr<TextEditor> mTextEditor;
1321 nsresult
1322 nsTextEditorState::PrepareEditor(const nsAString *aValue)
1324 if (!mBoundFrame) {
1325 // Cannot create an editor without a bound frame.
1326 // Don't return a failure code, because js callers can't handle that.
1327 return NS_OK;
1330 if (mEditorInitialized) {
1331 // Do not initialize the editor multiple times.
1332 return NS_OK;
1335 AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
1337 // Don't attempt to initialize recursively!
1338 InitializationGuard guard(*this);
1339 if (guard.IsInitializingRecursively()) {
1340 return NS_ERROR_NOT_INITIALIZED;
1343 // Note that we don't check mTextEditor here, because we might already have
1344 // one around, in which case we don't create a new one, and we'll just tie
1345 // the required machinery to it.
1347 nsPresContext *presContext = mBoundFrame->PresContext();
1348 nsIPresShell *shell = presContext->GetPresShell();
1350 // Setup the editor flags
1351 uint32_t editorFlags = nsIPlaintextEditor::eEditorPlaintextMask;
1352 if (IsSingleLineTextControl())
1353 editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask;
1354 if (IsPasswordTextControl())
1355 editorFlags |= nsIPlaintextEditor::eEditorPasswordMask;
1357 // All nsTextControlFrames are widgets
1358 editorFlags |= nsIPlaintextEditor::eEditorWidgetMask;
1360 // Spell check is diabled at creation time. It is enabled once
1361 // the editor comes into focus.
1362 editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck;
1364 bool shouldInitializeEditor = false;
1365 RefPtr<TextEditor> newTextEditor; // the editor that we might create
1366 nsresult rv = NS_OK;
1367 PreDestroyer preDestroyer;
1368 if (!mTextEditor) {
1369 shouldInitializeEditor = true;
1371 // Create an editor
1372 newTextEditor = new TextEditor();
1373 preDestroyer.Init(newTextEditor);
1375 // Make sure we clear out the non-breaking space before we initialize the editor
1376 rv = mBoundFrame->UpdateValueDisplay(true, true);
1377 NS_ENSURE_SUCCESS(rv, rv);
1378 } else {
1379 if (aValue || !mEditorInitialized) {
1380 // Set the correct value in the root node
1381 rv = mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
1382 NS_ENSURE_SUCCESS(rv, rv);
1385 newTextEditor = mTextEditor; // just pretend that we have a new editor!
1387 // Don't lose application flags in the process.
1388 if (newTextEditor->IsMailEditor()) {
1389 editorFlags |= nsIPlaintextEditor::eEditorMailMask;
1393 // Get the current value of the textfield from the content.
1394 // Note that if we've created a new editor, mTextEditor is null at this stage,
1395 // so we will get the real value from the content.
1396 nsAutoString defaultValue;
1397 if (aValue) {
1398 defaultValue = *aValue;
1399 } else {
1400 GetValue(defaultValue, true);
1403 if (!mEditorInitialized) {
1404 // Now initialize the editor.
1406 // NOTE: Conversion of '\n' to <BR> happens inside the
1407 // editor's Init() call.
1409 // Get the DOM document
1410 nsCOMPtr<nsIDocument> doc = shell->GetDocument();
1411 if (NS_WARN_IF(!doc)) {
1412 return NS_ERROR_FAILURE;
1415 // What follows is a bit of a hack. The editor uses the public DOM APIs
1416 // for its content manipulations, and it causes it to fail some security
1417 // checks deep inside when initializing. So we explictly make it clear that
1418 // we're native code.
1419 // Note that any script that's directly trying to access our value
1420 // has to be going through some scriptable object to do that and that
1421 // already does the relevant security checks.
1422 AutoNoJSAPI nojsapi;
1424 rv = newTextEditor->Init(*doc, GetRootNode(), mSelCon, editorFlags,
1425 defaultValue);
1426 NS_ENSURE_SUCCESS(rv, rv);
1429 // Initialize the controller for the editor
1431 if (!SuppressEventHandlers(presContext)) {
1432 nsCOMPtr<nsIControllers> controllers;
1433 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
1434 HTMLInputElement* inputElement =
1435 HTMLInputElement::FromNodeOrNull(content);
1436 if (inputElement) {
1437 rv = inputElement->GetControllers(getter_AddRefs(controllers));
1438 } else {
1439 HTMLTextAreaElement* textAreaElement =
1440 HTMLTextAreaElement::FromNodeOrNull(content);
1442 if (!textAreaElement)
1443 return NS_ERROR_FAILURE;
1445 rv = textAreaElement->GetControllers(getter_AddRefs(controllers));
1448 NS_ENSURE_SUCCESS(rv, rv);
1450 if (controllers) {
1451 uint32_t numControllers;
1452 bool found = false;
1453 rv = controllers->GetControllerCount(&numControllers);
1454 for (uint32_t i = 0; i < numControllers; i ++) {
1455 nsCOMPtr<nsIController> controller;
1456 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1457 if (NS_SUCCEEDED(rv) && controller) {
1458 nsCOMPtr<nsIControllerContext> editController =
1459 do_QueryInterface(controller);
1460 if (editController) {
1461 editController->SetCommandContext(
1462 static_cast<nsIEditor*>(newTextEditor));
1463 found = true;
1467 if (!found)
1468 rv = NS_ERROR_FAILURE;
1472 // Initialize the plaintext editor
1473 if (shouldInitializeEditor) {
1474 // Set up wrapping
1475 newTextEditor->SetWrapColumn(GetWrapCols());
1478 // Set max text field length
1479 newTextEditor->SetMaxTextLength(GetMaxLength());
1481 if (nsCOMPtr<Element> element = do_QueryInterface(mTextCtrlElement)) {
1482 editorFlags = newTextEditor->Flags();
1484 // Check if the readonly attribute is set.
1485 if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly))
1486 editorFlags |= nsIPlaintextEditor::eEditorReadonlyMask;
1488 // Check if the disabled attribute is set.
1489 // TODO: call IsDisabled() here!
1490 if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled))
1491 editorFlags |= nsIPlaintextEditor::eEditorDisabledMask;
1493 // Disable the selection if necessary.
1494 if (newTextEditor->IsDisabled()) {
1495 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF);
1498 SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1501 if (shouldInitializeEditor) {
1502 // Hold on to the newly created editor
1503 preDestroyer.Swap(mTextEditor);
1506 // If we have a default value, insert it under the div we created
1507 // above, but be sure to use the editor so that '*' characters get
1508 // displayed for password fields, etc. SetValue() will call the
1509 // editor for us.
1511 if (!defaultValue.IsEmpty()) {
1512 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1513 if (NS_WARN_IF(NS_FAILED(rv))) {
1514 return rv;
1517 // Now call SetValue() which will make the necessary editor calls to set
1518 // the default value. Make sure to turn off undo before setting the default
1519 // value, and turn it back on afterwards. This will make sure we can't undo
1520 // past the default value.
1521 // So, we use eSetValue_Internal flag only that it will turn off undo.
1523 bool success = SetValue(defaultValue, eSetValue_Internal);
1524 NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
1526 // Now restore the original editor flags.
1527 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1528 if (NS_WARN_IF(NS_FAILED(rv))) {
1529 return rv;
1533 if (IsPasswordTextControl()) {
1534 // Disable undo for <input type="password">. Note that we want to do this
1535 // at the very end of InitEditor(), so the calls to EnableUndoRedo() when
1536 // setting the default value don't screw us up. Since changing the
1537 // control type does a reframe, we don't have to worry about dynamic type
1538 // changes here.
1539 DebugOnly<bool> disabledUndoRedo = newTextEditor->DisableUndoRedo();
1540 NS_WARNING_ASSERTION(disabledUndoRedo,
1541 "Failed to disable undo/redo transaction");
1542 } else {
1543 DebugOnly<bool> enabledUndoRedo =
1544 newTextEditor->EnableUndoRedo(nsITextControlElement::DEFAULT_UNDO_CAP);
1545 NS_WARNING_ASSERTION(enabledUndoRedo,
1546 "Failed to enable undo/redo transaction");
1549 if (!mEditorInitialized) {
1550 newTextEditor->PostCreate();
1551 mEverInited = true;
1552 mEditorInitialized = true;
1555 if (mTextListener) {
1556 newTextEditor->SetTextInputListener(mTextListener);
1559 // Restore our selection after being bound to a new frame
1560 HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1561 if (number ? number->IsSelectionCached() : mSelectionCached) {
1562 if (mRestoringSelection) // paranoia
1563 mRestoringSelection->Revoke();
1564 mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1565 if (mRestoringSelection) {
1566 nsContentUtils::AddScriptRunner(mRestoringSelection);
1570 // The selection cache is no longer going to be valid.
1572 // XXXbz Shouldn't we do this at the point when we're actually about to
1573 // restore the properties or something? As things stand, if UnbindFromFrame
1574 // happens before our RestoreSelectionState runs, it looks like we'll lose our
1575 // selection info, because we will think we don't have it cached and try to
1576 // read it from the selection controller, which will not have it yet.
1577 if (number) {
1578 number->ClearSelectionCached();
1579 } else {
1580 mSelectionCached = false;
1583 return rv;
1586 void
1587 nsTextEditorState::FinishedRestoringSelection()
1589 mRestoringSelection = nullptr;
1592 bool
1593 nsTextEditorState::IsSelectionCached() const
1595 if (mBoundFrame) {
1596 HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1597 if (number) {
1598 return number->IsSelectionCached();
1601 return mSelectionCached;
1604 nsTextEditorState::SelectionProperties&
1605 nsTextEditorState::GetSelectionProperties()
1607 if (mBoundFrame) {
1608 HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1609 if (number) {
1610 return number->GetSelectionProperties();
1613 return mSelectionProperties;
1616 void
1617 nsTextEditorState::SyncUpSelectionPropertiesBeforeDestruction()
1619 if (mBoundFrame) {
1620 UnbindFromFrame(mBoundFrame);
1624 void
1625 nsTextEditorState::SetSelectionProperties(nsTextEditorState::SelectionProperties& aProps)
1627 if (mBoundFrame) {
1628 mBoundFrame->SetSelectionRange(aProps.GetStart(),
1629 aProps.GetEnd(),
1630 aProps.GetDirection());
1631 } else {
1632 mSelectionProperties = aProps;
1636 void
1637 nsTextEditorState::GetSelectionRange(uint32_t* aSelectionStart,
1638 uint32_t* aSelectionEnd,
1639 ErrorResult& aRv)
1641 MOZ_ASSERT(aSelectionStart);
1642 MOZ_ASSERT(aSelectionEnd);
1643 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1644 "How can we not have a cached selection if we have no selection "
1645 "controller?");
1647 // Note that we may have both IsSelectionCached() _and_
1648 // GetSelectionController() if we haven't initialized our editor yet.
1649 if (IsSelectionCached()) {
1650 const SelectionProperties& props = GetSelectionProperties();
1651 *aSelectionStart = props.GetStart();
1652 *aSelectionEnd = props.GetEnd();
1653 return;
1656 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
1657 if (NS_WARN_IF(!sel)) {
1658 aRv.Throw(NS_ERROR_FAILURE);
1659 return;
1662 mozilla::dom::Element* root = GetRootNode();
1663 if (NS_WARN_IF(!root)) {
1664 aRv.Throw(NS_ERROR_UNEXPECTED);
1665 return;
1667 nsContentUtils::GetSelectionInTextControl(sel, root,
1668 *aSelectionStart, *aSelectionEnd);
1671 nsITextControlFrame::SelectionDirection
1672 nsTextEditorState::GetSelectionDirection(ErrorResult& aRv)
1674 MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1675 "How can we not have a cached selection if we have no selection "
1676 "controller?");
1678 // Note that we may have both IsSelectionCached() _and_
1679 // GetSelectionController() if we haven't initialized our editor yet.
1680 if (IsSelectionCached()) {
1681 return GetSelectionProperties().GetDirection();
1684 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
1685 if (NS_WARN_IF(!sel)) {
1686 aRv.Throw(NS_ERROR_FAILURE);
1687 return nsITextControlFrame::eForward; // Doesn't really matter
1690 nsDirection direction = sel->GetDirection();
1691 if (direction == eDirNext) {
1692 return nsITextControlFrame::eForward;
1695 MOZ_ASSERT(direction == eDirPrevious);
1696 return nsITextControlFrame::eBackward;
1699 void
1700 nsTextEditorState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
1701 nsITextControlFrame::SelectionDirection aDirection,
1702 ErrorResult& aRv)
1704 MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
1705 "How can we have a non-cached selection but no frame?");
1707 if (aStart > aEnd) {
1708 aStart = aEnd;
1711 bool changed = false;
1712 nsresult rv = NS_OK; // For the ScrollSelectionIntoView() return value.
1713 if (IsSelectionCached()) {
1714 nsAutoString value;
1715 // XXXbz is "false" the right thing to pass here? Hard to tell, given the
1716 // various mismatches between our impl and the spec.
1717 GetValue(value, false);
1718 uint32_t length = value.Length();
1719 if (aStart > length) {
1720 aStart = length;
1722 if (aEnd > length) {
1723 aEnd = length;
1725 SelectionProperties& props = GetSelectionProperties();
1726 changed = props.GetStart() != aStart ||
1727 props.GetEnd() != aEnd ||
1728 props.GetDirection() != aDirection;
1729 props.SetStart(aStart);
1730 props.SetEnd(aEnd);
1731 props.SetDirection(aDirection);
1732 } else {
1733 WeakPtr<nsTextEditorState> self(this);
1734 aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
1735 if (aRv.Failed() || !self.get()) {
1736 return;
1738 rv = mBoundFrame->ScrollSelectionIntoView();
1739 // Press on to firing the event even if that failed, like our old code did.
1740 // But is that really what we want? Firing the event _and_ throwing from
1741 // here is weird. Maybe we should just ignore ScrollSelectionIntoView
1742 // failures?
1744 // XXXbz This is preserving our current behavior of firing a "select" event
1745 // on all mutations when we have an editor, but we should really consider
1746 // fixing that...
1747 changed = true;
1750 if (changed) {
1751 // It sure would be nice if we had an existing Element* or so to work with.
1752 nsCOMPtr<nsINode> node = do_QueryInterface(mTextCtrlElement);
1753 RefPtr<AsyncEventDispatcher> asyncDispatcher =
1754 new AsyncEventDispatcher(node,
1755 NS_LITERAL_STRING("select"),
1756 CanBubble::eYes,
1757 ChromeOnlyDispatch::eNo);
1758 asyncDispatcher->PostDOMEvent();
1761 if (NS_FAILED(rv)) {
1762 aRv.Throw(rv);
1766 void
1767 nsTextEditorState::SetSelectionStart(const Nullable<uint32_t>& aStart,
1768 ErrorResult& aRv)
1770 uint32_t start = 0;
1771 if (!aStart.IsNull()) {
1772 start = aStart.Value();
1775 uint32_t ignored, end;
1776 GetSelectionRange(&ignored, &end, aRv);
1777 if (aRv.Failed()) {
1778 return;
1781 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1782 if (aRv.Failed()) {
1783 return;
1786 if (end < start) {
1787 end = start;
1790 SetSelectionRange(start, end, dir, aRv);
1793 void
1794 nsTextEditorState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
1795 ErrorResult& aRv)
1797 uint32_t end = 0;
1798 if (!aEnd.IsNull()) {
1799 end = aEnd.Value();
1802 uint32_t start, ignored;
1803 GetSelectionRange(&start, &ignored, aRv);
1804 if (aRv.Failed()) {
1805 return;
1808 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1809 if (aRv.Failed()) {
1810 return;
1813 SetSelectionRange(start, end, dir, aRv);
1816 static void
1817 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
1819 if (dir == nsITextControlFrame::eNone) {
1820 NS_WARNING("We don't actually support this... how did we get it?");
1821 aDirection.AssignLiteral("none");
1822 } else if (dir == nsITextControlFrame::eForward) {
1823 aDirection.AssignLiteral("forward");
1824 } else if (dir == nsITextControlFrame::eBackward) {
1825 aDirection.AssignLiteral("backward");
1826 } else {
1827 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
1831 void
1832 nsTextEditorState::GetSelectionDirectionString(nsAString& aDirection,
1833 ErrorResult& aRv)
1835 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1836 if (aRv.Failed()) {
1837 return;
1839 DirectionToName(dir, aDirection);
1842 static nsITextControlFrame::SelectionDirection
1843 DirectionStringToSelectionDirection(const nsAString& aDirection)
1845 if (aDirection.EqualsLiteral("backward")) {
1846 return nsITextControlFrame::eBackward;
1849 // We don't support directionless selections.
1850 return nsITextControlFrame::eForward;
1853 void
1854 nsTextEditorState::SetSelectionDirection(const nsAString& aDirection,
1855 ErrorResult& aRv)
1857 nsITextControlFrame::SelectionDirection dir =
1858 DirectionStringToSelectionDirection(aDirection);
1860 if (IsSelectionCached()) {
1861 GetSelectionProperties().SetDirection(dir);
1862 return;
1865 uint32_t start, end;
1866 GetSelectionRange(&start, &end, aRv);
1867 if (aRv.Failed()) {
1868 return;
1871 SetSelectionRange(start, end, dir, aRv);
1874 static nsITextControlFrame::SelectionDirection
1875 DirectionStringToSelectionDirection(const Optional<nsAString>& aDirection)
1877 if (!aDirection.WasPassed()) {
1878 // We don't support directionless selections.
1879 return nsITextControlFrame::eForward;
1882 return DirectionStringToSelectionDirection(aDirection.Value());
1885 void
1886 nsTextEditorState::SetSelectionRange(uint32_t aSelectionStart,
1887 uint32_t aSelectionEnd,
1888 const Optional<nsAString>& aDirection,
1889 ErrorResult& aRv)
1891 nsITextControlFrame::SelectionDirection dir =
1892 DirectionStringToSelectionDirection(aDirection);
1894 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv);
1897 void
1898 nsTextEditorState::SetRangeText(const nsAString& aReplacement,
1899 ErrorResult& aRv)
1901 uint32_t start, end;
1902 GetSelectionRange(&start, &end, aRv);
1903 if (aRv.Failed()) {
1904 return;
1907 SetRangeText(aReplacement, start, end, SelectionMode::Preserve,
1908 aRv, Some(start), Some(end));
1911 void
1912 nsTextEditorState::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
1913 uint32_t aEnd, SelectionMode aSelectMode,
1914 ErrorResult& aRv,
1915 const Maybe<uint32_t>& aSelectionStart,
1916 const Maybe<uint32_t>& aSelectionEnd)
1918 if (aStart > aEnd) {
1919 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1920 return;
1923 nsAutoString value;
1924 mTextCtrlElement->GetValueFromSetRangeText(value);
1925 uint32_t inputValueLength = value.Length();
1927 if (aStart > inputValueLength) {
1928 aStart = inputValueLength;
1931 if (aEnd > inputValueLength) {
1932 aEnd = inputValueLength;
1935 uint32_t selectionStart, selectionEnd;
1936 if (!aSelectionStart) {
1937 MOZ_ASSERT(!aSelectionEnd);
1938 GetSelectionRange(&selectionStart, &selectionEnd, aRv);
1939 if (aRv.Failed()) {
1940 return;
1942 } else {
1943 MOZ_ASSERT(aSelectionEnd);
1944 selectionStart = *aSelectionStart;
1945 selectionEnd = *aSelectionEnd;
1948 MOZ_ASSERT(aStart <= aEnd);
1949 value.Replace(aStart, aEnd - aStart, aReplacement);
1950 nsresult rv = mTextCtrlElement->SetValueFromSetRangeText(value);
1951 if (NS_FAILED(rv)) {
1952 aRv.Throw(rv);
1953 return;
1956 uint32_t newEnd = aStart + aReplacement.Length();
1957 int32_t delta = aReplacement.Length() - (aEnd - aStart);
1959 switch (aSelectMode) {
1960 case mozilla::dom::SelectionMode::Select:
1962 selectionStart = aStart;
1963 selectionEnd = newEnd;
1965 break;
1966 case mozilla::dom::SelectionMode::Start:
1968 selectionStart = selectionEnd = aStart;
1970 break;
1971 case mozilla::dom::SelectionMode::End:
1973 selectionStart = selectionEnd = newEnd;
1975 break;
1976 case mozilla::dom::SelectionMode::Preserve:
1978 if (selectionStart > aEnd) {
1979 selectionStart += delta;
1980 } else if (selectionStart > aStart) {
1981 selectionStart = aStart;
1984 if (selectionEnd > aEnd) {
1985 selectionEnd += delta;
1986 } else if (selectionEnd > aStart) {
1987 selectionEnd = newEnd;
1990 break;
1991 default:
1992 MOZ_CRASH("Unknown mode!");
1995 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
1998 HTMLInputElement*
1999 nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const
2001 MOZ_ASSERT(aFrame);
2002 nsIContent* content = aFrame->GetContent();
2003 MOZ_ASSERT(content);
2004 nsIContent* parent = content->GetParent();
2005 if (!parent) {
2006 return nullptr;
2008 nsIContent* parentOfParent = parent->GetParent();
2009 if (!parentOfParent) {
2010 return nullptr;
2012 HTMLInputElement* input = HTMLInputElement::FromNode(parentOfParent);
2013 if (input) {
2014 // This function might be called during frame reconstruction as a result
2015 // of changing the input control's type from number to something else. In
2016 // that situation, the type of the control has changed, but its frame has
2017 // not been reconstructed yet. So we need to check the type of the input
2018 // control in addition to the type of the frame.
2019 return (input->ControlType() == NS_FORM_INPUT_NUMBER) ? input : nullptr;
2022 return nullptr;
2025 void
2026 nsTextEditorState::DestroyEditor()
2028 // notify the editor that we are going away
2029 if (mEditorInitialized) {
2030 mTextEditor->PreDestroy(true);
2031 mEditorInitialized = false;
2035 void
2036 nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
2038 NS_ENSURE_TRUE_VOID(mBoundFrame);
2040 // If it was, however, it should be unbounded from the same frame.
2041 MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2042 NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
2044 // If the editor is modified but nsIEditorObserver::EditAction() hasn't been
2045 // called yet, we need to notify it here because editor may be destroyed
2046 // before EditAction() is called if selection listener causes flushing layout.
2047 if (mTextListener && mTextEditor && mEditorInitialized &&
2048 mTextEditor->IsInEditSubAction()) {
2049 mTextListener->OnEditActionHandled();
2052 // We need to start storing the value outside of the editor if we're not
2053 // going to use it anymore, so retrieve it for now.
2054 nsAutoString value;
2055 GetValue(value, true);
2057 if (mRestoringSelection) {
2058 mRestoringSelection->Revoke();
2059 mRestoringSelection = nullptr;
2062 // Save our selection state if needed.
2063 // Note that GetSelectionRange will attempt to work with our selection
2064 // controller, so we should make sure we do it before we start doing things
2065 // like destroying our editor (if we have one), tearing down the selection
2066 // controller, and so forth.
2067 if (!IsSelectionCached()) {
2068 // Go ahead and cache it now.
2069 uint32_t start = 0, end = 0;
2070 GetSelectionRange(&start, &end, IgnoreErrors());
2072 nsITextControlFrame::SelectionDirection direction =
2073 GetSelectionDirection(IgnoreErrors());
2075 SelectionProperties& props = GetSelectionProperties();
2076 props.SetStart(start);
2077 props.SetEnd(end);
2078 props.SetDirection(direction);
2079 HTMLInputElement* number = GetParentNumberControl(aFrame);
2080 if (number) {
2081 // If we are inside a number control, cache the selection on the
2082 // parent control, because this text editor state will be destroyed
2083 // together with the native anonymous text control.
2084 number->SetSelectionCached();
2085 } else {
2086 mSelectionCached = true;
2090 // Destroy our editor
2091 DestroyEditor();
2093 // Clean up the controller
2094 if (!SuppressEventHandlers(mBoundFrame->PresContext()))
2096 nsCOMPtr<nsIControllers> controllers;
2097 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2098 HTMLInputElement* inputElement =
2099 HTMLInputElement::FromNodeOrNull(content);
2100 if (inputElement)
2101 inputElement->GetControllers(getter_AddRefs(controllers));
2102 else
2104 HTMLTextAreaElement* textAreaElement =
2105 HTMLTextAreaElement::FromNodeOrNull(content);
2106 if (textAreaElement) {
2107 textAreaElement->GetControllers(getter_AddRefs(controllers));
2111 if (controllers)
2113 uint32_t numControllers;
2114 nsresult rv = controllers->GetControllerCount(&numControllers);
2115 NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor");
2116 for (uint32_t i = 0; i < numControllers; i ++)
2118 nsCOMPtr<nsIController> controller;
2119 rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2120 if (NS_SUCCEEDED(rv) && controller)
2122 nsCOMPtr<nsIControllerContext> editController = do_QueryInterface(controller);
2123 if (editController)
2125 editController->SetCommandContext(nullptr);
2132 if (mSelCon) {
2133 if (mTextListener) {
2134 mTextListener->EndListeningToSelectionChange();
2137 mSelCon->SetScrollableFrame(nullptr);
2138 mSelCon = nullptr;
2141 if (mTextListener)
2143 mTextListener->SetFrame(nullptr);
2145 nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
2146 EventListenerManager* manager = target->GetExistingListenerManager();
2147 if (manager) {
2148 manager->RemoveEventListenerByType(mTextListener,
2149 NS_LITERAL_STRING("keydown"),
2150 TrustedEventsAtSystemGroupBubble());
2151 manager->RemoveEventListenerByType(mTextListener,
2152 NS_LITERAL_STRING("keypress"),
2153 TrustedEventsAtSystemGroupBubble());
2154 manager->RemoveEventListenerByType(mTextListener,
2155 NS_LITERAL_STRING("keyup"),
2156 TrustedEventsAtSystemGroupBubble());
2159 mTextListener = nullptr;
2162 mBoundFrame = nullptr;
2164 // Now that we don't have a frame any more, store the value in the text buffer.
2165 // The only case where we don't do this is if a value transfer is in progress.
2166 if (!mValueTransferInProgress) {
2167 bool success = SetValue(value, eSetValue_Internal);
2168 // TODO Find something better to do if this fails...
2169 NS_ENSURE_TRUE_VOID(success);
2173 int32_t
2174 nsTextEditorState::GetMaxLength()
2176 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2177 nsGenericHTMLElement* element =
2178 nsGenericHTMLElement::FromNodeOrNull(content);
2179 if (NS_WARN_IF(!element)) {
2180 return -1;
2183 const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength);
2184 if (attr && attr->Type() == nsAttrValue::eInteger) {
2185 return attr->GetIntegerValue();
2188 return -1;
2191 void
2192 nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const
2194 // While SetValue() is being called and requesting to commit composition to
2195 // IME, GetValue() may be called for appending text or something. Then, we
2196 // need to return the latest aValue of SetValue() since the value hasn't
2197 // been set to the editor yet.
2198 if (mIsCommittingComposition) {
2199 aValue = mValueBeingSet;
2200 return;
2203 if (mTextEditor && mBoundFrame &&
2204 (mEditorInitialized || !IsSingleLineTextControl())) {
2205 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2206 aValue = mBoundFrame->CachedValue();
2207 return;
2210 aValue.Truncate(); // initialize out param
2212 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2213 nsIDocumentEncoder::OutputPreformatted |
2214 nsIDocumentEncoder::OutputPersistNBSP |
2215 nsIDocumentEncoder::OutputBodyOnly);
2216 if (!aIgnoreWrap) {
2217 nsITextControlElement::nsHTMLTextWrap wrapProp;
2218 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2219 if (content &&
2220 nsITextControlElement::GetWrapPropertyEnum(content, wrapProp) &&
2221 wrapProp == nsITextControlElement::eHTMLTextWrap_Hard) {
2222 flags |= nsIDocumentEncoder::OutputWrap;
2226 // What follows is a bit of a hack. The problem is that we could be in
2227 // this method because we're being destroyed for whatever reason while
2228 // script is executing. If that happens, editor will run with the
2229 // privileges of the executing script, which means it may not be able to
2230 // access its own DOM nodes! Let's try to deal with that by pushing a null
2231 // JSContext on the JSContext stack to make it clear that we're native
2232 // code. Note that any script that's directly trying to access our value
2233 // has to be going through some scriptable object to do that and that
2234 // already does the relevant security checks.
2235 // XXXbz if we could just get the textContent of our anonymous content (eg
2236 // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2237 // this.
2238 { /* Scope for AutoNoJSAPI. */
2239 AutoNoJSAPI nojsapi;
2241 DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2242 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2244 // Only when the result doesn't include line breaks caused by hard-wrap,
2245 // mCacheValue should cache the value.
2246 if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2247 mBoundFrame->CacheValue(aValue);
2248 } else {
2249 mBoundFrame->ClearCachedValue();
2251 } else {
2252 if (!mTextCtrlElement->ValueChanged() || !mValue) {
2253 mTextCtrlElement->GetDefaultValueFromContent(aValue);
2254 } else {
2255 aValue = *mValue;
2260 bool
2261 nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
2262 uint32_t aFlags)
2264 nsAutoString newValue(aValue);
2266 // While mIsCommittingComposition is true (that means that some event
2267 // handlers which are fired during committing composition are the caller of
2268 // this method), GetValue() uses mValueBeingSet for its result because the
2269 // first calls of this methods hasn't set the value yet. So, when it's true,
2270 // we need to modify mValueBeingSet. In this case, we will back to the first
2271 // call of this method, then, mValueBeingSet will be truncated when
2272 // mIsCommittingComposition is set false. See below.
2273 if (mIsCommittingComposition) {
2274 mValueBeingSet = aValue;
2275 // GetValue doesn't return current text frame's content during committing.
2276 // So we cannot trust this old value
2277 aOldValue = nullptr;
2280 // Note that if this may be called during reframe of the editor. In such
2281 // case, we shouldn't commit composition. Therefore, when this is called
2282 // for internal processing, we shouldn't commit the composition.
2283 if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) {
2284 if (EditorHasComposition()) {
2285 // When this is called recursively, there shouldn't be composition.
2286 if (NS_WARN_IF(mIsCommittingComposition)) {
2287 // Don't request to commit composition again. But if it occurs,
2288 // we should skip to set the new value to the editor here. It should
2289 // be set later with the updated mValueBeingSet.
2290 return true;
2292 if (NS_WARN_IF(!mBoundFrame)) {
2293 // We're not sure if this case is possible.
2294 } else {
2295 // If setting value won't change current value, we shouldn't commit
2296 // composition for compatibility with the other browsers.
2297 nsAutoString currentValue;
2298 if (aOldValue) {
2299 #ifdef DEBUG
2300 mBoundFrame->GetText(currentValue);
2301 MOZ_ASSERT(currentValue.Equals(*aOldValue));
2302 #endif
2303 currentValue.Assign(*aOldValue);
2304 } else {
2305 mBoundFrame->GetText(currentValue);
2307 if (newValue == currentValue) {
2308 // Note that in this case, we shouldn't fire any events with setting
2309 // value because event handlers may try to set value recursively but
2310 // we cannot commit composition at that time due to unsafe to run
2311 // script (see below).
2312 return true;
2314 // IME might commit composition, then change value, so we cannot
2315 // trust old value from parameter.
2316 aOldValue = nullptr;
2318 // If there is composition, need to commit composition first because
2319 // other browsers do that.
2320 // NOTE: We don't need to block nested calls of this because input nor
2321 // other events won't be fired by setting values and script blocker
2322 // is used during setting the value to the editor. IE also allows
2323 // to set the editor value on the input event which is caused by
2324 // forcibly committing composition.
2325 if (nsContentUtils::IsSafeToRunScript()) {
2326 WeakPtr<nsTextEditorState> self(this);
2327 // WARNING: During this call, compositionupdate, compositionend, input
2328 // events will be fired. Therefore, everything can occur. E.g., the
2329 // document may be unloaded.
2330 mValueBeingSet = aValue;
2331 mIsCommittingComposition = true;
2332 RefPtr<TextEditor> textEditor = mTextEditor;
2333 nsresult rv = textEditor->CommitComposition();
2334 if (!self.get()) {
2335 return true;
2337 mIsCommittingComposition = false;
2338 // If this is called recursively during committing composition and
2339 // some of them may be skipped above. Therefore, we need to set
2340 // value to the editor with the aValue of the latest call.
2341 newValue = mValueBeingSet;
2342 // When mIsCommittingComposition is false, mValueBeingSet won't be
2343 // used. Therefore, let's clear it.
2344 mValueBeingSet.Truncate();
2345 if (NS_FAILED(rv)) {
2346 NS_WARNING("nsTextEditorState failed to commit composition");
2347 return true;
2349 } else {
2350 NS_WARNING("SetValue() is called when there is composition but "
2351 "it's not safe to request to commit the composition");
2356 // \r is an illegal character in the dom, but people use them,
2357 // so convert windows and mac platform linebreaks to \n:
2358 if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
2359 return false;
2362 if (mTextEditor && mBoundFrame) {
2363 // The InsertText call below might flush pending notifications, which
2364 // could lead into a scheduled PrepareEditor to be called. That will
2365 // lead to crashes (or worse) because we'd be initializing the editor
2366 // before InsertText returns. This script blocker makes sure that
2367 // PrepareEditor cannot be called prematurely.
2368 nsAutoScriptBlocker scriptBlocker;
2370 #ifdef DEBUG
2371 if (IsSingleLineTextControl()) {
2372 NS_ASSERTION(mEditorInitialized || mInitializing,
2373 "We should never try to use the editor if we're not initialized unless we're being initialized");
2375 #endif
2377 nsAutoString currentValue;
2378 if (aOldValue) {
2379 #ifdef DEBUG
2380 mBoundFrame->GetText(currentValue);
2381 MOZ_ASSERT(currentValue.Equals(*aOldValue));
2382 #endif
2383 currentValue.Assign(*aOldValue);
2384 } else {
2385 mBoundFrame->GetText(currentValue);
2388 AutoWeakFrame weakFrame(mBoundFrame);
2390 // this is necessary to avoid infinite recursion
2391 if (!currentValue.Equals(newValue)) {
2392 RefPtr<TextEditor> textEditor = mTextEditor;
2393 ValueSetter valueSetter(textEditor);
2395 nsCOMPtr<nsIDocument> document = textEditor->GetDocument();
2396 if (NS_WARN_IF(!document)) {
2397 return true;
2400 // Time to mess with our security context... See comments in GetValue()
2401 // for why this is needed. Note that we have to do this up here, because
2402 // otherwise SelectAll() will fail.
2404 AutoNoJSAPI nojsapi;
2406 // FYI: It's safe to use raw pointer for selection here because
2407 // SelectionBatcher will grab it with RefPtr.
2408 Selection* selection =
2409 mSelCon->GetSelection(SelectionType::eNormal);
2410 SelectionBatcher selectionBatcher(selection);
2412 if (NS_WARN_IF(!weakFrame.IsAlive())) {
2413 return true;
2416 valueSetter.Init();
2418 // get the flags, remove readonly, disabled and max-length,
2419 // set the value, restore flags
2421 AutoRestoreEditorState restoreState(textEditor);
2423 mTextListener->SettingValue(true);
2424 bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
2425 mTextListener->SetValueChanged(notifyValueChanged);
2427 if (aFlags & eSetValue_BySetUserInput) {
2428 // If the caller inserts text as part of user input, for example,
2429 // autocomplete, we need to replace the text as "insert string"
2430 // because undo should cancel only this operation (i.e., previous
2431 // transactions typed by user shouldn't be merged with this).
2432 DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
2433 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2434 "Failed to set the new value");
2435 } else if (aFlags & eSetValue_ForXUL) {
2436 // On XUL <textbox> element, we need to preserve existing undo
2437 // transactions.
2438 // XXX Do we really need to do such complicated optimization?
2439 // This was landed for web pages which set <textarea> value
2440 // per line (bug 518122). For example:
2441 // for (;;) {
2442 // textarea.value += oneLineText + "\n";
2443 // }
2444 // However, this path won't be used in web content anymore.
2445 nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
2446 uint32_t currentLength = currentValue.Length();
2447 uint32_t newlength = newValue.Length();
2448 if (!currentLength ||
2449 !StringBeginsWith(newValue, currentValue)) {
2450 // Replace the whole text.
2451 currentLength = 0;
2452 kungFuDeathGrip->SelectAll();
2453 } else {
2454 // Collapse selection to the end so that we can append data.
2455 mBoundFrame->SelectAllOrCollapseToEndOfText(false);
2457 const nsAString& insertValue =
2458 StringTail(newValue, newlength - currentLength);
2460 if (insertValue.IsEmpty()) {
2461 DebugOnly<nsresult> rv =
2462 textEditor->DeleteSelectionAsAction(nsIEditor::eNone,
2463 nsIEditor::eStrip);
2464 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2465 "Failed to remove the text");
2466 } else {
2467 DebugOnly<nsresult> rv =
2468 textEditor->InsertTextAsAction(insertValue);
2469 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2470 "Failed to insert the new value");
2472 } else {
2473 // On <input> or <textarea>, we shouldn't preserve existing undo
2474 // transactions because other browsers do not preserve them too
2475 // and not preserving transactions makes setting value faster.
2476 AutoDisableUndo disableUndo(textEditor);
2477 if (selection) {
2478 // Since we don't use undo transaction, we don't need to store
2479 // selection state. SetText will set selection to tail.
2480 // Note that textEditor will collapse selection to the end.
2481 // Therefore, it's safe to use RemoveAllRangesTemporarily() here.
2482 selection->RemoveAllRangesTemporarily();
2485 textEditor->SetText(newValue);
2487 // Call the listener's HandleValueChanged() callback manually, since
2488 // we don't use the transaction manager in this path and it could be
2489 // that the editor would bypass calling the listener for that reason.
2490 mTextListener->HandleValueChanged();
2493 mTextListener->SetValueChanged(true);
2494 mTextListener->SettingValue(false);
2496 if (!notifyValueChanged) {
2497 // Listener doesn't update frame, but it is required for placeholder
2498 ValueWasChanged(true);
2502 if (!weakFrame.IsAlive()) {
2503 // If the frame was destroyed because of a flush somewhere inside
2504 // InsertText, mBoundFrame here will be false. But it's also possible
2505 // for the frame to go away because of another reason (such as deleting
2506 // the existing selection -- see bug 574558), in which case we don't
2507 // need to reset the value here.
2508 if (!mBoundFrame) {
2509 return SetValue(newValue, aFlags & eSetValue_Notify);
2511 return true;
2514 // The new value never includes line breaks caused by hard-wrap.
2515 // So, mCachedValue can always cache the new value.
2516 if (!mBoundFrame->CacheValue(newValue, fallible)) {
2517 return false;
2521 } else {
2522 if (!mValue) {
2523 mValue.emplace();
2526 // We can't just early-return here if mValue->Equals(newValue), because
2527 // ValueWasChanged and OnValueChanged below still need to be called.
2528 if (!mValue->Equals(newValue) ||
2529 !nsContentUtils::SkipCursorMoveForSameValueSet()) {
2530 if (!mValue->Assign(newValue, fallible)) {
2531 return false;
2534 // Since we have no editor we presumably have cached selection state.
2535 if (IsSelectionCached()) {
2536 SelectionProperties& props = GetSelectionProperties();
2537 if (aFlags & eSetValue_MoveCursorToEndIfValueChanged) {
2538 props.SetStart(newValue.Length());
2539 props.SetEnd(newValue.Length());
2540 props.SetDirection(nsITextControlFrame::eForward);
2541 } else {
2542 // Make sure our cached selection position is not outside the new value.
2543 props.SetStart(std::min(props.GetStart(), newValue.Length()));
2544 props.SetEnd(std::min(props.GetEnd(), newValue.Length()));
2548 // Update the frame display if needed
2549 if (mBoundFrame) {
2550 mBoundFrame->UpdateValueDisplay(true);
2552 } else {
2553 // Even if our value is not actually changing, apparently we need to mark
2554 // our SelectionProperties dirty to make accessibility tests happy.
2555 // Probably because they depend on the SetSelectionRange() call we make on
2556 // our frame in RestoreSelectionState, but I have no idea why they do.
2557 if (IsSelectionCached()) {
2558 SelectionProperties& props = GetSelectionProperties();
2559 props.SetIsDirty();
2563 // If we've reached the point where the root node has been created, we
2564 // can assume that it's safe to notify.
2565 ValueWasChanged(!!mBoundFrame);
2568 mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
2569 /* aWasInteractiveUserChange = */ false);
2571 return true;
2574 bool
2575 nsTextEditorState::HasNonEmptyValue()
2577 if (mTextEditor && mBoundFrame && mEditorInitialized &&
2578 !mIsCommittingComposition) {
2579 bool empty;
2580 nsresult rv = mTextEditor->IsEmpty(&empty);
2581 if (NS_SUCCEEDED(rv)) {
2582 return !empty;
2586 nsAutoString value;
2587 GetValue(value, true);
2588 return !value.IsEmpty();
2591 void
2592 nsTextEditorState::InitializeKeyboardEventListeners()
2594 //register key listeners
2595 nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
2596 EventListenerManager* manager = target->GetOrCreateListenerManager();
2597 if (manager) {
2598 manager->AddEventListenerByType(mTextListener,
2599 NS_LITERAL_STRING("keydown"),
2600 TrustedEventsAtSystemGroupBubble());
2601 manager->AddEventListenerByType(mTextListener,
2602 NS_LITERAL_STRING("keypress"),
2603 TrustedEventsAtSystemGroupBubble());
2604 manager->AddEventListenerByType(mTextListener,
2605 NS_LITERAL_STRING("keyup"),
2606 TrustedEventsAtSystemGroupBubble());
2609 mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild()));
2612 void
2613 nsTextEditorState::ValueWasChanged(bool aNotify)
2615 UpdateOverlayTextVisibility(aNotify);
2618 void
2619 nsTextEditorState::SetPreviewText(const nsAString& aValue, bool aNotify)
2621 // If we don't have a preview div, there's nothing to do.
2622 Element* previewDiv = GetPreviewNode();
2623 if (!previewDiv)
2624 return;
2626 nsAutoString previewValue(aValue);
2628 nsContentUtils::RemoveNewlines(previewValue);
2629 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
2630 previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
2632 UpdateOverlayTextVisibility(aNotify);
2635 void
2636 nsTextEditorState::GetPreviewText(nsAString& aValue)
2638 // If we don't have a preview div, there's nothing to do.
2639 Element* previewDiv = GetPreviewNode();
2640 if (!previewDiv)
2641 return;
2643 MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
2644 const nsTextFragment *text = previewDiv->GetFirstChild()->GetText();
2646 aValue.Truncate();
2647 text->AppendTo(aValue);
2650 void
2651 nsTextEditorState::UpdateOverlayTextVisibility(bool aNotify)
2653 nsAutoString value, previewValue;
2654 bool valueIsEmpty = !HasNonEmptyValue();
2655 GetPreviewText(previewValue);
2657 mPreviewVisibility = valueIsEmpty && !previewValue.IsEmpty();
2658 mPlaceholderVisibility = valueIsEmpty && previewValue.IsEmpty();
2660 if (mPlaceholderVisibility &&
2661 !nsContentUtils::ShowInputPlaceholderOnFocus()) {
2662 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2663 mPlaceholderVisibility = !nsContentUtils::IsFocusedContent(content);
2666 if (mBoundFrame && aNotify) {
2667 mBoundFrame->InvalidateFrame();
2671 void
2672 nsTextEditorState::HideSelectionIfBlurred()
2674 MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!");
2675 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2676 if (!nsContentUtils::IsFocusedContent(content)) {
2677 mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
2681 bool
2682 nsTextEditorState::EditorHasComposition()
2684 return mTextEditor && mTextEditor->IsIMEComposing();