1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "TextEditor.h"
10 #include "EditAction.h"
11 #include "EditAggregateTransaction.h"
12 #include "EditorDOMPoint.h"
13 #include "HTMLEditor.h"
14 #include "HTMLEditUtils.h"
15 #include "InternetCiter.h"
16 #include "PlaceholderTransaction.h"
17 #include "gfxFontUtils.h"
19 #include "mozilla/dom/DocumentInlines.h"
20 #include "mozilla/Assertions.h"
21 #include "mozilla/ContentIterator.h"
22 #include "mozilla/IMEStateManager.h"
23 #include "mozilla/LookAndFeel.h"
24 #include "mozilla/mozalloc.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/StaticPrefs_editor.h"
28 #include "mozilla/TextComposition.h"
29 #include "mozilla/TextEvents.h"
30 #include "mozilla/TextServicesDocument.h"
31 #include "mozilla/dom/Event.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/Selection.h"
34 #include "mozilla/dom/StaticRange.h"
36 #include "nsAString.h"
39 #include "nsCharTraits.h"
40 #include "nsComponentManagerUtils.h"
41 #include "nsContentCID.h"
42 #include "nsContentList.h"
44 #include "nsDependentSubstring.h"
46 #include "nsGkAtoms.h"
47 #include "nsIClipboard.h"
48 #include "nsIContent.h"
50 #include "nsIPrincipal.h"
51 #include "nsISelectionController.h"
52 #include "nsISupportsPrimitives.h"
53 #include "nsITransferable.h"
54 #include "nsIWeakReferenceUtils.h"
55 #include "nsNameSpaceManager.h"
56 #include "nsLiteralString.h"
57 #include "nsPresContext.h"
58 #include "nsReadableUtils.h"
59 #include "nsServiceManagerUtils.h"
61 #include "nsStringFwd.h"
62 #include "nsTextFragment.h"
63 #include "nsTextNode.h"
64 #include "nsUnicharUtils.h"
67 class nsIOutputStream
;
74 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
75 using LeafNodeTypes
= HTMLEditUtils::LeafNodeTypes
;
77 TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text
) {
78 // printf("Size of TextEditor: %zu\n", sizeof(TextEditor));
80 sizeof(TextEditor
) <= 512,
81 "TextEditor instance should be allocatable in the quantum class bins");
84 TextEditor::~TextEditor() {
85 // Remove event listeners. Note that if we had an HTML editor,
86 // it installed its own instead of these
87 RemoveEventListeners();
90 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor
)
92 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor
, EditorBase
)
93 if (tmp
->mPasswordMaskData
) {
94 tmp
->mPasswordMaskData
->CancelTimer(PasswordMaskData::ReleaseTimer::No
);
95 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPasswordMaskData
->mTimer
)
97 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor
, EditorBase
)
100 if (tmp
->mPasswordMaskData
) {
101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPasswordMaskData
->mTimer
)
103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
105 NS_IMPL_ADDREF_INHERITED(TextEditor
, EditorBase
)
106 NS_IMPL_RELEASE_INHERITED(TextEditor
, EditorBase
)
108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor
)
109 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
110 NS_INTERFACE_MAP_ENTRY(nsINamed
)
111 NS_INTERFACE_MAP_END_INHERITING(EditorBase
)
113 NS_IMETHODIMP
TextEditor::EndOfDocument() {
114 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
115 if (NS_WARN_IF(!editActionData
.CanHandle())) {
116 return NS_ERROR_NOT_INITIALIZED
;
118 nsresult rv
= CollapseSelectionToEndOfTextNode();
119 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
120 "TextEditor::CollapseSelectionToEndOfTextNode() failed");
121 // This is low level API for embedders and chrome script so that we can return
122 // raw error code here.
126 nsresult
TextEditor::CollapseSelectionToEndOfTextNode() {
127 MOZ_ASSERT(IsEditActionDataAvailable());
129 Element
* anonymousDivElement
= GetRoot();
130 if (NS_WARN_IF(!anonymousDivElement
)) {
131 return NS_ERROR_NULL_POINTER
;
134 RefPtr
<Text
> textNode
=
135 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
136 MOZ_ASSERT(textNode
);
137 nsresult rv
= CollapseSelectionToEndOf(*textNode
);
138 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
139 "EditorBase::CollapseSelectionToEndOf() failed");
143 nsresult
TextEditor::Init(Document
& aDocument
, Element
& aAnonymousDivElement
,
144 nsISelectionController
& aSelectionController
,
146 UniquePtr
<PasswordMaskData
>&& aPasswordMaskData
) {
147 MOZ_ASSERT(!mInitSucceeded
,
148 "TextEditor::Init() called again without calling PreDestroy()?");
149 MOZ_ASSERT(!(aFlags
& nsIEditor::eEditorPasswordMask
) == !aPasswordMaskData
);
150 mPasswordMaskData
= std::move(aPasswordMaskData
);
152 // Init the base editor
153 nsresult rv
= InitInternal(aDocument
, &aAnonymousDivElement
,
154 aSelectionController
, aFlags
);
156 NS_WARNING("EditorBase::InitInternal() failed");
160 AutoEditActionDataSetter
editActionData(*this, EditAction::eInitializing
);
161 if (NS_WARN_IF(!editActionData
.CanHandle())) {
162 return NS_ERROR_FAILURE
;
165 // We set mInitSucceeded here rather than at the end of the function,
166 // since InitEditorContentAndSelection() can perform some transactions
167 // and can warn if mInitSucceeded is still false.
168 MOZ_ASSERT(!mInitSucceeded
, "TextEditor::Init() shouldn't be nested");
169 mInitSucceeded
= true;
171 rv
= InitEditorContentAndSelection();
173 NS_WARNING("TextEditor::InitEditorContentAndSelection() failed");
174 // XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
175 // is a public method?
176 mInitSucceeded
= false;
177 return EditorBase::ToGenericNSResult(rv
);
180 // Throw away the old transaction manager if this is not the first time that
181 // we're initializing the editor.
187 nsresult
TextEditor::InitEditorContentAndSelection() {
188 MOZ_ASSERT(IsEditActionDataAvailable());
190 MOZ_TRY(EnsureEmptyTextFirstChild());
192 // If the selection hasn't been set up yet, set it up collapsed to the end of
193 // our editable content.
194 if (!SelectionRef().RangeCount()) {
195 nsresult rv
= CollapseSelectionToEndOfTextNode();
197 NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed");
202 if (!IsSingleLineEditor()) {
203 nsresult rv
= EnsurePaddingBRElementInMultilineEditor();
206 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
214 nsresult
TextEditor::PostCreate() {
215 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
216 if (NS_WARN_IF(!editActionData
.CanHandle())) {
217 return NS_ERROR_NOT_INITIALIZED
;
220 nsresult rv
= PostCreateInternal();
222 // Restore unmasked range if there is.
223 if (IsPasswordEditor() && !IsAllMasked()) {
224 DebugOnly
<nsresult
> rvIgnored
=
225 SetUnmaskRangeAndNotify(UnmaskedStart(), UnmaskedLength());
226 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
227 "TextEditor::SetUnmaskRangeAndNotify() failed to "
228 "restore unmasked range, but ignored");
230 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
231 "EditorBase::PostCreateInternal() failed");
235 UniquePtr
<PasswordMaskData
> TextEditor::PreDestroy() {
236 if (mDidPreDestroy
) {
240 UniquePtr
<PasswordMaskData
> passwordMaskData
= std::move(mPasswordMaskData
);
241 if (passwordMaskData
) {
242 // Disable auto-masking timer since nobody can catch the notification
243 // from the timer and canceling the unmasking.
244 passwordMaskData
->CancelTimer(PasswordMaskData::ReleaseTimer::Yes
);
245 // Similary, keeping preventing echoing password temporarily across
246 // TextEditor instances is hard. So, we should forget it.
247 passwordMaskData
->mEchoingPasswordPrevented
= false;
250 PreDestroyInternal();
252 return passwordMaskData
;
255 nsresult
TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent
* aKeyboardEvent
) {
256 // NOTE: When you change this method, you should also change:
257 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
258 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
260 // And also when you add new key handling, you need to change the subclass's
261 // HandleKeyPressEvent()'s switch statement.
263 if (NS_WARN_IF(!aKeyboardEvent
)) {
264 return NS_ERROR_UNEXPECTED
;
268 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent
);
272 MOZ_ASSERT(aKeyboardEvent
->mMessage
== eKeyPress
,
273 "HandleKeyPressEvent gets non-keypress event");
275 switch (aKeyboardEvent
->mKeyCode
) {
281 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress
283 aKeyboardEvent
->PreventDefault();
289 nsresult rv
= EditorBase::HandleKeyPressEvent(aKeyboardEvent
);
290 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
291 "EditorBase::HandleKeyPressEvent() failed");
295 if (!aKeyboardEvent
->IsInputtingLineBreak()) {
298 if (!IsSingleLineEditor()) {
299 aKeyboardEvent
->PreventDefault();
301 // We need to dispatch "beforeinput" event at least even if we're a
302 // single line text editor.
303 nsresult rv
= InsertLineBreakAsAction();
304 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
305 "TextEditor::InsertLineBreakAsAction() failed");
310 if (!aKeyboardEvent
->IsInputtingText()) {
311 // we don't PreventDefault() here or keybindings like control-x won't work
314 // Our widget shouldn't set `\r` to `mCharCode`, but it may be synthesized
315 // keyboard event and its value may be `\r`. In such case, we should treat
316 // it as `\n` for the backward compatibility because we stopped converting
317 // `\r` and `\r\n` to `\n` at getting `HTMLInputElement.value` and
318 // `HTMLTextAreaElement.value` for the performance (i.e., we don't need to
319 // take care in `HTMLEditor`).
321 static_cast<char16_t
>(aKeyboardEvent
->mCharCode
) == nsCRT::CR
323 : static_cast<char16_t
>(aKeyboardEvent
->mCharCode
);
324 aKeyboardEvent
->PreventDefault();
325 nsAutoString
str(charCode
);
326 nsresult rv
= OnInputText(str
);
327 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "EditorBase::OnInputText() failed");
331 NS_IMETHODIMP
TextEditor::InsertLineBreak() {
332 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertLineBreak
);
333 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
335 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
336 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
337 return EditorBase::ToGenericNSResult(rv
);
340 if (NS_WARN_IF(IsSingleLineEditor())) {
341 return NS_ERROR_FAILURE
;
344 AutoPlaceholderBatch
treatAsOneTransaction(
345 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
346 rv
= InsertLineBreakAsSubAction();
347 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
348 "TextEditor::InsertLineBreakAsSubAction() failed");
349 return EditorBase::ToGenericNSResult(rv
);
352 nsresult
TextEditor::InsertLineBreakAsAction(nsIPrincipal
* aPrincipal
) {
353 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertLineBreak
,
355 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
357 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
358 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
359 return EditorBase::ToGenericNSResult(rv
);
362 if (IsSingleLineEditor()) {
366 // XXX This may be called by execCommand() with "insertParagraph".
367 // In such case, naming the transaction "TypingTxnName" is odd.
368 AutoPlaceholderBatch
treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName
,
369 ScrollSelectionIntoView::Yes
,
371 rv
= InsertLineBreakAsSubAction();
372 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
373 "EditorBase::InsertLineBreakAsSubAction() failed");
374 return EditorBase::ToGenericNSResult(rv
);
377 nsresult
TextEditor::SetTextAsAction(
378 const nsAString
& aString
,
379 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable
,
380 nsIPrincipal
* aPrincipal
) {
381 MOZ_ASSERT(aString
.FindChar(nsCRT::CR
) == kNotFound
);
383 AutoEditActionDataSetter
editActionData(*this, EditAction::eSetText
,
385 if (aAllowBeforeInputEventCancelable
== AllowBeforeInputEventCancelable::No
) {
386 editActionData
.MakeBeforeInputEventNonCancelable();
388 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
390 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
391 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
392 return EditorBase::ToGenericNSResult(rv
);
395 AutoPlaceholderBatch
treatAsOneTransaction(
396 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
397 rv
= SetTextAsSubAction(aString
);
398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
399 "TextEditor::SetTextAsSubAction() failed");
400 return EditorBase::ToGenericNSResult(rv
);
403 nsresult
TextEditor::SetTextAsSubAction(const nsAString
& aString
) {
404 MOZ_ASSERT(IsEditActionDataAvailable());
405 MOZ_ASSERT(mPlaceholderBatch
);
407 if (NS_WARN_IF(!mInitSucceeded
)) {
408 return NS_ERROR_NOT_INITIALIZED
;
411 IgnoredErrorResult ignoredError
;
412 AutoEditSubActionNotifier
startToHandleEditSubAction(
413 *this, EditSubAction::eSetText
, nsIEditor::eNext
, ignoredError
);
414 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
415 return ignoredError
.StealNSResult();
417 NS_WARNING_ASSERTION(
418 !ignoredError
.Failed(),
419 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
421 if (!IsIMEComposing() && !IsUndoRedoEnabled() &&
422 GetEditAction() != EditAction::eReplaceText
&& mMaxTextLength
< 0) {
423 Result
<EditActionResult
, nsresult
> result
=
424 SetTextWithoutTransaction(aString
);
425 if (MOZ_UNLIKELY(result
.isErr())) {
426 NS_WARNING("TextEditor::SetTextWithoutTransaction() failed");
427 return result
.unwrapErr();
429 if (!result
.inspect().Ignored()) {
435 // Note that do not notify selectionchange caused by selecting all text
436 // because it's preparation of our delete implementation so web apps
437 // shouldn't receive such selectionchange before the first mutation.
438 AutoUpdateViewBatch
preventSelectionChangeEvent(*this, __FUNCTION__
);
440 // XXX We should make ReplaceSelectionAsSubAction() take range. Then,
441 // we can saving the expensive cost of modifying `Selection` here.
442 if (NS_SUCCEEDED(SelectEntireDocument())) {
443 DebugOnly
<nsresult
> rvIgnored
= ReplaceSelectionAsSubAction(aString
);
444 NS_WARNING_ASSERTION(
445 NS_SUCCEEDED(rvIgnored
),
446 "EditorBase::ReplaceSelectionAsSubAction() failed, but ignored");
450 // Destroying AutoUpdateViewBatch may cause destroying us.
451 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
: NS_OK
;
454 already_AddRefed
<Element
> TextEditor::GetInputEventTargetElement() const {
455 RefPtr
<Element
> target
= Element::FromEventTargetOrNull(mEventTarget
);
456 return target
.forget();
459 bool TextEditor::IsEmpty() const {
460 // Even if there is no padding <br> element for empty editor, we should be
461 // detected as empty editor if all the children are text nodes and these
463 Element
* anonymousDivElement
= GetRoot();
464 if (!anonymousDivElement
) {
465 return true; // Don't warn it, this is possible, e.g., 997805.html
468 MOZ_ASSERT(anonymousDivElement
->GetFirstChild() &&
469 anonymousDivElement
->GetFirstChild()->IsText());
471 // Only when there is non-empty text node, we are not empty.
472 return !anonymousDivElement
->GetFirstChild()->Length();
475 NS_IMETHODIMP
TextEditor::GetTextLength(uint32_t* aCount
) {
478 // initialize out params
481 // special-case for empty document, to account for the padding <br> element
483 // XXX This should be overridden by `HTMLEditor` and we should return the
484 // first text node's length from `TextEditor` instead. The following
485 // code is too expensive.
490 Element
* rootElement
= GetRoot();
491 if (NS_WARN_IF(!rootElement
)) {
492 return NS_ERROR_FAILURE
;
495 uint32_t totalLength
= 0;
496 PostContentIterator postOrderIter
;
497 DebugOnly
<nsresult
> rvIgnored
= postOrderIter
.Init(rootElement
);
498 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
499 "PostContentIterator::Init() failed, but ignored");
500 EditorType editorType
= GetEditorType();
501 for (; !postOrderIter
.IsDone(); postOrderIter
.Next()) {
502 nsINode
* currentNode
= postOrderIter
.GetCurrentNode();
503 if (currentNode
&& currentNode
->IsText() &&
504 EditorUtils::IsEditableContent(*currentNode
->AsText(), editorType
)) {
505 totalLength
+= currentNode
->Length();
509 *aCount
= totalLength
;
513 bool TextEditor::IsCopyToClipboardAllowedInternal() const {
514 MOZ_ASSERT(IsEditActionDataAvailable());
515 if (!EditorBase::IsCopyToClipboardAllowedInternal()) {
519 if (!IsSingleLineEditor() || !IsPasswordEditor() ||
520 NS_WARN_IF(!mPasswordMaskData
)) {
524 // If we're a password editor, we should allow selected text to be copied
525 // to the clipboard only when selection range is in unmasked range.
526 if (IsAllMasked() || IsMaskingPassword() || !UnmaskedLength()) {
530 // If there are 2 or more ranges, we don't allow to copy/cut for now since
531 // we need to check whether all ranges are in unmasked range or not.
532 // Anyway, such operation in password field does not make sense.
533 if (SelectionRef().RangeCount() > 1) {
537 uint32_t selectionStart
= 0, selectionEnd
= 0;
538 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), mRootElement
,
539 selectionStart
, selectionEnd
);
540 return UnmaskedStart() <= selectionStart
&& UnmaskedEnd() >= selectionEnd
;
543 nsresult
TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType
,
544 bool aDispatchPasteEvent
,
545 nsIPrincipal
* aPrincipal
) {
546 MOZ_ASSERT(aClipboardType
== nsIClipboard::kGlobalClipboard
||
547 aClipboardType
== nsIClipboard::kSelectionClipboard
);
549 AutoEditActionDataSetter
editActionData(*this, EditAction::ePasteAsQuotation
,
551 if (NS_WARN_IF(!editActionData
.CanHandle())) {
552 return NS_ERROR_NOT_INITIALIZED
;
554 MOZ_ASSERT(GetDocument());
556 // Get Clipboard Service
558 nsCOMPtr
<nsIClipboard
> clipboard
=
559 do_GetService("@mozilla.org/widget/clipboard;1", &rv
);
561 NS_WARNING("Failed to get nsIClipboard service");
565 // XXX Why don't we dispatch ePaste event here?
567 // Get the nsITransferable interface for getting the data from the clipboard
568 Result
<nsCOMPtr
<nsITransferable
>, nsresult
> maybeTransferable
=
569 EditorUtils::CreateTransferableForPlainText(*GetDocument());
570 if (maybeTransferable
.isErr()) {
571 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
572 return EditorBase::ToGenericNSResult(maybeTransferable
.unwrapErr());
574 nsCOMPtr
<nsITransferable
> trans(maybeTransferable
.unwrap());
577 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
582 // Get the Data from the clipboard
583 clipboard
->GetData(trans
, aClipboardType
);
585 // Now we ask the transferable for the data
586 // it still owns the data, we just have a pointer to it.
587 // If it can't support a "text" output of the data the call will fail
588 nsCOMPtr
<nsISupports
> genericDataObj
;
590 rv
= trans
->GetAnyTransferData(flav
, getter_AddRefs(genericDataObj
));
592 NS_WARNING("nsITransferable::GetAnyTransferData() failed");
593 return EditorBase::ToGenericNSResult(rv
);
596 if (!flav
.EqualsLiteral(kUnicodeMime
) &&
597 !flav
.EqualsLiteral(kMozTextInternal
)) {
601 nsCOMPtr
<nsISupportsString
> text
= do_QueryInterface(genericDataObj
);
606 nsString stuffToPaste
;
607 DebugOnly
<nsresult
> rvIgnored
= text
->GetData(stuffToPaste
);
608 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
609 "nsISupportsString::GetData() failed, but ignored");
610 if (stuffToPaste
.IsEmpty()) {
614 editActionData
.SetData(stuffToPaste
);
615 if (!stuffToPaste
.IsEmpty()) {
616 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste
);
618 // XXX Perhaps, we should dispatch "paste" event with the pasting text data.
619 editActionData
.NotifyOfDispatchingClipboardEvent();
620 rv
= editActionData
.MaybeDispatchBeforeInputEvent();
622 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
623 "MaybeDispatchBeforeInputEvent() failed");
624 return EditorBase::ToGenericNSResult(rv
);
627 AutoPlaceholderBatch
treatAsOneTransaction(
628 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
629 rv
= InsertWithQuotationsAsSubAction(stuffToPaste
);
630 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
631 "TextEditor::InsertWithQuotationsAsSubAction() failed");
632 return EditorBase::ToGenericNSResult(rv
);
635 nsresult
TextEditor::InsertWithQuotationsAsSubAction(
636 const nsAString
& aQuotedText
) {
637 MOZ_ASSERT(IsEditActionDataAvailable());
643 // Let the citer quote it for us:
644 nsString quotedStuff
;
645 InternetCiter::GetCiteString(aQuotedText
, quotedStuff
);
647 // It's best to put a blank line after the quoted text so that mails
648 // written without thinking won't be so ugly.
649 if (!aQuotedText
.IsEmpty() && (aQuotedText
.Last() != char16_t('\n'))) {
650 quotedStuff
.Append(char16_t('\n'));
653 IgnoredErrorResult ignoredError
;
654 AutoEditSubActionNotifier
startToHandleEditSubAction(
655 *this, EditSubAction::eInsertText
, nsIEditor::eNext
, ignoredError
);
656 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
657 return ignoredError
.StealNSResult();
659 NS_WARNING_ASSERTION(
660 !ignoredError
.Failed(),
661 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
663 // XXX Do we need to support paste-as-quotation in password editor (and
664 // also in single line editor)?
665 MaybeDoAutoPasswordMasking();
667 nsresult rv
= InsertTextAsSubAction(quotedStuff
, SelectionHandling::Delete
);
668 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
669 "EditorBase::InsertTextAsSubAction() failed");
673 nsresult
TextEditor::SelectEntireDocument() {
674 MOZ_ASSERT(IsEditActionDataAvailable());
676 if (NS_WARN_IF(!mInitSucceeded
)) {
677 return NS_ERROR_NOT_INITIALIZED
;
680 RefPtr
<Element
> anonymousDivElement
= GetRoot();
681 if (NS_WARN_IF(!anonymousDivElement
)) {
682 return NS_ERROR_NOT_INITIALIZED
;
686 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
689 MOZ_TRY(SelectionRef().SetStartAndEndInLimiter(
690 *text
, 0, *text
, text
->TextDataLength(), eDirNext
,
691 nsISelectionListener::SELECTALL_REASON
));
696 EventTarget
* TextEditor::GetDOMEventTarget() const { return mEventTarget
; }
698 void TextEditor::ReinitializeSelection(Element
& aElement
) {
699 if (NS_WARN_IF(Destroyed())) {
703 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
704 if (NS_WARN_IF(!editActionData
.CanHandle())) {
708 // We don't need to flush pending notifications here and we don't need to
709 // handle spellcheck at first focus. Therefore, we don't need to call
710 // `TextEditor::OnFocus` here.
711 EditorBase::OnFocus(aElement
);
713 // If previous focused editor turn on spellcheck and this editor doesn't
714 // turn on it, spellcheck state is mismatched. So we need to re-sync it.
718 nsresult
TextEditor::OnFocus(const nsINode
& aOriginalEventTargetNode
) {
719 RefPtr
<PresShell
> presShell
= GetPresShell();
720 if (NS_WARN_IF(!presShell
)) {
721 return NS_ERROR_FAILURE
;
723 // Let's update the layout information right now because there are some
724 // pending notifications and flushing them may cause destroying the editor.
725 presShell
->FlushPendingNotifications(FlushType::Layout
);
726 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode
))) {
730 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
731 if (NS_WARN_IF(!editActionData
.CanHandle())) {
732 return NS_ERROR_FAILURE
;
735 // Spell check a textarea the first time that it is focused.
736 nsresult rv
= FlushPendingSpellCheck();
737 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
738 NS_WARNING("EditorBase::FlushPendingSpellCheck() failed");
739 return NS_ERROR_EDITOR_DESTROYED
;
741 NS_WARNING_ASSERTION(
743 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
744 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode
))) {
748 return EditorBase::OnFocus(aOriginalEventTargetNode
);
751 nsresult
TextEditor::OnBlur(const EventTarget
* aEventTarget
) {
752 nsresult rv
= FinalizeSelection();
753 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
754 "EditorBase::FinalizeSelection() failed");
758 nsresult
TextEditor::SetAttributeOrEquivalent(Element
* aElement
,
760 const nsAString
& aValue
,
761 bool aSuppressTransaction
) {
762 if (NS_WARN_IF(!aElement
) || NS_WARN_IF(!aAttribute
)) {
763 return NS_ERROR_INVALID_ARG
;
766 AutoEditActionDataSetter
editActionData(*this, EditAction::eSetAttribute
);
767 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
769 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
770 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
771 return EditorBase::ToGenericNSResult(rv
);
774 rv
= SetAttributeWithTransaction(*aElement
, *aAttribute
, aValue
);
775 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
776 "EditorBase::SetAttributeWithTransaction() failed");
777 return EditorBase::ToGenericNSResult(rv
);
780 nsresult
TextEditor::RemoveAttributeOrEquivalent(Element
* aElement
,
782 bool aSuppressTransaction
) {
783 if (NS_WARN_IF(!aElement
) || NS_WARN_IF(!aAttribute
)) {
784 return NS_ERROR_INVALID_ARG
;
787 AutoEditActionDataSetter
editActionData(*this, EditAction::eRemoveAttribute
);
788 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
790 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
791 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
792 return EditorBase::ToGenericNSResult(rv
);
795 rv
= RemoveAttributeWithTransaction(*aElement
, *aAttribute
);
796 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
797 "EditorBase::RemoveAttributeWithTransaction() failed");
798 return EditorBase::ToGenericNSResult(rv
);
802 void TextEditor::MaskString(nsString
& aString
, const Text
& aTextNode
,
803 uint32_t aStartOffsetInString
,
804 uint32_t aStartOffsetInText
) {
805 MOZ_ASSERT(aTextNode
.HasFlag(NS_MAYBE_MASKED
));
806 MOZ_ASSERT(aStartOffsetInString
== 0 || aStartOffsetInText
== 0);
808 uint32_t unmaskStart
= UINT32_MAX
, unmaskLength
= 0;
809 TextEditor
* textEditor
=
810 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(&aTextNode
);
811 if (textEditor
&& textEditor
->UnmaskedLength() > 0) {
812 unmaskStart
= textEditor
->UnmaskedStart();
813 unmaskLength
= textEditor
->UnmaskedLength();
814 // If text is copied from after unmasked range, we can treat this case
816 if (aStartOffsetInText
>= unmaskStart
+ unmaskLength
) {
818 unmaskStart
= UINT32_MAX
;
820 // If text is copied from middle of unmasked range, reduce the length
821 // and adjust start offset.
822 if (aStartOffsetInText
> unmaskStart
) {
823 unmaskLength
= unmaskStart
+ unmaskLength
- aStartOffsetInText
;
826 // If text is copied from before start of unmasked range, just adjust
829 unmaskStart
-= aStartOffsetInText
;
831 // Make the range is in the string.
832 unmaskStart
+= aStartOffsetInString
;
836 const char16_t kPasswordMask
= TextEditor::PasswordMask();
837 for (uint32_t i
= aStartOffsetInString
; i
< aString
.Length(); ++i
) {
838 bool isSurrogatePair
= NS_IS_HIGH_SURROGATE(aString
.CharAt(i
)) &&
839 i
< aString
.Length() - 1 &&
840 NS_IS_LOW_SURROGATE(aString
.CharAt(i
+ 1));
841 if (i
< unmaskStart
|| i
>= unmaskStart
+ unmaskLength
) {
842 if (isSurrogatePair
) {
843 aString
.SetCharAt(kPasswordMask
, i
);
844 aString
.SetCharAt(kPasswordMask
, i
+ 1);
846 aString
.SetCharAt(kPasswordMask
, i
);
850 // Skip the following low surrogate.
851 if (isSurrogatePair
) {
857 nsresult
TextEditor::SetUnmaskRangeInternal(uint32_t aStart
, uint32_t aLength
,
858 uint32_t aTimeout
, bool aNotify
,
859 bool aForceStartMasking
) {
860 if (mPasswordMaskData
) {
861 mPasswordMaskData
->mIsMaskingPassword
= aForceStartMasking
|| aTimeout
!= 0;
863 // We cannot manage multiple unmasked ranges so that shrink the previous
865 if (!IsAllMasked()) {
866 mPasswordMaskData
->mUnmaskedLength
= 0;
867 mPasswordMaskData
->CancelTimer(PasswordMaskData::ReleaseTimer::No
);
871 // If we're not a password editor, return error since this call does not
873 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData
)) {
874 mPasswordMaskData
->CancelTimer(PasswordMaskData::ReleaseTimer::Yes
);
875 return NS_ERROR_NOT_AVAILABLE
;
878 Element
* rootElement
= GetRoot();
879 if (NS_WARN_IF(!rootElement
)) {
880 return NS_ERROR_NOT_INITIALIZED
;
882 Text
* text
= Text::FromNodeOrNull(rootElement
->GetFirstChild());
883 if (!text
|| !text
->Length()) {
884 // There is no anonymous text node in the editor.
885 return aStart
> 0 && aStart
!= UINT32_MAX
? NS_ERROR_INVALID_ARG
: NS_OK
;
888 if (aStart
< UINT32_MAX
) {
889 uint32_t valueLength
= text
->Length();
890 if (aStart
>= valueLength
) {
891 return NS_ERROR_INVALID_ARG
; // There is no character can be masked.
893 // If aStart is middle of a surrogate pair, expand it to include the
894 // preceding high surrogate because the caller may want to show a
895 // character before the character at `aStart + 1`.
896 const nsTextFragment
* textFragment
= text
->GetText();
897 if (textFragment
->IsLowSurrogateFollowingHighSurrogateAt(aStart
)) {
898 mPasswordMaskData
->mUnmaskedStart
= aStart
- 1;
899 // If caller collapses the range, keep it. Otherwise, expand the length.
904 mPasswordMaskData
->mUnmaskedStart
= aStart
;
906 mPasswordMaskData
->mUnmaskedLength
=
907 std::min(valueLength
- UnmaskedStart(), aLength
);
908 // If unmasked end is middle of a surrogate pair, expand it to include
909 // the following low surrogate because the caller may want to show a
910 // character after the character at `aStart + aLength`.
911 if (UnmaskedEnd() < valueLength
&&
912 textFragment
->IsLowSurrogateFollowingHighSurrogateAt(UnmaskedEnd())) {
913 mPasswordMaskData
->mUnmaskedLength
++;
915 // If it's first time to mask the unmasking characters with timer, create
916 // the timer now. Then, we'll keep using it for saving the creation cost.
917 if (!HasAutoMaskingTimer() && aLength
&& aTimeout
&& UnmaskedLength()) {
918 mPasswordMaskData
->mTimer
= NS_NewTimer();
921 if (NS_WARN_IF(aLength
!= 0)) {
922 return NS_ERROR_INVALID_ARG
;
924 mPasswordMaskData
->MaskAll();
927 // Notify nsTextFrame of this update if the caller wants this to do it.
928 // Only in this case, script may run.
930 MOZ_ASSERT(IsEditActionDataAvailable());
932 RefPtr
<Document
> document
= GetDocument();
933 if (NS_WARN_IF(!document
)) {
934 return NS_ERROR_NOT_INITIALIZED
;
936 // Notify nsTextFrame of masking range change.
937 if (RefPtr
<PresShell
> presShell
= document
->GetObservingPresShell()) {
938 nsAutoScriptBlocker blockRunningScript
;
939 uint32_t valueLength
= text
->Length();
940 CharacterDataChangeInfo changeInfo
= {false, 0, valueLength
, valueLength
,
942 presShell
->CharacterDataChanged(text
, changeInfo
);
945 // Scroll caret into the view since masking or unmasking character may
946 // move caret to outside of the view.
947 nsresult rv
= ScrollSelectionFocusIntoView();
949 NS_WARNING("EditorBase::ScrollSelectionFocusIntoView() failed");
954 if (!IsAllMasked() && aTimeout
!= 0) {
955 // Initialize the timer to mask the range automatically.
956 MOZ_ASSERT(HasAutoMaskingTimer());
957 DebugOnly
<nsresult
> rvIgnored
= mPasswordMaskData
->mTimer
->InitWithCallback(
958 this, aTimeout
, nsITimer::TYPE_ONE_SHOT
);
959 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
960 "nsITimer::InitWithCallback() failed, but ignored");
967 char16_t
TextEditor::PasswordMask() {
968 char16_t ret
= LookAndFeel::GetPasswordCharacter();
975 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
TextEditor::Notify(nsITimer
* aTimer
) {
976 // Check whether our text editor's password flag was changed before this
977 // "hide password character" timer actually fires.
978 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData
)) {
986 AutoEditActionDataSetter
editActionData(*this, EditAction::eHidePassword
);
987 if (NS_WARN_IF(!editActionData
.CanHandle())) {
988 return NS_ERROR_NOT_INITIALIZED
;
991 // Mask all characters.
992 nsresult rv
= MaskAllCharactersAndNotify();
993 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
994 "TextEditor::MaskAllCharactersAndNotify() failed");
996 if (StaticPrefs::editor_password_testing_mask_delay()) {
997 if (RefPtr
<Element
> target
= GetInputEventTargetElement()) {
998 RefPtr
<Document
> document
= target
->OwnerDoc();
999 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchTrustedEvent(
1000 document
, target
, u
"MozLastInputMasked"_ns
, CanBubble::eYes
,
1002 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1003 "nsContentUtils::DispatchTrustedEvent("
1004 "MozLastInputMasked) failed, but ignored");
1008 return EditorBase::ToGenericNSResult(rv
);
1011 NS_IMETHODIMP
TextEditor::GetName(nsACString
& aName
) {
1012 aName
.AssignLiteral("TextEditor");
1016 void TextEditor::WillDeleteText(uint32_t aCurrentLength
,
1017 uint32_t aRemoveStartOffset
,
1018 uint32_t aRemoveLength
) {
1019 MOZ_ASSERT(IsEditActionDataAvailable());
1021 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData
) || IsAllMasked()) {
1025 // Adjust unmasked range before deletion since DOM mutation may cause
1026 // layout referring the range in old text.
1028 // If we need to mask automatically, mask all now.
1029 if (IsMaskingPassword()) {
1030 DebugOnly
<nsresult
> rvIgnored
= MaskAllCharacters();
1031 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1032 "TextEditor::MaskAllCharacters() failed, but ignored");
1036 if (aRemoveStartOffset
< UnmaskedStart()) {
1037 // If removing range is before the unmasked range, move it.
1038 if (aRemoveStartOffset
+ aRemoveLength
<= UnmaskedStart()) {
1039 DebugOnly
<nsresult
> rvIgnored
=
1040 SetUnmaskRange(UnmaskedStart() - aRemoveLength
, UnmaskedLength());
1041 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1042 "TextEditor::SetUnmaskRange() failed, but ignored");
1046 // If removing range starts before unmasked range, and ends in unmasked
1047 // range, move and shrink the range.
1048 if (aRemoveStartOffset
+ aRemoveLength
< UnmaskedEnd()) {
1049 uint32_t unmaskedLengthInRemovingRange
=
1050 aRemoveStartOffset
+ aRemoveLength
- UnmaskedStart();
1051 DebugOnly
<nsresult
> rvIgnored
= SetUnmaskRange(
1052 aRemoveStartOffset
, UnmaskedLength() - unmaskedLengthInRemovingRange
);
1053 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1054 "TextEditor::SetUnmaskRange() failed, but ignored");
1058 // If removing range includes all unmasked range, collapse it to the
1060 DebugOnly
<nsresult
> rvIgnored
= SetUnmaskRange(aRemoveStartOffset
, 0);
1061 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1062 "TextEditor::SetUnmaskRange() failed, but ignored");
1066 if (aRemoveStartOffset
< UnmaskedEnd()) {
1067 // If removing range is in unmasked range, shrink the range.
1068 if (aRemoveStartOffset
+ aRemoveLength
<= UnmaskedEnd()) {
1069 DebugOnly
<nsresult
> rvIgnored
=
1070 SetUnmaskRange(UnmaskedStart(), UnmaskedLength() - aRemoveLength
);
1071 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1072 "TextEditor::SetUnmaskRange() failed, but ignored");
1076 // If removing range starts from unmasked range, and ends after it,
1078 DebugOnly
<nsresult
> rvIgnored
=
1079 SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset
- UnmaskedStart());
1080 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1081 "TextEditor::SetUnmaskRange() failed, but ignored");
1085 // If removing range is after the unmasked range, keep it.
1088 nsresult
TextEditor::DidInsertText(uint32_t aNewLength
,
1089 uint32_t aInsertedOffset
,
1090 uint32_t aInsertedLength
) {
1091 MOZ_ASSERT(IsEditActionDataAvailable());
1093 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData
) || IsAllMasked()) {
1097 if (IsMaskingPassword()) {
1098 // If we need to mask password, mask all right now.
1099 nsresult rv
= MaskAllCharactersAndNotify();
1100 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1101 "TextEditor::MaskAllCharacters() failed");
1105 if (aInsertedOffset
< UnmaskedStart()) {
1106 // If insertion point is before unmasked range, expand the unmasked range
1107 // to include the new text.
1108 nsresult rv
= SetUnmaskRangeAndNotify(
1109 aInsertedOffset
, UnmaskedEnd() + aInsertedLength
- aInsertedOffset
);
1110 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1111 "TextEditor::SetUnmaskRangeAndNotify() failed");
1115 if (aInsertedOffset
<= UnmaskedEnd()) {
1116 // If insertion point is in unmasked range, unmask new text.
1117 nsresult rv
= SetUnmaskRangeAndNotify(UnmaskedStart(),
1118 UnmaskedLength() + aInsertedLength
);
1119 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1120 "TextEditor::SetUnmaskRangeAndNotify() failed");
1124 // If insertion point is after unmasked range, extend the unmask range to
1125 // include the new text.
1126 nsresult rv
= SetUnmaskRangeAndNotify(
1127 UnmaskedStart(), aInsertedOffset
+ aInsertedLength
- UnmaskedStart());
1128 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1129 "TextEditor::SetUnmaskRangeAndNotify() failed");
1133 } // namespace mozilla