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 "AutoClonedRangeArray.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 InsertTextTo::ExistingTextNodeIfAvailable
);
224 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
225 NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed");
226 return insertTextResult
.propagateErr();
228 insertTextResult
.inspect().IgnoreCaretPointSuggestion();
229 EditorDOMPoint pointToPutCaret
=
230 insertTextResult
.inspect().Handled()
231 ? insertTextResult
.inspect().EndOfInsertedTextRef()
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 InsertTextTo::ExistingTextNodeIfAvailable
);
466 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
467 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
468 return insertTextResult
.propagateErr();
470 nsresult rv
= insertTextResult
.unwrap().SuggestCaretPointTo(
471 *this, {SuggestCaret::OnlyIfHasSuggestion
,
472 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
473 SuggestCaret::AndIgnoreTrivialError
});
475 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
478 NS_WARNING_ASSERTION(
479 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
480 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
482 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
);
484 Result
<InsertTextResult
, nsresult
> insertTextResult
=
485 InsertTextWithTransaction(*document
, insertionString
,
487 InsertTextTo::ExistingTextNodeIfAvailable
);
488 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
489 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
490 return insertTextResult
.propagateErr();
492 // Ignore caret suggestion because there was
493 // AutoTransactionsConserveSelection.
494 insertTextResult
.inspect().IgnoreCaretPointSuggestion();
495 if (insertTextResult
.inspect().Handled()) {
496 // Make the caret attach to the inserted text, unless this text ends with
497 // a LF, in which case make the caret attach to the next line.
498 const bool endsWithLF
=
499 !insertionString
.IsEmpty() && insertionString
.Last() == nsCRT::LF
;
500 EditorDOMPoint pointToPutCaret
=
501 insertTextResult
.inspect().EndOfInsertedTextRef();
502 pointToPutCaret
.SetInterlinePosition(
503 endsWithLF
? InterlinePosition::StartOfNextLine
504 : InterlinePosition::EndOfLine
);
505 MOZ_ASSERT(pointToPutCaret
.IsInTextNode(),
506 "After inserting text into a text node, insertTextResult "
507 "should return a point in a text node");
508 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
509 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
510 return Err(NS_ERROR_EDITOR_DESTROYED
);
512 NS_WARNING_ASSERTION(
514 "EditorBase::CollapseSelectionTo() failed, but ignored");
518 // Unmask inputted character(s) if necessary.
519 if (IsPasswordEditor() && IsMaskingPassword() && CanEchoPasswordNow()) {
520 nsresult rv
= SetUnmaskRangeAndNotify(start
, insertionString
.Length(),
521 LookAndFeel::GetPasswordMaskDelay());
523 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
526 return EditActionResult::HandledResult();
529 return EditActionResult::HandledResult();
532 Result
<EditActionResult
, nsresult
> TextEditor::SetTextWithoutTransaction(
533 const nsAString
& aValue
) {
534 MOZ_ASSERT(IsEditActionDataAvailable());
535 MOZ_ASSERT(!IsIMEComposing());
536 MOZ_ASSERT(!IsUndoRedoEnabled());
537 MOZ_ASSERT(GetEditAction() != EditAction::eReplaceText
);
538 MOZ_ASSERT(mMaxTextLength
< 0);
539 MOZ_ASSERT(aValue
.FindChar(static_cast<char16_t
>('\r')) == kNotFound
);
541 UndefineCaretBidiLevel();
543 // XXX If we're setting value, shouldn't we keep setting the new value here?
544 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
546 MaybeDoAutoPasswordMasking();
548 RefPtr
<Element
> anonymousDivElement
= GetRoot();
549 RefPtr
<Text
> textNode
=
550 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
551 MOZ_ASSERT(textNode
);
553 // We can use this fast path only when:
554 // - we need to insert a text node.
555 // - we need to replace content of existing text node.
556 // Additionally, for avoiding odd result, we should check whether we're in
558 if (!IsSingleLineEditor()) {
559 // If we're a multiline text editor, i.e., <textarea>, there is a padding
560 // <br> element for empty last line followed by scrollbar/resizer elements.
561 // Otherwise, a text node is followed by them.
562 if (!textNode
->GetNextSibling() ||
563 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
564 *textNode
->GetNextSibling())) {
565 return EditActionResult::IgnoredResult();
569 // XXX Password fields accept line breaks as normal characters with this code.
570 // Is this intentional?
571 nsAutoString
sanitizedValue(aValue
);
572 if (IsSingleLineEditor() && !IsPasswordEditor()) {
573 HandleNewLinesInStringForSingleLineEditor(sanitizedValue
);
576 nsresult rv
= SetTextNodeWithoutTransaction(sanitizedValue
, *textNode
);
578 NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
582 return EditActionResult::HandledResult();
585 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelection(
586 nsIEditor::EDirection aDirectionAndAmount
,
587 nsIEditor::EStripWrappers aStripWrappers
) {
588 MOZ_ASSERT(IsEditActionDataAvailable());
589 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
591 UndefineCaretBidiLevel();
593 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
596 return EditActionResult::CanceledResult();
598 Result
<EditActionResult
, nsresult
> result
=
599 HandleDeleteSelectionInternal(aDirectionAndAmount
, nsIEditor::eNoStrip
);
600 // HandleDeleteSelectionInternal() creates SelectionBatcher. Therefore,
601 // quitting from it might cause having destroyed the editor.
602 if (NS_WARN_IF(Destroyed())) {
603 return Err(NS_ERROR_EDITOR_DESTROYED
);
605 NS_WARNING_ASSERTION(
607 "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
611 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelectionInternal(
612 nsIEditor::EDirection aDirectionAndAmount
,
613 nsIEditor::EStripWrappers aStripWrappers
) {
614 MOZ_ASSERT(IsEditActionDataAvailable());
615 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
617 // If the current selection is empty (e.g the user presses backspace with
618 // a collapsed selection), then we want to avoid sending the selectstart
619 // event to the user, so we hide selection changes. However, we still
620 // want to send a single selectionchange event to the document, so we
621 // batch the selectionchange events, such that a single event fires after
622 // the AutoHideSelectionChanges destructor has been run.
623 SelectionBatcher
selectionBatcher(SelectionRef(), __FUNCTION__
);
624 AutoHideSelectionChanges
hideSelection(SelectionRef());
625 nsAutoScriptBlocker scriptBlocker
;
627 if (IsPasswordEditor() && IsMaskingPassword()) {
630 const auto selectionStartPoint
=
631 GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
632 if (NS_WARN_IF(!selectionStartPoint
.IsSet())) {
633 return Err(NS_ERROR_FAILURE
);
636 if (!SelectionRef().IsCollapsed()) {
637 nsresult rv
= DeleteSelectionWithTransaction(aDirectionAndAmount
,
638 nsIEditor::eNoStrip
);
641 "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed");
644 return EditActionResult::HandledResult();
647 // Test for distance between caret and text that will be deleted
648 AutoCaretBidiLevelManager
bidiLevelManager(*this, aDirectionAndAmount
,
649 selectionStartPoint
);
650 if (MOZ_UNLIKELY(bidiLevelManager
.Failed())) {
651 NS_WARNING("EditorBase::AutoCaretBidiLevelManager() failed");
652 return Err(NS_ERROR_FAILURE
);
654 bidiLevelManager
.MaybeUpdateCaretBidiLevel(*this);
655 if (bidiLevelManager
.Canceled()) {
656 return EditActionResult::CanceledResult();
660 AutoClonedSelectionRangeArray
rangesToDelete(SelectionRef());
661 Result
<nsIEditor::EDirection
, nsresult
> result
=
662 rangesToDelete
.ExtendAnchorFocusRangeFor(*this, aDirectionAndAmount
);
663 if (result
.isErr()) {
665 "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
666 return result
.propagateErr();
668 if (const Text
* theTextNode
= GetTextNode()) {
669 rangesToDelete
.EnsureRangesInTextNode(*theTextNode
);
672 Result
<CaretPoint
, nsresult
> caretPointOrError
= DeleteRangesWithTransaction(
673 result
.unwrap(), nsIEditor::eNoStrip
, rangesToDelete
);
674 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
675 NS_WARNING("EditorBase::DeleteRangesWithTransaction(eNoStrip) failed");
676 return caretPointOrError
.propagateErr();
679 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
680 *this, {SuggestCaret::OnlyIfHasSuggestion
,
681 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
682 SuggestCaret::AndIgnoreTrivialError
});
684 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
687 NS_WARNING_ASSERTION(rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
688 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
690 return EditActionResult::HandledResult();
693 Result
<EditActionResult
, nsresult
>
694 TextEditor::ComputeValueFromTextNodeAndBRElement(nsAString
& aValue
) const {
695 MOZ_ASSERT(IsEditActionDataAvailable());
696 MOZ_ASSERT(!IsHTMLEditor());
698 Element
* anonymousDivElement
= GetRoot();
699 if (MOZ_UNLIKELY(!anonymousDivElement
)) {
700 // Don't warn this case, this is possible, e.g., 997805.html
702 return EditActionResult::HandledResult();
705 Text
* textNode
= Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
706 MOZ_ASSERT(textNode
);
708 if (!textNode
->Length()) {
710 return EditActionResult::HandledResult();
713 nsIContent
* firstChildExceptText
= textNode
->GetNextSibling();
714 // If the DOM tree is unexpected, fall back to the expensive path.
715 bool isInput
= IsSingleLineEditor();
716 bool isTextarea
= !isInput
;
717 if (NS_WARN_IF(isInput
&& firstChildExceptText
) ||
718 NS_WARN_IF(isTextarea
&& !firstChildExceptText
) ||
719 NS_WARN_IF(isTextarea
&&
720 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
721 *firstChildExceptText
) &&
722 !firstChildExceptText
->IsXULElement(nsGkAtoms::scrollbar
))) {
723 return EditActionResult::IgnoredResult();
726 // Otherwise, the text data is the value.
727 textNode
->GetData(aValue
);
728 return EditActionResult::HandledResult();
731 Result
<EditActionResult
, nsresult
>
732 TextEditor::MaybeTruncateInsertionStringForMaxLength(
733 nsAString
& aInsertionString
) {
734 MOZ_ASSERT(IsEditActionDataAvailable());
735 MOZ_ASSERT(mMaxTextLength
>= 0);
737 if (IsIMEComposing()) {
738 return EditActionResult::IgnoredResult();
741 // Ignore user pastes
742 switch (GetEditAction()) {
743 case EditAction::ePaste
:
744 case EditAction::ePasteAsQuotation
:
745 case EditAction::eDrop
:
746 case EditAction::eReplaceText
:
747 // EditActionPrinciple() is non-null iff the edit was requested by
749 if (!GetEditActionPrincipal()) {
750 // By now we are certain that this is a user paste, before we ignore it,
751 // lets check if the user explictly enabled truncating user pastes.
752 if (!StaticPrefs::editor_truncate_user_pastes()) {
753 return EditActionResult::IgnoredResult();
761 uint32_t currentLength
= UINT32_MAX
;
762 nsresult rv
= GetTextLength(¤tLength
);
764 NS_WARNING("TextEditor::GetTextLength() failed");
768 uint32_t selectionStart
, selectionEnd
;
769 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
770 selectionStart
, selectionEnd
);
772 TextComposition
* composition
= GetComposition();
773 const uint32_t kOldCompositionStringLength
=
774 composition
? composition
->String().Length() : 0;
776 const uint32_t kSelectionLength
= selectionEnd
- selectionStart
;
777 // XXX This computation must be wrong. If we'll support non-collapsed
778 // selection even during composition for Korean IME, kSelectionLength
779 // is part of kOldCompositionStringLength.
780 const uint32_t kNewLength
=
781 currentLength
- kSelectionLength
- kOldCompositionStringLength
;
782 if (kNewLength
>= AssertedCast
<uint32_t>(mMaxTextLength
)) {
783 aInsertionString
.Truncate(); // Too long, we cannot accept new character.
784 return EditActionResult::HandledResult();
787 if (aInsertionString
.Length() + kNewLength
<=
788 AssertedCast
<uint32_t>(mMaxTextLength
)) {
789 return EditActionResult::IgnoredResult(); // Enough short string.
792 int32_t newInsertionStringLength
= mMaxTextLength
- kNewLength
;
793 MOZ_ASSERT(newInsertionStringLength
> 0);
794 char16_t maybeHighSurrogate
=
795 aInsertionString
.CharAt(newInsertionStringLength
- 1);
796 char16_t maybeLowSurrogate
=
797 aInsertionString
.CharAt(newInsertionStringLength
);
798 // Don't split the surrogate pair.
799 if (NS_IS_SURROGATE_PAIR(maybeHighSurrogate
, maybeLowSurrogate
)) {
800 newInsertionStringLength
--;
802 // XXX What should we do if we're removing IVS but its preceding
803 // character won't be removed?
804 aInsertionString
.Truncate(newInsertionStringLength
);
805 return EditActionResult::HandledResult();
808 bool TextEditor::CanEchoPasswordNow() const {
809 if (!LookAndFeel::GetEchoPassword() || EchoingPasswordPrevented()) {
813 return GetEditAction() != EditAction::eDrop
&&
814 GetEditAction() != EditAction::ePaste
;
817 } // namespace mozilla