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/. */
7 #include "TextEditor.h"
9 #include "AutoRangeArray.h"
10 #include "EditAction.h"
11 #include "EditorDOMPoint.h"
12 #include "EditorUtils.h"
13 #include "HTMLEditor.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/LookAndFeel.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/StaticPrefs_editor.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/HTMLBRElement.h"
22 #include "mozilla/dom/NodeFilterBinding.h"
23 #include "mozilla/dom/NodeIterator.h"
24 #include "mozilla/dom/Selection.h"
26 #include "nsAString.h"
29 #include "nsCRTGlue.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsContentUtils.h"
34 #include "nsGkAtoms.h"
35 #include "nsIContent.h"
36 #include "nsIHTMLCollection.h"
38 #include "nsISupports.h"
39 #include "nsLiteralString.h"
40 #include "nsNameSpaceManager.h"
41 #include "nsPrintfCString.h"
42 #include "nsTextNode.h"
43 #include "nsUnicharUtils.h"
49 #define CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY \
51 return EditActionResult::CanceledResult(); \
54 void TextEditor::OnStartToHandleTopLevelEditSubAction(
55 EditSubAction aTopLevelEditSubAction
,
56 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction
, ErrorResult
& aRv
) {
57 MOZ_ASSERT(IsEditActionDataAvailable());
58 MOZ_ASSERT(!aRv
.Failed());
60 EditorBase::OnStartToHandleTopLevelEditSubAction(
61 aTopLevelEditSubAction
, aDirectionOfTopLevelEditSubAction
, aRv
);
63 MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction
);
64 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
65 aDirectionOfTopLevelEditSubAction
);
67 if (NS_WARN_IF(Destroyed())) {
68 aRv
.Throw(NS_ERROR_EDITOR_DESTROYED
);
72 if (NS_WARN_IF(!mInitSucceeded
)) {
76 if (aTopLevelEditSubAction
== EditSubAction::eSetText
) {
77 // SetText replaces all text, so spell checker handles starting from the
78 // start of new value.
79 SetSpellCheckRestartPoint(EditorDOMPoint(mRootElement
, 0));
83 if (aTopLevelEditSubAction
== EditSubAction::eInsertText
||
84 aTopLevelEditSubAction
== EditSubAction::eInsertTextComingFromIME
) {
85 // For spell checker, previous selected node should be text node if
86 // possible. If anchor is root of editor, it may become invalid offset
87 // after inserting text.
88 const EditorRawDOMPoint point
=
89 FindBetterInsertionPoint(EditorRawDOMPoint(SelectionRef().AnchorRef()));
91 SetSpellCheckRestartPoint(point
);
94 NS_WARNING("TextEditor::FindBetterInsertionPoint() failed, but ignored");
96 if (SelectionRef().AnchorRef().IsSet()) {
97 SetSpellCheckRestartPoint(EditorRawDOMPoint(SelectionRef().AnchorRef()));
101 nsresult
TextEditor::OnEndHandlingTopLevelEditSubAction() {
102 MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
106 if (NS_WARN_IF(Destroyed())) {
107 rv
= NS_ERROR_EDITOR_DESTROYED
;
111 // XXX Probably, we should spellcheck again after edit action (not top-level
112 // sub-action) is handled because the ranges can be referred only by
114 if (NS_FAILED(rv
= HandleInlineSpellCheckAfterEdit())) {
115 NS_WARNING("TextEditor::HandleInlineSpellCheckAfterEdit() failed");
119 if (!IsSingleLineEditor() &&
120 NS_FAILED(rv
= EnsurePaddingBRElementInMultilineEditor())) {
122 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
126 rv
= EnsureCaretNotAtEndOfTextNode();
127 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
130 NS_WARNING_ASSERTION(
132 "TextEditor::EnsureCaretNotAtEndOfTextNode() failed, but ignored");
136 DebugOnly
<nsresult
> rvIgnored
=
137 EditorBase::OnEndHandlingTopLevelEditSubAction();
138 NS_WARNING_ASSERTION(
139 NS_SUCCEEDED(rvIgnored
),
140 "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
141 MOZ_ASSERT(!GetTopLevelEditSubAction());
142 MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone
);
146 nsresult
TextEditor::InsertLineBreakAsSubAction() {
147 MOZ_ASSERT(IsEditActionDataAvailable());
149 if (NS_WARN_IF(!mInitSucceeded
)) {
150 return NS_ERROR_NOT_INITIALIZED
;
153 IgnoredErrorResult ignoredError
;
154 AutoEditSubActionNotifier
startToHandleEditSubAction(
155 *this, EditSubAction::eInsertLineBreak
, nsIEditor::eNext
, ignoredError
);
156 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
157 return ignoredError
.StealNSResult();
159 NS_WARNING_ASSERTION(
160 !ignoredError
.Failed(),
161 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
163 Result
<EditActionResult
, nsresult
> result
=
164 InsertLineFeedCharacterAtSelection();
165 if (MOZ_UNLIKELY(result
.isErr())) {
167 "TextEditor::InsertLineFeedCharacterAtSelection() failed, but ignored");
168 return result
.unwrapErr();
173 Result
<EditActionResult
, nsresult
>
174 TextEditor::InsertLineFeedCharacterAtSelection() {
175 MOZ_ASSERT(IsEditActionDataAvailable());
176 MOZ_ASSERT(!IsSingleLineEditor());
178 UndefineCaretBidiLevel();
180 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
182 if (mMaxTextLength
>= 0) {
183 nsAutoString
insertionString(u
"\n"_ns
);
184 Result
<EditActionResult
, nsresult
> result
=
185 MaybeTruncateInsertionStringForMaxLength(insertionString
);
186 if (MOZ_UNLIKELY(result
.isErr())) {
188 "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
191 if (result
.inspect().Handled()) {
192 // Don't return as handled since we stopped inserting the line break.
193 return EditActionResult::CanceledResult();
197 // if the selection isn't collapsed, delete it.
198 if (!SelectionRef().IsCollapsed()) {
200 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eNoStrip
);
203 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
208 const auto pointToInsert
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
209 if (NS_WARN_IF(!pointToInsert
.IsSet())) {
210 return Err(NS_ERROR_FAILURE
);
212 MOZ_ASSERT(pointToInsert
.IsSetAndValid());
213 MOZ_ASSERT(!pointToInsert
.IsContainerHTMLElement(nsGkAtoms::br
));
215 RefPtr
<Document
> document
= GetDocument();
216 if (NS_WARN_IF(!document
)) {
217 return Err(NS_ERROR_NOT_INITIALIZED
);
220 // Insert a linefeed character.
221 Result
<InsertTextResult
, nsresult
> insertTextResult
=
222 InsertTextWithTransaction(*document
, u
"\n"_ns
, pointToInsert
);
223 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
224 NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed");
225 return insertTextResult
.propagateErr();
227 insertTextResult
.inspect().IgnoreCaretPointSuggestion();
228 EditorDOMPoint pointToPutCaret
= insertTextResult
.inspect().Handled()
229 ? insertTextResult
.inspect()
230 .EndOfInsertedTextRef()
231 .To
<EditorDOMPoint
>()
233 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValid())) {
234 return Err(NS_ERROR_FAILURE
);
236 // XXX I don't think we still need this. This must have been required when
237 // `<textarea>` was implemented with text nodes and `<br>` elements.
238 // We want the caret to stick to the content on the "right". We want the
239 // caret to stick to whatever is past the break. This is because the break is
240 // on the same line we were on, but the next content will be on the following
242 pointToPutCaret
.SetInterlinePosition(InterlinePosition::StartOfNextLine
);
243 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
245 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
248 return EditActionResult::HandledResult();
251 nsresult
TextEditor::EnsureCaretNotAtEndOfTextNode() {
252 MOZ_ASSERT(IsEditActionDataAvailable());
254 // If there is no selection ranges, we should set to the end of the editor.
255 // This is usually performed in InitEditorContentAndSelection(), however,
256 // if the editor is reframed, this may be called by
257 // OnEndHandlingTopLevelEditSubAction().
258 if (SelectionRef().RangeCount()) {
262 nsresult rv
= CollapseSelectionToEndOfTextNode();
263 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
265 "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the "
267 return NS_ERROR_EDITOR_DESTROYED
;
269 NS_WARNING_ASSERTION(
271 "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
276 void TextEditor::HandleNewLinesInStringForSingleLineEditor(
277 nsString
& aString
) const {
278 static const char16_t kLF
= static_cast<char16_t
>('\n');
279 MOZ_ASSERT(IsEditActionDataAvailable());
280 MOZ_ASSERT(aString
.FindChar(static_cast<uint16_t>('\r')) == kNotFound
);
282 // First of all, check if aString contains '\n' since if the string
283 // does not include it, we don't need to do nothing here.
284 int32_t firstLF
= aString
.FindChar(kLF
, 0);
285 if (firstLF
== kNotFound
) {
289 switch (mNewlineHandling
) {
290 case nsIEditor::eNewlinesReplaceWithSpaces
:
291 // Default of Firefox:
292 // Strip trailing newlines first so we don't wind up with trailing spaces
293 aString
.Trim(LFSTR
, false, true);
294 aString
.ReplaceChar(kLF
, ' ');
296 case nsIEditor::eNewlinesStrip
:
297 aString
.StripChar(kLF
);
299 case nsIEditor::eNewlinesPasteToFirst
:
301 // we get first *non-empty* line.
303 while (firstLF
== offset
) {
305 firstLF
= aString
.FindChar(kLF
, offset
);
308 aString
.Truncate(firstLF
);
311 aString
.Cut(0, offset
);
315 case nsIEditor::eNewlinesReplaceWithCommas
:
316 // Default of Thunderbird:
317 aString
.Trim(LFSTR
, true, true);
318 aString
.ReplaceChar(kLF
, ',');
320 case nsIEditor::eNewlinesStripSurroundingWhitespace
: {
323 while (offset
< aString
.Length()) {
324 int32_t nextLF
= !offset
? firstLF
: aString
.FindChar(kLF
, offset
);
326 result
.Append(nsDependentSubstring(aString
, offset
));
329 uint32_t wsBegin
= nextLF
;
330 // look backwards for the first non-white-space char
331 while (wsBegin
> offset
&& NS_IS_SPACE(aString
[wsBegin
- 1])) {
334 result
.Append(nsDependentSubstring(aString
, offset
, wsBegin
- offset
));
336 while (offset
< aString
.Length() && NS_IS_SPACE(aString
[offset
])) {
343 case nsIEditor::eNewlinesPasteIntact
:
344 // even if we're pasting newlines, don't paste leading/trailing ones
345 aString
.Trim(LFSTR
, true, true);
350 Result
<EditActionResult
, nsresult
> TextEditor::HandleInsertText(
351 EditSubAction aEditSubAction
, const nsAString
& aInsertionString
,
352 SelectionHandling aSelectionHandling
) {
353 MOZ_ASSERT(IsEditActionDataAvailable());
354 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
||
355 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
356 MOZ_ASSERT_IF(aSelectionHandling
== SelectionHandling::Ignore
,
357 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
359 UndefineCaretBidiLevel();
361 nsAutoString
insertionString(aInsertionString
);
362 if (!aInsertionString
.IsEmpty() && mMaxTextLength
>= 0) {
363 Result
<EditActionResult
, nsresult
> result
=
364 MaybeTruncateInsertionStringForMaxLength(insertionString
);
365 if (MOZ_UNLIKELY(result
.isErr())) {
367 "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
368 EditActionResult unwrappedResult
= result
.unwrap();
369 unwrappedResult
.MarkAsHandled();
370 return unwrappedResult
;
372 // If we're exceeding the maxlength when composing IME, we need to clean up
373 // the composing text, so we shouldn't return early.
374 if (result
.inspect().Handled() && insertionString
.IsEmpty() &&
375 aEditSubAction
!= EditSubAction::eInsertTextComingFromIME
) {
376 return EditActionResult::CanceledResult();
381 if (IsPasswordEditor()) {
382 if (GetComposition() && !GetComposition()->String().IsEmpty()) {
383 start
= GetComposition()->XPOffsetInTextNode();
386 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
391 // if the selection isn't collapsed, delete it.
392 if (!SelectionRef().IsCollapsed() &&
393 aSelectionHandling
== SelectionHandling::Delete
) {
395 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eNoStrip
);
398 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
403 if (aInsertionString
.IsEmpty() &&
404 aEditSubAction
!= EditSubAction::eInsertTextComingFromIME
) {
405 // HACK: this is a fix for bug 19395
406 // I can't outlaw all empty insertions
407 // because IME transaction depend on them
408 // There is more work to do to make the
409 // world safe for IME.
410 return EditActionResult::CanceledResult();
413 // XXX Why don't we cancel here? Shouldn't we do this first?
414 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
416 MaybeDoAutoPasswordMasking();
418 // People have lots of different ideas about what text fields
419 // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
420 // The six possible options are:
421 // 0. paste newlines intact
422 // 1. paste up to the first newline (default)
423 // 2. replace newlines with spaces
425 // 4. replace with commas
426 // 5. strip newlines and surrounding white-space
427 // So find out what we're expected to do:
428 if (IsSingleLineEditor()) {
429 // XXX Some callers of TextEditor::InsertTextAsAction() already make the
430 // string use only \n as a linebreaker. However, they are not hot
431 // path and nsContentUtils::PlatformToDOMLineBreaks() does nothing
432 // if the string doesn't include \r. So, let's convert linebreakers
433 // here. Note that there are too many callers of
434 // TextEditor::InsertTextAsAction(). So, it's difficult to keep
435 // maintaining all of them won't reach here without \r nor \r\n.
436 // XXX Should we handle do this before truncating the string for
438 nsContentUtils::PlatformToDOMLineBreaks(insertionString
);
439 HandleNewLinesInStringForSingleLineEditor(insertionString
);
442 const auto atStartOfSelection
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
443 if (NS_WARN_IF(!atStartOfSelection
.IsSetAndValid())) {
444 return Err(NS_ERROR_FAILURE
);
446 MOZ_ASSERT(!atStartOfSelection
.IsContainerHTMLElement(nsGkAtoms::br
));
448 RefPtr
<Document
> document
= GetDocument();
449 if (NS_WARN_IF(!document
)) {
450 return Err(NS_ERROR_NOT_INITIALIZED
);
453 if (aEditSubAction
== EditSubAction::eInsertTextComingFromIME
) {
454 EditorDOMPoint compositionStartPoint
=
455 GetFirstIMESelectionStartPoint
<EditorDOMPoint
>();
456 if (!compositionStartPoint
.IsSet()) {
457 compositionStartPoint
= FindBetterInsertionPoint(atStartOfSelection
);
458 NS_WARNING_ASSERTION(
459 compositionStartPoint
.IsSet(),
460 "TextEditor::FindBetterInsertionPoint() failed, but ignored");
462 Result
<InsertTextResult
, nsresult
> insertTextResult
=
463 InsertTextWithTransaction(*document
, insertionString
,
464 compositionStartPoint
);
465 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
466 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
467 return insertTextResult
.propagateErr();
469 nsresult rv
= insertTextResult
.unwrap().SuggestCaretPointTo(
470 *this, {SuggestCaret::OnlyIfHasSuggestion
,
471 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
472 SuggestCaret::AndIgnoreTrivialError
});
474 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
477 NS_WARNING_ASSERTION(
478 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
479 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
481 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
);
483 Result
<InsertTextResult
, nsresult
> insertTextResult
=
484 InsertTextWithTransaction(*document
, insertionString
,
486 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
487 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
488 return insertTextResult
.propagateErr();
490 // Ignore caret suggestion because there was
491 // AutoTransactionsConserveSelection.
492 insertTextResult
.inspect().IgnoreCaretPointSuggestion();
493 if (insertTextResult
.inspect().Handled()) {
494 // Make the caret attach to the inserted text, unless this text ends with
495 // a LF, in which case make the caret attach to the next line.
496 const bool endsWithLF
=
497 !insertionString
.IsEmpty() && insertionString
.Last() == nsCRT::LF
;
498 EditorDOMPoint pointToPutCaret
= insertTextResult
.inspect()
499 .EndOfInsertedTextRef()
500 .To
<EditorDOMPoint
>();
501 pointToPutCaret
.SetInterlinePosition(
502 endsWithLF
? InterlinePosition::StartOfNextLine
503 : InterlinePosition::EndOfLine
);
504 MOZ_ASSERT(pointToPutCaret
.IsInTextNode(),
505 "After inserting text into a text node, insertTextResult "
506 "should return a point in a text node");
507 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
508 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
509 return Err(NS_ERROR_EDITOR_DESTROYED
);
511 NS_WARNING_ASSERTION(
513 "EditorBase::CollapseSelectionTo() failed, but ignored");
517 // Unmask inputted character(s) if necessary.
518 if (IsPasswordEditor() && IsMaskingPassword() && CanEchoPasswordNow()) {
519 nsresult rv
= SetUnmaskRangeAndNotify(start
, insertionString
.Length(),
520 LookAndFeel::GetPasswordMaskDelay());
522 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
525 return EditActionResult::HandledResult();
528 return EditActionResult::HandledResult();
531 Result
<EditActionResult
, nsresult
> TextEditor::SetTextWithoutTransaction(
532 const nsAString
& aValue
) {
533 MOZ_ASSERT(IsEditActionDataAvailable());
534 MOZ_ASSERT(!IsIMEComposing());
535 MOZ_ASSERT(!IsUndoRedoEnabled());
536 MOZ_ASSERT(GetEditAction() != EditAction::eReplaceText
);
537 MOZ_ASSERT(mMaxTextLength
< 0);
538 MOZ_ASSERT(aValue
.FindChar(static_cast<char16_t
>('\r')) == kNotFound
);
540 UndefineCaretBidiLevel();
542 // XXX If we're setting value, shouldn't we keep setting the new value here?
543 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
545 MaybeDoAutoPasswordMasking();
547 RefPtr
<Element
> anonymousDivElement
= GetRoot();
548 RefPtr
<Text
> textNode
=
549 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
550 MOZ_ASSERT(textNode
);
552 // We can use this fast path only when:
553 // - we need to insert a text node.
554 // - we need to replace content of existing text node.
555 // Additionally, for avoiding odd result, we should check whether we're in
557 if (!IsSingleLineEditor()) {
558 // If we're a multiline text editor, i.e., <textarea>, there is a padding
559 // <br> element for empty last line followed by scrollbar/resizer elements.
560 // Otherwise, a text node is followed by them.
561 if (!textNode
->GetNextSibling() ||
562 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
563 *textNode
->GetNextSibling())) {
564 return EditActionResult::IgnoredResult();
568 // XXX Password fields accept line breaks as normal characters with this code.
569 // Is this intentional?
570 nsAutoString
sanitizedValue(aValue
);
571 if (IsSingleLineEditor() && !IsPasswordEditor()) {
572 HandleNewLinesInStringForSingleLineEditor(sanitizedValue
);
575 nsresult rv
= SetTextNodeWithoutTransaction(sanitizedValue
, *textNode
);
577 NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
581 return EditActionResult::HandledResult();
584 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelection(
585 nsIEditor::EDirection aDirectionAndAmount
,
586 nsIEditor::EStripWrappers aStripWrappers
) {
587 MOZ_ASSERT(IsEditActionDataAvailable());
588 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
590 UndefineCaretBidiLevel();
592 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
595 return EditActionResult::CanceledResult();
597 Result
<EditActionResult
, nsresult
> result
=
598 HandleDeleteSelectionInternal(aDirectionAndAmount
, nsIEditor::eNoStrip
);
599 // HandleDeleteSelectionInternal() creates SelectionBatcher. Therefore,
600 // quitting from it might cause having destroyed the editor.
601 if (NS_WARN_IF(Destroyed())) {
602 return Err(NS_ERROR_EDITOR_DESTROYED
);
604 NS_WARNING_ASSERTION(
606 "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
610 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelectionInternal(
611 nsIEditor::EDirection aDirectionAndAmount
,
612 nsIEditor::EStripWrappers aStripWrappers
) {
613 MOZ_ASSERT(IsEditActionDataAvailable());
614 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
616 // If the current selection is empty (e.g the user presses backspace with
617 // a collapsed selection), then we want to avoid sending the selectstart
618 // event to the user, so we hide selection changes. However, we still
619 // want to send a single selectionchange event to the document, so we
620 // batch the selectionchange events, such that a single event fires after
621 // the AutoHideSelectionChanges destructor has been run.
622 SelectionBatcher
selectionBatcher(SelectionRef(), __FUNCTION__
);
623 AutoHideSelectionChanges
hideSelection(SelectionRef());
624 nsAutoScriptBlocker scriptBlocker
;
626 if (IsPasswordEditor() && IsMaskingPassword()) {
629 const auto selectionStartPoint
=
630 GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
631 if (NS_WARN_IF(!selectionStartPoint
.IsSet())) {
632 return Err(NS_ERROR_FAILURE
);
635 if (!SelectionRef().IsCollapsed()) {
636 nsresult rv
= DeleteSelectionWithTransaction(aDirectionAndAmount
,
637 nsIEditor::eNoStrip
);
640 "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed");
643 return EditActionResult::HandledResult();
646 // Test for distance between caret and text that will be deleted
647 AutoCaretBidiLevelManager
bidiLevelManager(*this, aDirectionAndAmount
,
648 selectionStartPoint
);
649 if (MOZ_UNLIKELY(bidiLevelManager
.Failed())) {
650 NS_WARNING("EditorBase::AutoCaretBidiLevelManager() failed");
651 return Err(NS_ERROR_FAILURE
);
653 bidiLevelManager
.MaybeUpdateCaretBidiLevel(*this);
654 if (bidiLevelManager
.Canceled()) {
655 return EditActionResult::CanceledResult();
659 AutoRangeArray
rangesToDelete(SelectionRef());
660 Result
<nsIEditor::EDirection
, nsresult
> result
=
661 rangesToDelete
.ExtendAnchorFocusRangeFor(*this, aDirectionAndAmount
);
662 if (result
.isErr()) {
663 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
664 return result
.propagateErr();
666 if (const Text
* theTextNode
= GetTextNode()) {
667 rangesToDelete
.EnsureRangesInTextNode(*theTextNode
);
670 Result
<CaretPoint
, nsresult
> caretPointOrError
= DeleteRangesWithTransaction(
671 result
.unwrap(), nsIEditor::eNoStrip
, rangesToDelete
);
672 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
673 NS_WARNING("EditorBase::DeleteRangesWithTransaction(eNoStrip) failed");
674 return caretPointOrError
.propagateErr();
677 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
678 *this, {SuggestCaret::OnlyIfHasSuggestion
,
679 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
680 SuggestCaret::AndIgnoreTrivialError
});
682 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
685 NS_WARNING_ASSERTION(rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
686 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
688 return EditActionResult::HandledResult();
691 Result
<EditActionResult
, nsresult
>
692 TextEditor::ComputeValueFromTextNodeAndBRElement(nsAString
& aValue
) const {
693 MOZ_ASSERT(IsEditActionDataAvailable());
694 MOZ_ASSERT(!IsHTMLEditor());
696 Element
* anonymousDivElement
= GetRoot();
697 if (MOZ_UNLIKELY(!anonymousDivElement
)) {
698 // Don't warn this case, this is possible, e.g., 997805.html
700 return EditActionResult::HandledResult();
703 Text
* textNode
= Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
704 MOZ_ASSERT(textNode
);
706 if (!textNode
->Length()) {
708 return EditActionResult::HandledResult();
711 nsIContent
* firstChildExceptText
= textNode
->GetNextSibling();
712 // If the DOM tree is unexpected, fall back to the expensive path.
713 bool isInput
= IsSingleLineEditor();
714 bool isTextarea
= !isInput
;
715 if (NS_WARN_IF(isInput
&& firstChildExceptText
) ||
716 NS_WARN_IF(isTextarea
&& !firstChildExceptText
) ||
717 NS_WARN_IF(isTextarea
&&
718 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
719 *firstChildExceptText
) &&
720 !firstChildExceptText
->IsXULElement(nsGkAtoms::scrollbar
))) {
721 return EditActionResult::IgnoredResult();
724 // Otherwise, the text data is the value.
725 textNode
->GetData(aValue
);
726 return EditActionResult::HandledResult();
729 Result
<EditActionResult
, nsresult
>
730 TextEditor::MaybeTruncateInsertionStringForMaxLength(
731 nsAString
& aInsertionString
) {
732 MOZ_ASSERT(IsEditActionDataAvailable());
733 MOZ_ASSERT(mMaxTextLength
>= 0);
735 if (IsIMEComposing()) {
736 return EditActionResult::IgnoredResult();
739 // Ignore user pastes
740 switch (GetEditAction()) {
741 case EditAction::ePaste
:
742 case EditAction::ePasteAsQuotation
:
743 case EditAction::eDrop
:
744 case EditAction::eReplaceText
:
745 // EditActionPrinciple() is non-null iff the edit was requested by
747 if (!GetEditActionPrincipal()) {
748 // By now we are certain that this is a user paste, before we ignore it,
749 // lets check if the user explictly enabled truncating user pastes.
750 if (!StaticPrefs::editor_truncate_user_pastes()) {
751 return EditActionResult::IgnoredResult();
759 uint32_t currentLength
= UINT32_MAX
;
760 nsresult rv
= GetTextLength(¤tLength
);
762 NS_WARNING("TextEditor::GetTextLength() failed");
766 uint32_t selectionStart
, selectionEnd
;
767 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
768 selectionStart
, selectionEnd
);
770 TextComposition
* composition
= GetComposition();
771 const uint32_t kOldCompositionStringLength
=
772 composition
? composition
->String().Length() : 0;
774 const uint32_t kSelectionLength
= selectionEnd
- selectionStart
;
775 // XXX This computation must be wrong. If we'll support non-collapsed
776 // selection even during composition for Korean IME, kSelectionLength
777 // is part of kOldCompositionStringLength.
778 const uint32_t kNewLength
=
779 currentLength
- kSelectionLength
- kOldCompositionStringLength
;
780 if (kNewLength
>= AssertedCast
<uint32_t>(mMaxTextLength
)) {
781 aInsertionString
.Truncate(); // Too long, we cannot accept new character.
782 return EditActionResult::HandledResult();
785 if (aInsertionString
.Length() + kNewLength
<=
786 AssertedCast
<uint32_t>(mMaxTextLength
)) {
787 return EditActionResult::IgnoredResult(); // Enough short string.
790 int32_t newInsertionStringLength
= mMaxTextLength
- kNewLength
;
791 MOZ_ASSERT(newInsertionStringLength
> 0);
792 char16_t maybeHighSurrogate
=
793 aInsertionString
.CharAt(newInsertionStringLength
- 1);
794 char16_t maybeLowSurrogate
=
795 aInsertionString
.CharAt(newInsertionStringLength
);
796 // Don't split the surrogate pair.
797 if (NS_IS_SURROGATE_PAIR(maybeHighSurrogate
, maybeLowSurrogate
)) {
798 newInsertionStringLength
--;
800 // XXX What should we do if we're removing IVS but its preceding
801 // character won't be removed?
802 aInsertionString
.Truncate(newInsertionStringLength
);
803 return EditActionResult::HandledResult();
806 bool TextEditor::CanEchoPasswordNow() const {
807 if (!LookAndFeel::GetEchoPassword() || EchoingPasswordPrevented()) {
811 return GetEditAction() != EditAction::eDrop
&&
812 GetEditAction() != EditAction::ePaste
;
815 } // namespace mozilla