Bug 1554951 [wpt PR 17043] - wake-lock: Fix invalid types test in wakelock-type.https...
[gecko.git] / dom / html / nsTextEditorState.cpp
blob4a4e56518090c13e7523325112c447599e4c3d88
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 "nsView.h"
12 #include "nsCaret.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,
58 uint32_t aFlags) {
59 if (aEditorBase.Flags() == aFlags) {
60 return NS_OK;
62 return aEditorBase.SetFlags(aFlags);
65 class MOZ_STACK_CLASS AutoInputEventSuppresser final {
66 public:
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); }
81 private:
82 RefPtr<TextEditor> mTextEditor;
83 bool mOuterTransaction;
86 class RestoreSelectionState : public Runnable {
87 public:
88 RestoreSelectionState(nsTextEditorState* aState, nsTextControlFrame* aFrame)
89 : mozilla::Runnable("RestoreSelectionState"),
90 mFrame(aFrame),
91 mTextEditorState(aState) {}
93 NS_IMETHOD Run() override {
94 if (!mTextEditorState) {
95 return NS_OK;
98 AutoHideSelectionChanges hideSelectionChanges(
99 mFrame->GetConstFrameSelection());
101 if (mFrame) {
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();
120 return NS_OK;
123 // Let the text editor tell us we're no longer relevant - avoids use of
124 // AutoWeakFrame
125 void Revoke() {
126 mFrame = nullptr;
127 mTextEditorState = nullptr;
130 private:
131 nsTextControlFrame* mFrame;
132 nsTextEditorState* mTextEditorState;
135 class MOZ_RAII AutoRestoreEditorState final {
136 public:
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);
164 private:
165 TextEditor* mTextEditor;
166 uint32_t mSavedFlags;
167 int32_t mSavedMaxLength;
168 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
171 class MOZ_RAII AutoDisableUndo final {
172 public:
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");
186 ~AutoDisableUndo() {
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()) {
191 return;
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");
200 } else {
201 DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
202 NS_WARNING_ASSERTION(disabledUndoRedo,
203 "Failed to disable undo/redo transactions");
207 private:
208 TextEditor* mTextEditor;
209 int32_t mNumberOfMaximumTransactions;
210 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
213 /*static*/
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
222 nsAutoString wrap;
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)) {
229 case 0:
230 aWrapProp = eHTMLTextWrap_Hard;
231 break;
232 case 1:
233 aWrapProp = eHTMLTextWrap_Off;
234 break;
237 return true;
240 return false;
243 /*static*/
244 already_AddRefed<nsITextControlElement>
245 nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
246 if (!aHost) {
247 return nullptr;
250 nsCOMPtr<nsITextControlElement> parent =
251 do_QueryInterface(aHost->GetParent());
253 return parent.forget();
256 static bool SuppressEventHandlers(nsPresContext* aPresContext) {
257 bool suppressHandlers = false;
259 if (aPresContext) {
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 {
273 public:
274 explicit nsAnonDivObserver(nsTextEditorState* aTextEditorState)
275 : mTextEditorState(aTextEditorState) {}
276 NS_DECL_ISUPPORTS
277 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
278 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
279 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
280 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
282 private:
283 ~nsAnonDivObserver() {}
284 nsTextEditorState* mTextEditorState;
287 class nsTextInputSelectionImpl final : public nsSupportsWeakReference,
288 public nsISelectionController {
289 ~nsTextInputSelectionImpl() {}
291 public:
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;
331 MOZ_CAN_RUN_SCRIPT
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,
343 int16_t aEndOffset,
344 bool* aRetval) override;
346 private:
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)
359 NS_INTERFACE_MAP_END
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!
371 mLimiter = aLimiter;
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) {
391 return nullptr;
394 return mFrameSelection->GetSelection(aSelectionType);
397 NS_IMETHODIMP
398 nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle) {
399 if (!mFrameSelection) return NS_ERROR_NULL_POINTER;
401 mFrameSelection->SetDisplaySelection(aToggle);
402 return NS_OK;
405 NS_IMETHODIMP
406 nsTextInputSelectionImpl::GetDisplaySelection(int16_t* aToggle) {
407 if (!mFrameSelection) return NS_ERROR_NULL_POINTER;
409 *aToggle = mFrameSelection->GetDisplaySelection();
410 return NS_OK;
413 NS_IMETHODIMP
414 nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle) {
415 return NS_OK; // stub this out. not used in input
418 NS_IMETHODIMP
419 nsTextInputSelectionImpl::GetSelectionFlags(int16_t* aOutEnable) {
420 *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
421 return NS_OK;
424 NS_IMETHODIMP
425 nsTextInputSelectionImpl::GetSelectionFromScript(
426 RawSelectionType aRawSelectionType, Selection** aSelection) {
427 if (!mFrameSelection) return NS_ERROR_NULL_POINTER;
429 *aSelection =
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);
438 return NS_OK;
441 Selection* nsTextInputSelectionImpl::GetSelection(
442 RawSelectionType aRawSelectionType) {
443 return GetSelection(ToSelectionType(aRawSelectionType));
446 NS_IMETHODIMP
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);
456 NS_IMETHODIMP
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);
472 NS_IMETHODIMP
473 nsTextInputSelectionImpl::SetCaretEnabled(bool enabled) {
474 if (!mPresShellWeak) {
475 return NS_ERROR_NOT_INITIALIZED;
477 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak);
478 if (!presShell) {
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
484 // maintained.
485 presShell->SetCaretEnabled(enabled);
487 return NS_OK;
490 NS_IMETHODIMP
491 nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly) {
492 if (!mPresShellWeak) {
493 return NS_ERROR_NOT_INITIALIZED;
495 nsresult rv;
496 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
497 if (!presShell) {
498 return NS_ERROR_FAILURE;
500 RefPtr<nsCaret> caret = presShell->GetCaret();
501 if (!caret) {
502 return NS_ERROR_FAILURE;
504 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
505 if (selection) {
506 caret->SetCaretReadOnly(aReadOnly);
508 return NS_OK;
511 NS_IMETHODIMP
512 nsTextInputSelectionImpl::GetCaretEnabled(bool* _retval) {
513 return GetCaretVisible(_retval);
516 NS_IMETHODIMP
517 nsTextInputSelectionImpl::GetCaretVisible(bool* _retval) {
518 if (!mPresShellWeak) {
519 return NS_ERROR_NOT_INITIALIZED;
521 nsresult rv;
522 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
523 if (!presShell) {
524 return NS_ERROR_FAILURE;
526 RefPtr<nsCaret> caret = presShell->GetCaret();
527 if (!caret) {
528 return NS_ERROR_FAILURE;
530 *_retval = caret->IsVisible();
531 return NS_OK;
534 NS_IMETHODIMP
535 nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility) {
536 if (!mPresShellWeak) {
537 return NS_ERROR_NOT_INITIALIZED;
539 nsresult rv;
540 RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
541 if (!presShell) {
542 return NS_ERROR_FAILURE;
544 RefPtr<nsCaret> caret = presShell->GetCaret();
545 if (!caret) {
546 return NS_ERROR_FAILURE;
548 Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
549 if (selection) {
550 caret->SetVisibilityDuringSelection(aVisibility);
552 return NS_OK;
555 NS_IMETHODIMP
556 nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount,
557 bool aExtend) {
558 if (mFrameSelection) {
559 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
560 return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
562 return NS_ERROR_NULL_POINTER;
565 NS_IMETHODIMP
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;
574 NS_IMETHODIMP
575 nsTextInputSelectionImpl::CharacterExtendForDelete() {
576 if (mFrameSelection) {
577 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
578 return frameSelection->CharacterExtendForDelete();
580 return NS_ERROR_NULL_POINTER;
583 NS_IMETHODIMP
584 nsTextInputSelectionImpl::CharacterExtendForBackspace() {
585 if (mFrameSelection) {
586 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
587 return frameSelection->CharacterExtendForBackspace();
589 return NS_ERROR_NULL_POINTER;
592 NS_IMETHODIMP
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;
601 NS_IMETHODIMP
602 nsTextInputSelectionImpl::WordExtendForDelete(bool aForward) {
603 if (mFrameSelection) {
604 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
605 return frameSelection->WordExtendForDelete(aForward);
607 return NS_ERROR_NULL_POINTER;
610 NS_IMETHODIMP
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);
616 return result;
618 return NS_ERROR_NULL_POINTER;
621 NS_IMETHODIMP
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;
630 NS_IMETHODIMP
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.
634 if (mScrollFrame) {
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);
648 NS_IMETHODIMP
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);
654 return NS_OK;
657 NS_IMETHODIMP
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
667 int32_t offset = 0;
668 CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE;
669 if (aForward) {
670 offset = parentDIV->GetChildCount();
672 // Prevent the caret from being placed after the last
673 // BR node in the content tree!
675 if (offset > 0) {
676 nsIContent* child = parentDIV->GetLastChild();
678 if (child->IsHTMLElement(nsGkAtoms::br)) {
679 --offset;
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);
691 NS_IMETHODIMP
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);
697 return NS_OK;
700 NS_IMETHODIMP
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);
706 return NS_OK;
709 NS_IMETHODIMP
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);
715 return NS_OK;
718 NS_IMETHODIMP
719 nsTextInputSelectionImpl::SelectAll() {
720 if (mFrameSelection) {
721 RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
722 return frameSelection->SelectAll();
724 return NS_ERROR_NULL_POINTER;
727 NS_IMETHODIMP
728 nsTextInputSelectionImpl::CheckVisibility(nsINode* node, int16_t startOffset,
729 int16_t EndOffset, bool* _retval) {
730 if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
731 nsresult result;
732 nsCOMPtr<nsISelectionController> shell =
733 do_QueryReferent(mPresShellWeak, &result);
734 if (shell) {
735 return shell->CheckVisibility(node, startOffset, EndOffset, _retval);
737 return NS_ERROR_FAILURE;
740 nsresult nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode,
741 int16_t aStartOffset,
742 int16_t aEndOffset,
743 bool* aRetval) {
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,
752 aRetval);
756 * mozilla::TextInputListener implementation
759 TextInputListener::TextInputListener(nsITextControlElement* aTxtCtrlElement)
760 : mFrame(nullptr),
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)
777 NS_INTERFACE_MAP_END
779 NS_IMPL_CYCLE_COLLECTION_0(TextInputListener)
781 void TextInputListener::OnSelectionChange(Selection& aSelection,
782 int16_t aReason) {
783 if (!mListeningToSelectionChange) {
784 return;
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) {
821 return;
824 mSelectionWasCollapsed = collapsed;
826 if (!weakFrame.IsAlive() || !mFrame ||
827 !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
828 return;
831 UpdateTextInputCommands(NS_LITERAL_STRING("select"), &aSelection, aReason);
834 MOZ_CAN_RUN_SCRIPT
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);
841 if (input) {
842 input->GetControllers(getter_AddRefs(controllers));
843 } else {
844 HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(content);
846 if (textArea) {
847 textArea->GetControllers(getter_AddRefs(controllers));
851 if (!controllers) {
852 NS_WARNING("Could not get controllers");
853 return;
856 const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
858 nsCOMPtr<nsIController> controller;
859 controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
860 if (!controller) {
861 return;
864 bool commandEnabled;
865 nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
866 NS_ENSURE_SUCCESS_VOID(rv);
867 if (commandEnabled) {
868 controller->DoCommand(commandStr);
872 NS_IMETHODIMP
873 TextInputListener::HandleEvent(Event* aEvent) {
874 if (aEvent->DefaultPrevented()) {
875 return NS_OK;
878 if (!aEvent->IsTrusted()) {
879 return NS_OK;
882 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
883 if (!keyEvent) {
884 return NS_ERROR_UNEXPECTED;
887 WidgetKeyboardEvent* widgetKeyEvent =
888 aEvent->WidgetEventPtr()->AsKeyboardEvent();
889 if (!keyEvent) {
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)) {
902 continue;
905 if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) {
906 continue;
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)) {
914 return rv;
918 if (widgetKeyEvent->mMessage != eKeyPress) {
919 return NS_OK;
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.
929 if (!widget) {
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();
944 return NS_OK;
947 void TextInputListener::OnEditActionHandled() {
948 if (!mFrame) {
949 // We've been disconnected from the nsTextEditorState object, nothing to do
950 // here.
951 return;
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()) {
977 return;
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) {
987 if (!aFrame) {
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,
1003 int16_t aReason) {
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);
1014 return NS_OK;
1017 // END mozilla::TextInputListener
1019 // nsTextEditorState
1021 nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
1022 : mTextCtrlElement(aOwningElement),
1023 mBoundFrame(nullptr),
1024 mEverInited(false),
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.
1058 return state;
1061 return new nsTextEditorState(aOwningElement);
1064 nsTextEditorState::~nsTextEditorState() {
1065 MOZ_COUNT_DTOR(nsTextEditorState);
1066 Clear();
1069 Element* nsTextEditorState::GetRootNode() {
1070 return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1073 Element* nsTextEditorState::GetPreviewNode() {
1074 return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1077 void nsTextEditorState::Clear() {
1078 if (mTextEditor) {
1079 mTextEditor->SetTextInputListener(nullptr);
1082 if (mBoundFrame) {
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
1087 // properly.
1088 UnbindFromFrame(mBoundFrame);
1089 mTextEditor = nullptr;
1090 } else {
1091 // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1092 // for us.
1093 DestroyEditor();
1095 mTextListener = nullptr;
1098 void nsTextEditorState::Unlink() {
1099 nsTextEditorState* tmp = this;
1100 tmp->Clear();
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();
1113 return nullptr;
1116 TextEditor* nsTextEditorState::GetTextEditor() {
1117 if (!mTextEditor) {
1118 nsresult rv = PrepareEditor();
1119 NS_ENSURE_SUCCESS(rv, nullptr);
1121 return mTextEditor;
1124 TextEditor* nsTextEditorState::GetTextEditorWithoutCreation() {
1125 return mTextEditor;
1128 nsISelectionController* nsTextEditorState::GetSelectionController() const {
1129 return mSelCon;
1132 // Helper class, used below in BindToFrame().
1133 class PrepareEditorEvent : public Runnable {
1134 public:
1135 PrepareEditorEvent(nsTextEditorState& aState, nsIContent* aOwnerContent,
1136 const nsAString& aCurrentValue)
1137 : mozilla::Runnable("PrepareEditorEvent"),
1138 mState(&aState),
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;
1159 return NS_OK;
1162 private:
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;
1178 if (mTextEditor) {
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);
1190 // Create selection
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);
1206 if (selection) {
1207 RefPtr<nsCaret> caret = presShell->GetCaret();
1208 if (caret) {
1209 selection->AddSelectionListener(caret);
1211 mTextListener->StartToListenToSelectionChange();
1214 // If an editor exists from before, prepare it for usage
1215 if (mTextEditor) {
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);
1226 } else {
1227 // otherwise, inherit the content node's direction
1230 nsContentUtils::AddScriptRunner(
1231 new PrepareEditorEvent(*this, content, currentValue));
1234 return NS_OK;
1237 struct MOZ_STACK_CLASS PreDestroyer {
1238 void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
1239 MOZ_CAN_RUN_SCRIPT ~PreDestroyer() {
1240 if (mTextEditor) {
1241 MOZ_KnownLive(mTextEditor)->PreDestroy(true);
1244 void Swap(RefPtr<TextEditor>& aTextEditor) {
1245 return mTextEditor.swap(aTextEditor);
1248 private:
1249 RefPtr<TextEditor> mTextEditor;
1252 nsresult nsTextEditorState::PrepareEditor(const nsAString* aValue) {
1253 if (!mBoundFrame) {
1254 // Cannot create an editor without a bound frame.
1255 // Don't return a failure code, because js callers can't handle that.
1256 return NS_OK;
1259 if (mEditorInitialized) {
1260 // Do not initialize the editor multiple times.
1261 return NS_OK;
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;
1297 if (!mTextEditor) {
1298 shouldInitializeEditor = true;
1300 // Create an editor
1301 newTextEditor = new TextEditor();
1302 preDestroyer.Init(newTextEditor);
1304 // Make sure we clear out the non-breaking space before we initialize the
1305 // editor
1306 rv = mBoundFrame->UpdateValueDisplay(true, true);
1307 NS_ENSURE_SUCCESS(rv, rv);
1308 } else {
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;
1327 if (aValue) {
1328 defaultValue = *aValue;
1329 } else {
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);
1367 if (inputElement) {
1368 rv = inputElement->GetControllers(getter_AddRefs(controllers));
1369 } else {
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);
1380 if (controllers) {
1381 uint32_t numControllers;
1382 bool found = false;
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));
1393 found = true;
1397 if (!found) rv = NS_ERROR_FAILURE;
1401 // Initialize the plaintext editor
1402 if (shouldInitializeEditor) {
1403 // Set up wrapping
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
1438 // editor for us.
1440 if (!defaultValue.IsEmpty()) {
1441 rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1442 if (NS_WARN_IF(NS_FAILED(rv))) {
1443 return 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))) {
1458 return 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
1467 // changes here.
1468 DebugOnly<bool> disabledUndoRedo = newTextEditor->DisableUndoRedo();
1469 NS_WARNING_ASSERTION(disabledUndoRedo,
1470 "Failed to disable undo/redo transaction");
1471 } else {
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();
1480 mEverInited = true;
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.
1506 if (number) {
1507 number->ClearSelectionCached();
1508 } else {
1509 mSelectionCached = false;
1512 return rv;
1515 void nsTextEditorState::FinishedRestoringSelection() {
1516 mRestoringSelection = nullptr;
1519 bool nsTextEditorState::IsSelectionCached() const {
1520 if (mBoundFrame) {
1521 HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1522 if (number) {
1523 return number->IsSelectionCached();
1526 return mSelectionCached;
1529 nsTextEditorState::SelectionProperties&
1530 nsTextEditorState::GetSelectionProperties() {
1531 if (mBoundFrame) {
1532 HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1533 if (number) {
1534 return number->GetSelectionProperties();
1537 return mSelectionProperties;
1540 void nsTextEditorState::SyncUpSelectionPropertiesBeforeDestruction() {
1541 if (mBoundFrame) {
1542 UnbindFromFrame(mBoundFrame);
1546 void nsTextEditorState::SetSelectionProperties(
1547 nsTextEditorState::SelectionProperties& aProps) {
1548 if (mBoundFrame) {
1549 mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
1550 aProps.GetDirection());
1551 } else {
1552 mSelectionProperties = aProps;
1556 void nsTextEditorState::GetSelectionRange(uint32_t* aSelectionStart,
1557 uint32_t* aSelectionEnd,
1558 ErrorResult& aRv) {
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 "
1563 "controller?");
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();
1571 return;
1574 Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
1575 if (NS_WARN_IF(!sel)) {
1576 aRv.Throw(NS_ERROR_FAILURE);
1577 return;
1580 mozilla::dom::Element* root = GetRootNode();
1581 if (NS_WARN_IF(!root)) {
1582 aRv.Throw(NS_ERROR_UNEXPECTED);
1583 return;
1585 nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
1586 *aSelectionEnd);
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 "
1593 "controller?");
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) {
1623 aStart = aEnd;
1626 bool changed = false;
1627 nsresult rv = NS_OK; // For the ScrollSelectionIntoView() return value.
1628 if (IsSelectionCached()) {
1629 nsAutoString value;
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) {
1635 aStart = length;
1637 if (aEnd > length) {
1638 aEnd = length;
1640 SelectionProperties& props = GetSelectionProperties();
1641 changed = props.GetStart() != aStart || props.GetEnd() != aEnd ||
1642 props.GetDirection() != aDirection;
1643 props.SetStart(aStart);
1644 props.SetEnd(aEnd);
1645 props.SetDirection(aDirection);
1646 } else {
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()) {
1651 return;
1653 if (mBoundFrame) {
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
1659 // failures?
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
1663 // fixing that...
1664 changed = true;
1667 if (changed) {
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)) {
1677 aRv.Throw(rv);
1681 void nsTextEditorState::SetSelectionStart(const Nullable<uint32_t>& aStart,
1682 ErrorResult& aRv) {
1683 uint32_t start = 0;
1684 if (!aStart.IsNull()) {
1685 start = aStart.Value();
1688 uint32_t ignored, end;
1689 GetSelectionRange(&ignored, &end, aRv);
1690 if (aRv.Failed()) {
1691 return;
1694 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1695 if (aRv.Failed()) {
1696 return;
1699 if (end < start) {
1700 end = start;
1703 SetSelectionRange(start, end, dir, aRv);
1706 void nsTextEditorState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
1707 ErrorResult& aRv) {
1708 uint32_t end = 0;
1709 if (!aEnd.IsNull()) {
1710 end = aEnd.Value();
1713 uint32_t start, ignored;
1714 GetSelectionRange(&start, &ignored, aRv);
1715 if (aRv.Failed()) {
1716 return;
1719 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1720 if (aRv.Failed()) {
1721 return;
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");
1738 } else {
1739 MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
1743 void nsTextEditorState::GetSelectionDirectionString(nsAString& aDirection,
1744 ErrorResult& aRv) {
1745 nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1746 if (aRv.Failed()) {
1747 return;
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,
1763 ErrorResult& aRv) {
1764 nsITextControlFrame::SelectionDirection dir =
1765 DirectionStringToSelectionDirection(aDirection);
1767 if (IsSelectionCached()) {
1768 GetSelectionProperties().SetDirection(dir);
1769 return;
1772 uint32_t start, end;
1773 GetSelectionRange(&start, &end, aRv);
1774 if (aRv.Failed()) {
1775 return;
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,
1794 ErrorResult& aRv) {
1795 nsITextControlFrame::SelectionDirection dir =
1796 DirectionStringToSelectionDirection(aDirection);
1798 SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv);
1801 void nsTextEditorState::SetRangeText(const nsAString& aReplacement,
1802 ErrorResult& aRv) {
1803 uint32_t start, end;
1804 GetSelectionRange(&start, &end, aRv);
1805 if (aRv.Failed()) {
1806 return;
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,
1816 ErrorResult& aRv,
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);
1821 return;
1824 nsAutoString value;
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);
1840 if (aRv.Failed()) {
1841 return;
1843 } else {
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)) {
1853 aRv.Throw(rv);
1854 return;
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;
1864 } break;
1865 case mozilla::dom::SelectionMode::Start: {
1866 selectionStart = selectionEnd = aStart;
1867 } break;
1868 case mozilla::dom::SelectionMode::End: {
1869 selectionStart = selectionEnd = newEnd;
1870 } break;
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;
1883 } break;
1884 default:
1885 MOZ_CRASH("Unknown mode!");
1888 SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
1891 HTMLInputElement* nsTextEditorState::GetParentNumberControl(
1892 nsFrame* aFrame) const {
1893 MOZ_ASSERT(aFrame);
1894 nsIContent* content = aFrame->GetContent();
1895 MOZ_ASSERT(content);
1896 nsIContent* parent = content->GetParent();
1897 if (!parent) {
1898 return nullptr;
1900 nsIContent* parentOfParent = parent->GetParent();
1901 if (!parentOfParent) {
1902 return nullptr;
1904 HTMLInputElement* input = HTMLInputElement::FromNode(parentOfParent);
1905 if (input) {
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;
1914 return 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.
1935 nsAutoString value;
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);
1958 props.SetEnd(end);
1959 props.SetDirection(direction);
1960 HTMLInputElement* number = GetParentNumberControl(aFrame);
1961 if (number) {
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();
1966 } else {
1967 mSelectionCached = true;
1971 // Destroy our editor
1972 DestroyEditor();
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);
1979 if (inputElement)
1980 inputElement->GetControllers(getter_AddRefs(controllers));
1981 else {
1982 HTMLTextAreaElement* textAreaElement =
1983 HTMLTextAreaElement::FromNodeOrNull(content);
1984 if (textAreaElement) {
1985 textAreaElement->GetControllers(getter_AddRefs(controllers));
1989 if (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);
2008 if (mSelCon) {
2009 if (mTextListener) {
2010 mTextListener->EndListeningToSelectionChange();
2013 mSelCon->SetScrollableFrame(nullptr);
2014 mSelCon = nullptr;
2017 if (mTextListener) {
2018 mTextListener->SetFrame(nullptr);
2020 nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
2021 EventListenerManager* manager = target->GetExistingListenerManager();
2022 if (manager) {
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
2041 // progress.
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)) {
2053 return -1;
2056 const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength);
2057 if (attr && attr->Type() == nsAttrValue::eInteger) {
2058 return attr->GetIntegerValue();
2061 return -1;
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;
2071 return;
2074 if (mTextEditor && mBoundFrame &&
2075 (mEditorInitialized || !IsSingleLineTextControl())) {
2076 if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2077 aValue = mBoundFrame->CachedValue();
2078 return;
2081 aValue.Truncate(); // initialize out param
2083 uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2084 nsIDocumentEncoder::OutputPreformatted |
2085 nsIDocumentEncoder::OutputPersistNBSP |
2086 nsIDocumentEncoder::OutputBodyOnly);
2087 if (!aIgnoreWrap) {
2088 nsITextControlElement::nsHTMLTextWrap wrapProp;
2089 nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2090 if (content &&
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
2108 // this.
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);
2119 } else {
2120 mBoundFrame->ClearCachedValue();
2122 } else {
2123 if (!mTextCtrlElement->ValueChanged() || !mValue) {
2124 mTextCtrlElement->GetDefaultValueFromContent(aValue);
2125 } else {
2126 aValue = *mValue;
2131 #ifdef DEBUG
2132 namespace {
2133 // @param aFlags nsTextEditorState::SetValueFlags
2134 bool AreFlagsNotDemandingContradictingMovements(uint32_t aFlags) {
2135 return !(
2136 !!(aFlags &
2137 nsTextEditorState::
2138 eSetValue_MoveCursorToBeginSetSelectionDirectionForward) &&
2139 !!(aFlags & nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged));
2141 } // anonymous namespace
2142 #endif // DEBUG
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.
2172 return true;
2174 if (NS_WARN_IF(!mBoundFrame)) {
2175 // We're not sure if this case is possible.
2176 } else {
2177 // If setting value won't change current value, we shouldn't commit
2178 // composition for compatibility with the other browsers.
2179 nsAutoString currentValue;
2180 if (aOldValue) {
2181 #ifdef DEBUG
2182 mBoundFrame->GetText(currentValue);
2183 MOZ_ASSERT(currentValue.Equals(*aOldValue));
2184 #endif
2185 currentValue.Assign(*aOldValue);
2186 } else {
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).
2194 return true;
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();
2216 if (!self.get()) {
2217 return true;
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");
2229 return true;
2231 } else {
2232 NS_WARNING(
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)) {
2242 return false;
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;
2257 #ifdef DEBUG
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");
2263 #endif
2265 nsAutoString currentValue;
2266 if (aOldValue) {
2267 #ifdef DEBUG
2268 mBoundFrame->GetText(currentValue);
2269 MOZ_ASSERT(currentValue.Equals(*aOldValue));
2270 #endif
2271 currentValue.Assign(*aOldValue);
2272 } else {
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)) {
2285 return true;
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())) {
2300 return true;
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
2323 // do it.
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
2330 // "input" event.
2331 suppressInputEventDispatching.Init();
2333 // On XUL <textbox> element, we need to preserve existing undo
2334 // transactions.
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:
2338 // for (;;) {
2339 // textarea.value += oneLineText + "\n";
2340 // }
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.
2347 currentLength = 0;
2348 kungFuDeathGrip->SelectAll();
2349 } else {
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
2359 // now.
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");
2364 } else {
2365 // In this case, we makes the editor stop dispatching "input"
2366 // event so that passing nullptr as nsIPrincipal is safe for
2367 // now.
2368 DebugOnly<nsresult> rv =
2369 textEditor->InsertTextAsAction(insertValue, nullptr);
2370 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2371 "Failed to insert the new value");
2373 } else {
2374 // When setting value of <input>, we shouldn't dispatch "input"
2375 // event.
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);
2382 if (selection) {
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
2397 // reason.
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.
2416 if (!mBoundFrame) {
2417 return SetValue(newValue, aFlags & eSetValue_Notify);
2419 return true;
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)) {
2425 return false;
2429 } else {
2430 if (!mValue) {
2431 mValue.emplace();
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)) {
2439 return false;
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);
2451 } else if (aFlags &
2452 eSetValue_MoveCursorToBeginSetSelectionDirectionForward) {
2453 props.SetStart(0);
2454 props.SetEnd(0);
2455 props.SetDirection(nsITextControlFrame::eForward);
2456 } else {
2457 // Make sure our cached selection position is not outside the new
2458 // value.
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
2465 if (mBoundFrame) {
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");
2483 } else {
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();
2490 props.SetIsDirty();
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
2505 // been cleared?
2506 textControlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame, changeKind);
2507 return true;
2510 bool nsTextEditorState::HasNonEmptyValue() {
2511 if (mTextEditor && mBoundFrame && mEditorInitialized &&
2512 !mIsCommittingComposition) {
2513 bool empty;
2514 nsresult rv = mTextEditor->IsEmpty(&empty);
2515 if (NS_SUCCEEDED(rv)) {
2516 return !empty;
2520 nsAutoString value;
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();
2529 if (manager) {
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();
2569 aValue.Truncate();
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();