Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / editor / libeditor / TextEditSubActionHandler.cpp
blob41105b3070d3b711a8fd6f55f6b2d360f02ac3a7
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 "ErrorList.h"
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"
27 #include "nsCOMPtr.h"
28 #include "nsCRT.h"
29 #include "nsCRTGlue.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsContentUtils.h"
32 #include "nsDebug.h"
33 #include "nsError.h"
34 #include "nsGkAtoms.h"
35 #include "nsIContent.h"
36 #include "nsIHTMLCollection.h"
37 #include "nsINode.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"
45 namespace mozilla {
47 using namespace dom;
49 #define CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY \
50 if (IsReadonly()) { \
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);
69 return;
72 if (NS_WARN_IF(!mInitSucceeded)) {
73 return;
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));
80 return;
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()));
90 if (point.IsSet()) {
91 SetSpellCheckRestartPoint(point);
92 return;
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());
104 nsresult rv;
105 while (true) {
106 if (NS_WARN_IF(Destroyed())) {
107 rv = NS_ERROR_EDITOR_DESTROYED;
108 break;
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
113 // users.
114 if (NS_FAILED(rv = HandleInlineSpellCheckAfterEdit())) {
115 NS_WARNING("TextEditor::HandleInlineSpellCheckAfterEdit() failed");
116 break;
119 if (!IsSingleLineEditor() &&
120 NS_FAILED(rv = EnsurePaddingBRElementInMultilineEditor())) {
121 NS_WARNING(
122 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
123 break;
126 rv = EnsureCaretNotAtEndOfTextNode();
127 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
128 break;
130 NS_WARNING_ASSERTION(
131 NS_SUCCEEDED(rv),
132 "TextEditor::EnsureCaretNotAtEndOfTextNode() failed, but ignored");
133 rv = NS_OK;
134 break;
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);
143 return rv;
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())) {
166 NS_WARNING(
167 "TextEditor::InsertLineFeedCharacterAtSelection() failed, but ignored");
168 return result.unwrapErr();
170 return NS_OK;
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())) {
187 NS_WARNING(
188 "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
189 return result;
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()) {
199 nsresult rv =
200 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
201 if (NS_FAILED(rv)) {
202 NS_WARNING(
203 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
204 return Err(rv);
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>()
233 : pointToInsert;
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
242 // line.
243 pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
244 nsresult rv = CollapseSelectionTo(pointToPutCaret);
245 if (NS_FAILED(rv)) {
246 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
247 return Err(rv);
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()) {
260 return NS_OK;
263 nsresult rv = CollapseSelectionToEndOfTextNode();
264 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
265 NS_WARNING(
266 "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the "
267 "editor");
268 return NS_ERROR_EDITOR_DESTROYED;
270 NS_WARNING_ASSERTION(
271 NS_SUCCEEDED(rv),
272 "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
274 return NS_OK;
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) {
287 return;
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, ' ');
296 break;
297 case nsIEditor::eNewlinesStrip:
298 aString.StripChar(kLF);
299 break;
300 case nsIEditor::eNewlinesPasteToFirst:
301 default: {
302 // we get first *non-empty* line.
303 int32_t offset = 0;
304 while (firstLF == offset) {
305 offset++;
306 firstLF = aString.FindChar(kLF, offset);
308 if (firstLF > 0) {
309 aString.Truncate(firstLF);
311 if (offset > 0) {
312 aString.Cut(0, offset);
314 break;
316 case nsIEditor::eNewlinesReplaceWithCommas:
317 // Default of Thunderbird:
318 aString.Trim(LFSTR, true, true);
319 aString.ReplaceChar(kLF, ',');
320 break;
321 case nsIEditor::eNewlinesStripSurroundingWhitespace: {
322 nsAutoString result;
323 uint32_t offset = 0;
324 while (offset < aString.Length()) {
325 int32_t nextLF = !offset ? firstLF : aString.FindChar(kLF, offset);
326 if (nextLF < 0) {
327 result.Append(nsDependentSubstring(aString, offset));
328 break;
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])) {
333 --wsBegin;
335 result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
336 offset = nextLF + 1;
337 while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) {
338 ++offset;
341 aString = result;
342 break;
344 case nsIEditor::eNewlinesPasteIntact:
345 // even if we're pasting newlines, don't paste leading/trailing ones
346 aString.Trim(LFSTR, true, true);
347 break;
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())) {
367 NS_WARNING(
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();
381 uint32_t start = 0;
382 if (IsPasswordEditor()) {
383 if (GetComposition() && !GetComposition()->String().IsEmpty()) {
384 start = GetComposition()->XPOffsetInTextNode();
385 } else {
386 uint32_t end = 0;
387 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
388 start, end);
392 // if the selection isn't collapsed, delete it.
393 if (!SelectionRef().IsCollapsed() &&
394 aSelectionHandling == SelectionHandling::Delete) {
395 nsresult rv =
396 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
397 if (NS_FAILED(rv)) {
398 NS_WARNING(
399 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
400 return Err(rv);
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
425 // 3. strip newlines
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
438 // `maxlength`?
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});
475 if (NS_FAILED(rv)) {
476 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
477 return Err(rv);
479 NS_WARNING_ASSERTION(
480 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
481 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
482 } else {
483 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
485 Result<InsertTextResult, nsresult> insertTextResult =
486 InsertTextWithTransaction(*document, insertionString,
487 atStartOfSelection,
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(
515 NS_SUCCEEDED(rv),
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());
524 if (NS_FAILED(rv)) {
525 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
526 return Err(rv);
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
559 // usual condition.
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);
579 if (NS_FAILED(rv)) {
580 NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
581 return Err(rv);
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
597 if (IsEmpty()) {
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(
608 result.isOk(),
609 "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
610 return result;
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()) {
630 MaskAllCharacters();
631 } else {
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);
641 if (NS_FAILED(rv)) {
642 NS_WARNING(
643 "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed");
644 return Err(rv);
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});
684 if (NS_FAILED(rv)) {
685 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
686 return Err(rv);
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
702 aValue.Truncate();
703 return EditActionResult::HandledResult();
706 Text* textNode = Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
707 MOZ_ASSERT(textNode);
709 if (!textNode->Length()) {
710 aValue.Truncate();
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
749 // javascript.
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();
757 [[fallthrough]];
758 default:
759 break;
762 uint32_t currentLength = UINT32_MAX;
763 nsresult rv = GetTextLength(&currentLength);
764 if (NS_FAILED(rv)) {
765 NS_WARNING("TextEditor::GetTextLength() failed");
766 return Err(rv);
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()) {
811 return false;
814 return GetEditAction() != EditAction::eDrop &&
815 GetEditAction() != EditAction::ePaste;
818 } // namespace mozilla