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 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
= insertTextResult
.inspect().Handled()
230 ? insertTextResult
.inspect()
231 .EndOfInsertedTextRef()
232 .To
<EditorDOMPoint
>()
234 if (NS_WARN_IF(!pointToPutCaret
.IsSetAndValid())) {
235 return Err(NS_ERROR_FAILURE
);
237 // XXX I don't think we still need this. This must have been required when
238 // `<textarea>` was implemented with text nodes and `<br>` elements.
239 // We want the caret to stick to the content on the "right". We want the
240 // caret to stick to whatever is past the break. This is because the break is
241 // on the same line we were on, but the next content will be on the following
243 pointToPutCaret
.SetInterlinePosition(InterlinePosition::StartOfNextLine
);
244 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
246 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
249 return EditActionResult::HandledResult();
252 nsresult
TextEditor::EnsureCaretNotAtEndOfTextNode() {
253 MOZ_ASSERT(IsEditActionDataAvailable());
255 // If there is no selection ranges, we should set to the end of the editor.
256 // This is usually performed in InitEditorContentAndSelection(), however,
257 // if the editor is reframed, this may be called by
258 // OnEndHandlingTopLevelEditSubAction().
259 if (SelectionRef().RangeCount()) {
263 nsresult rv
= CollapseSelectionToEndOfTextNode();
264 if (MOZ_UNLIKELY(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
266 "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the "
268 return NS_ERROR_EDITOR_DESTROYED
;
270 NS_WARNING_ASSERTION(
272 "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
277 void TextEditor::HandleNewLinesInStringForSingleLineEditor(
278 nsString
& aString
) const {
279 static const char16_t kLF
= static_cast<char16_t
>('\n');
280 MOZ_ASSERT(IsEditActionDataAvailable());
281 MOZ_ASSERT(aString
.FindChar(static_cast<uint16_t>('\r')) == kNotFound
);
283 // First of all, check if aString contains '\n' since if the string
284 // does not include it, we don't need to do nothing here.
285 int32_t firstLF
= aString
.FindChar(kLF
, 0);
286 if (firstLF
== kNotFound
) {
290 switch (mNewlineHandling
) {
291 case nsIEditor::eNewlinesReplaceWithSpaces
:
292 // Default of Firefox:
293 // Strip trailing newlines first so we don't wind up with trailing spaces
294 aString
.Trim(LFSTR
, false, true);
295 aString
.ReplaceChar(kLF
, ' ');
297 case nsIEditor::eNewlinesStrip
:
298 aString
.StripChar(kLF
);
300 case nsIEditor::eNewlinesPasteToFirst
:
302 // we get first *non-empty* line.
304 while (firstLF
== offset
) {
306 firstLF
= aString
.FindChar(kLF
, offset
);
309 aString
.Truncate(firstLF
);
312 aString
.Cut(0, offset
);
316 case nsIEditor::eNewlinesReplaceWithCommas
:
317 // Default of Thunderbird:
318 aString
.Trim(LFSTR
, true, true);
319 aString
.ReplaceChar(kLF
, ',');
321 case nsIEditor::eNewlinesStripSurroundingWhitespace
: {
324 while (offset
< aString
.Length()) {
325 int32_t nextLF
= !offset
? firstLF
: aString
.FindChar(kLF
, offset
);
327 result
.Append(nsDependentSubstring(aString
, offset
));
330 uint32_t wsBegin
= nextLF
;
331 // look backwards for the first non-white-space char
332 while (wsBegin
> offset
&& NS_IS_SPACE(aString
[wsBegin
- 1])) {
335 result
.Append(nsDependentSubstring(aString
, offset
, wsBegin
- offset
));
337 while (offset
< aString
.Length() && NS_IS_SPACE(aString
[offset
])) {
344 case nsIEditor::eNewlinesPasteIntact
:
345 // even if we're pasting newlines, don't paste leading/trailing ones
346 aString
.Trim(LFSTR
, true, true);
351 Result
<EditActionResult
, nsresult
> TextEditor::HandleInsertText(
352 EditSubAction aEditSubAction
, const nsAString
& aInsertionString
,
353 SelectionHandling aSelectionHandling
) {
354 MOZ_ASSERT(IsEditActionDataAvailable());
355 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
||
356 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
357 MOZ_ASSERT_IF(aSelectionHandling
== SelectionHandling::Ignore
,
358 aEditSubAction
== EditSubAction::eInsertTextComingFromIME
);
360 UndefineCaretBidiLevel();
362 nsAutoString
insertionString(aInsertionString
);
363 if (!aInsertionString
.IsEmpty() && mMaxTextLength
>= 0) {
364 Result
<EditActionResult
, nsresult
> result
=
365 MaybeTruncateInsertionStringForMaxLength(insertionString
);
366 if (MOZ_UNLIKELY(result
.isErr())) {
368 "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
369 EditActionResult unwrappedResult
= result
.unwrap();
370 unwrappedResult
.MarkAsHandled();
371 return unwrappedResult
;
373 // If we're exceeding the maxlength when composing IME, we need to clean up
374 // the composing text, so we shouldn't return early.
375 if (result
.inspect().Handled() && insertionString
.IsEmpty() &&
376 aEditSubAction
!= EditSubAction::eInsertTextComingFromIME
) {
377 return EditActionResult::CanceledResult();
382 if (IsPasswordEditor()) {
383 if (GetComposition() && !GetComposition()->String().IsEmpty()) {
384 start
= GetComposition()->XPOffsetInTextNode();
387 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
392 // if the selection isn't collapsed, delete it.
393 if (!SelectionRef().IsCollapsed() &&
394 aSelectionHandling
== SelectionHandling::Delete
) {
396 DeleteSelectionAsSubAction(nsIEditor::eNone
, nsIEditor::eNoStrip
);
399 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
404 if (aInsertionString
.IsEmpty() &&
405 aEditSubAction
!= EditSubAction::eInsertTextComingFromIME
) {
406 // HACK: this is a fix for bug 19395
407 // I can't outlaw all empty insertions
408 // because IME transaction depend on them
409 // There is more work to do to make the
410 // world safe for IME.
411 return EditActionResult::CanceledResult();
414 // XXX Why don't we cancel here? Shouldn't we do this first?
415 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
417 MaybeDoAutoPasswordMasking();
419 // People have lots of different ideas about what text fields
420 // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
421 // The six possible options are:
422 // 0. paste newlines intact
423 // 1. paste up to the first newline (default)
424 // 2. replace newlines with spaces
426 // 4. replace with commas
427 // 5. strip newlines and surrounding white-space
428 // So find out what we're expected to do:
429 if (IsSingleLineEditor()) {
430 // XXX Some callers of TextEditor::InsertTextAsAction() already make the
431 // string use only \n as a linebreaker. However, they are not hot
432 // path and nsContentUtils::PlatformToDOMLineBreaks() does nothing
433 // if the string doesn't include \r. So, let's convert linebreakers
434 // here. Note that there are too many callers of
435 // TextEditor::InsertTextAsAction(). So, it's difficult to keep
436 // maintaining all of them won't reach here without \r nor \r\n.
437 // XXX Should we handle do this before truncating the string for
439 nsContentUtils::PlatformToDOMLineBreaks(insertionString
);
440 HandleNewLinesInStringForSingleLineEditor(insertionString
);
443 const auto atStartOfSelection
= GetFirstSelectionStartPoint
<EditorDOMPoint
>();
444 if (NS_WARN_IF(!atStartOfSelection
.IsSetAndValid())) {
445 return Err(NS_ERROR_FAILURE
);
447 MOZ_ASSERT(!atStartOfSelection
.IsContainerHTMLElement(nsGkAtoms::br
));
449 RefPtr
<Document
> document
= GetDocument();
450 if (NS_WARN_IF(!document
)) {
451 return Err(NS_ERROR_NOT_INITIALIZED
);
454 if (aEditSubAction
== EditSubAction::eInsertTextComingFromIME
) {
455 EditorDOMPoint compositionStartPoint
=
456 GetFirstIMESelectionStartPoint
<EditorDOMPoint
>();
457 if (!compositionStartPoint
.IsSet()) {
458 compositionStartPoint
= FindBetterInsertionPoint(atStartOfSelection
);
459 NS_WARNING_ASSERTION(
460 compositionStartPoint
.IsSet(),
461 "TextEditor::FindBetterInsertionPoint() failed, but ignored");
463 Result
<InsertTextResult
, nsresult
> insertTextResult
=
464 InsertTextWithTransaction(*document
, insertionString
,
465 compositionStartPoint
,
466 InsertTextTo::ExistingTextNodeIfAvailable
);
467 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
468 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
469 return insertTextResult
.propagateErr();
471 nsresult rv
= insertTextResult
.unwrap().SuggestCaretPointTo(
472 *this, {SuggestCaret::OnlyIfHasSuggestion
,
473 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
474 SuggestCaret::AndIgnoreTrivialError
});
476 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
479 NS_WARNING_ASSERTION(
480 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
481 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
483 MOZ_ASSERT(aEditSubAction
== EditSubAction::eInsertText
);
485 Result
<InsertTextResult
, nsresult
> insertTextResult
=
486 InsertTextWithTransaction(*document
, insertionString
,
488 InsertTextTo::ExistingTextNodeIfAvailable
);
489 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
490 NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
491 return insertTextResult
.propagateErr();
493 // Ignore caret suggestion because there was
494 // AutoTransactionsConserveSelection.
495 insertTextResult
.inspect().IgnoreCaretPointSuggestion();
496 if (insertTextResult
.inspect().Handled()) {
497 // Make the caret attach to the inserted text, unless this text ends with
498 // a LF, in which case make the caret attach to the next line.
499 const bool endsWithLF
=
500 !insertionString
.IsEmpty() && insertionString
.Last() == nsCRT::LF
;
501 EditorDOMPoint pointToPutCaret
= insertTextResult
.inspect()
502 .EndOfInsertedTextRef()
503 .To
<EditorDOMPoint
>();
504 pointToPutCaret
.SetInterlinePosition(
505 endsWithLF
? InterlinePosition::StartOfNextLine
506 : InterlinePosition::EndOfLine
);
507 MOZ_ASSERT(pointToPutCaret
.IsInTextNode(),
508 "After inserting text into a text node, insertTextResult "
509 "should return a point in a text node");
510 nsresult rv
= CollapseSelectionTo(pointToPutCaret
);
511 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
512 return Err(NS_ERROR_EDITOR_DESTROYED
);
514 NS_WARNING_ASSERTION(
516 "EditorBase::CollapseSelectionTo() failed, but ignored");
520 // Unmask inputted character(s) if necessary.
521 if (IsPasswordEditor() && IsMaskingPassword() && CanEchoPasswordNow()) {
522 nsresult rv
= SetUnmaskRangeAndNotify(start
, insertionString
.Length(),
523 LookAndFeel::GetPasswordMaskDelay());
525 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
528 return EditActionResult::HandledResult();
531 return EditActionResult::HandledResult();
534 Result
<EditActionResult
, nsresult
> TextEditor::SetTextWithoutTransaction(
535 const nsAString
& aValue
) {
536 MOZ_ASSERT(IsEditActionDataAvailable());
537 MOZ_ASSERT(!IsIMEComposing());
538 MOZ_ASSERT(!IsUndoRedoEnabled());
539 MOZ_ASSERT(GetEditAction() != EditAction::eReplaceText
);
540 MOZ_ASSERT(mMaxTextLength
< 0);
541 MOZ_ASSERT(aValue
.FindChar(static_cast<char16_t
>('\r')) == kNotFound
);
543 UndefineCaretBidiLevel();
545 // XXX If we're setting value, shouldn't we keep setting the new value here?
546 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
548 MaybeDoAutoPasswordMasking();
550 RefPtr
<Element
> anonymousDivElement
= GetRoot();
551 RefPtr
<Text
> textNode
=
552 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
553 MOZ_ASSERT(textNode
);
555 // We can use this fast path only when:
556 // - we need to insert a text node.
557 // - we need to replace content of existing text node.
558 // Additionally, for avoiding odd result, we should check whether we're in
560 if (!IsSingleLineEditor()) {
561 // If we're a multiline text editor, i.e., <textarea>, there is a padding
562 // <br> element for empty last line followed by scrollbar/resizer elements.
563 // Otherwise, a text node is followed by them.
564 if (!textNode
->GetNextSibling() ||
565 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
566 *textNode
->GetNextSibling())) {
567 return EditActionResult::IgnoredResult();
571 // XXX Password fields accept line breaks as normal characters with this code.
572 // Is this intentional?
573 nsAutoString
sanitizedValue(aValue
);
574 if (IsSingleLineEditor() && !IsPasswordEditor()) {
575 HandleNewLinesInStringForSingleLineEditor(sanitizedValue
);
578 nsresult rv
= SetTextNodeWithoutTransaction(sanitizedValue
, *textNode
);
580 NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
584 return EditActionResult::HandledResult();
587 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelection(
588 nsIEditor::EDirection aDirectionAndAmount
,
589 nsIEditor::EStripWrappers aStripWrappers
) {
590 MOZ_ASSERT(IsEditActionDataAvailable());
591 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
593 UndefineCaretBidiLevel();
595 CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
598 return EditActionResult::CanceledResult();
600 Result
<EditActionResult
, nsresult
> result
=
601 HandleDeleteSelectionInternal(aDirectionAndAmount
, nsIEditor::eNoStrip
);
602 // HandleDeleteSelectionInternal() creates SelectionBatcher. Therefore,
603 // quitting from it might cause having destroyed the editor.
604 if (NS_WARN_IF(Destroyed())) {
605 return Err(NS_ERROR_EDITOR_DESTROYED
);
607 NS_WARNING_ASSERTION(
609 "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
613 Result
<EditActionResult
, nsresult
> TextEditor::HandleDeleteSelectionInternal(
614 nsIEditor::EDirection aDirectionAndAmount
,
615 nsIEditor::EStripWrappers aStripWrappers
) {
616 MOZ_ASSERT(IsEditActionDataAvailable());
617 MOZ_ASSERT(aStripWrappers
== nsIEditor::eNoStrip
);
619 // If the current selection is empty (e.g the user presses backspace with
620 // a collapsed selection), then we want to avoid sending the selectstart
621 // event to the user, so we hide selection changes. However, we still
622 // want to send a single selectionchange event to the document, so we
623 // batch the selectionchange events, such that a single event fires after
624 // the AutoHideSelectionChanges destructor has been run.
625 SelectionBatcher
selectionBatcher(SelectionRef(), __FUNCTION__
);
626 AutoHideSelectionChanges
hideSelection(SelectionRef());
627 nsAutoScriptBlocker scriptBlocker
;
629 if (IsPasswordEditor() && IsMaskingPassword()) {
632 const auto selectionStartPoint
=
633 GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
634 if (NS_WARN_IF(!selectionStartPoint
.IsSet())) {
635 return Err(NS_ERROR_FAILURE
);
638 if (!SelectionRef().IsCollapsed()) {
639 nsresult rv
= DeleteSelectionWithTransaction(aDirectionAndAmount
,
640 nsIEditor::eNoStrip
);
643 "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed");
646 return EditActionResult::HandledResult();
649 // Test for distance between caret and text that will be deleted
650 AutoCaretBidiLevelManager
bidiLevelManager(*this, aDirectionAndAmount
,
651 selectionStartPoint
);
652 if (MOZ_UNLIKELY(bidiLevelManager
.Failed())) {
653 NS_WARNING("EditorBase::AutoCaretBidiLevelManager() failed");
654 return Err(NS_ERROR_FAILURE
);
656 bidiLevelManager
.MaybeUpdateCaretBidiLevel(*this);
657 if (bidiLevelManager
.Canceled()) {
658 return EditActionResult::CanceledResult();
662 AutoRangeArray
rangesToDelete(SelectionRef());
663 Result
<nsIEditor::EDirection
, nsresult
> result
=
664 rangesToDelete
.ExtendAnchorFocusRangeFor(*this, aDirectionAndAmount
);
665 if (result
.isErr()) {
666 NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed");
667 return result
.propagateErr();
669 if (const Text
* theTextNode
= GetTextNode()) {
670 rangesToDelete
.EnsureRangesInTextNode(*theTextNode
);
673 Result
<CaretPoint
, nsresult
> caretPointOrError
= DeleteRangesWithTransaction(
674 result
.unwrap(), nsIEditor::eNoStrip
, rangesToDelete
);
675 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
676 NS_WARNING("EditorBase::DeleteRangesWithTransaction(eNoStrip) failed");
677 return caretPointOrError
.propagateErr();
680 nsresult rv
= caretPointOrError
.inspect().SuggestCaretPointTo(
681 *this, {SuggestCaret::OnlyIfHasSuggestion
,
682 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
683 SuggestCaret::AndIgnoreTrivialError
});
685 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
688 NS_WARNING_ASSERTION(rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
689 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
691 return EditActionResult::HandledResult();
694 Result
<EditActionResult
, nsresult
>
695 TextEditor::ComputeValueFromTextNodeAndBRElement(nsAString
& aValue
) const {
696 MOZ_ASSERT(IsEditActionDataAvailable());
697 MOZ_ASSERT(!IsHTMLEditor());
699 Element
* anonymousDivElement
= GetRoot();
700 if (MOZ_UNLIKELY(!anonymousDivElement
)) {
701 // Don't warn this case, this is possible, e.g., 997805.html
703 return EditActionResult::HandledResult();
706 Text
* textNode
= Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
707 MOZ_ASSERT(textNode
);
709 if (!textNode
->Length()) {
711 return EditActionResult::HandledResult();
714 nsIContent
* firstChildExceptText
= textNode
->GetNextSibling();
715 // If the DOM tree is unexpected, fall back to the expensive path.
716 bool isInput
= IsSingleLineEditor();
717 bool isTextarea
= !isInput
;
718 if (NS_WARN_IF(isInput
&& firstChildExceptText
) ||
719 NS_WARN_IF(isTextarea
&& !firstChildExceptText
) ||
720 NS_WARN_IF(isTextarea
&&
721 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
722 *firstChildExceptText
) &&
723 !firstChildExceptText
->IsXULElement(nsGkAtoms::scrollbar
))) {
724 return EditActionResult::IgnoredResult();
727 // Otherwise, the text data is the value.
728 textNode
->GetData(aValue
);
729 return EditActionResult::HandledResult();
732 Result
<EditActionResult
, nsresult
>
733 TextEditor::MaybeTruncateInsertionStringForMaxLength(
734 nsAString
& aInsertionString
) {
735 MOZ_ASSERT(IsEditActionDataAvailable());
736 MOZ_ASSERT(mMaxTextLength
>= 0);
738 if (IsIMEComposing()) {
739 return EditActionResult::IgnoredResult();
742 // Ignore user pastes
743 switch (GetEditAction()) {
744 case EditAction::ePaste
:
745 case EditAction::ePasteAsQuotation
:
746 case EditAction::eDrop
:
747 case EditAction::eReplaceText
:
748 // EditActionPrinciple() is non-null iff the edit was requested by
750 if (!GetEditActionPrincipal()) {
751 // By now we are certain that this is a user paste, before we ignore it,
752 // lets check if the user explictly enabled truncating user pastes.
753 if (!StaticPrefs::editor_truncate_user_pastes()) {
754 return EditActionResult::IgnoredResult();
762 uint32_t currentLength
= UINT32_MAX
;
763 nsresult rv
= GetTextLength(¤tLength
);
765 NS_WARNING("TextEditor::GetTextLength() failed");
769 uint32_t selectionStart
, selectionEnd
;
770 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
771 selectionStart
, selectionEnd
);
773 TextComposition
* composition
= GetComposition();
774 const uint32_t kOldCompositionStringLength
=
775 composition
? composition
->String().Length() : 0;
777 const uint32_t kSelectionLength
= selectionEnd
- selectionStart
;
778 // XXX This computation must be wrong. If we'll support non-collapsed
779 // selection even during composition for Korean IME, kSelectionLength
780 // is part of kOldCompositionStringLength.
781 const uint32_t kNewLength
=
782 currentLength
- kSelectionLength
- kOldCompositionStringLength
;
783 if (kNewLength
>= AssertedCast
<uint32_t>(mMaxTextLength
)) {
784 aInsertionString
.Truncate(); // Too long, we cannot accept new character.
785 return EditActionResult::HandledResult();
788 if (aInsertionString
.Length() + kNewLength
<=
789 AssertedCast
<uint32_t>(mMaxTextLength
)) {
790 return EditActionResult::IgnoredResult(); // Enough short string.
793 int32_t newInsertionStringLength
= mMaxTextLength
- kNewLength
;
794 MOZ_ASSERT(newInsertionStringLength
> 0);
795 char16_t maybeHighSurrogate
=
796 aInsertionString
.CharAt(newInsertionStringLength
- 1);
797 char16_t maybeLowSurrogate
=
798 aInsertionString
.CharAt(newInsertionStringLength
);
799 // Don't split the surrogate pair.
800 if (NS_IS_SURROGATE_PAIR(maybeHighSurrogate
, maybeLowSurrogate
)) {
801 newInsertionStringLength
--;
803 // XXX What should we do if we're removing IVS but its preceding
804 // character won't be removed?
805 aInsertionString
.Truncate(newInsertionStringLength
);
806 return EditActionResult::HandledResult();
809 bool TextEditor::CanEchoPasswordNow() const {
810 if (!LookAndFeel::GetEchoPassword() || EchoingPasswordPrevented()) {
814 return GetEditAction() != EditAction::eDrop
&&
815 GetEditAction() != EditAction::ePaste
;
818 } // namespace mozilla