no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / editor / libeditor / TextEditSubActionHandler.cpp
blob526407d24af1c16873213c0844bc71431d94373f
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 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>()
232 : pointToInsert;
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
241 // line.
242 pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
243 nsresult rv = CollapseSelectionTo(pointToPutCaret);
244 if (NS_FAILED(rv)) {
245 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
246 return Err(rv);
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()) {
259 return NS_OK;
262 nsresult rv = CollapseSelectionToEndOfTextNode();
263 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
264 NS_WARNING(
265 "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the "
266 "editor");
267 return NS_ERROR_EDITOR_DESTROYED;
269 NS_WARNING_ASSERTION(
270 NS_SUCCEEDED(rv),
271 "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
273 return NS_OK;
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) {
286 return;
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, ' ');
295 break;
296 case nsIEditor::eNewlinesStrip:
297 aString.StripChar(kLF);
298 break;
299 case nsIEditor::eNewlinesPasteToFirst:
300 default: {
301 // we get first *non-empty* line.
302 int32_t offset = 0;
303 while (firstLF == offset) {
304 offset++;
305 firstLF = aString.FindChar(kLF, offset);
307 if (firstLF > 0) {
308 aString.Truncate(firstLF);
310 if (offset > 0) {
311 aString.Cut(0, offset);
313 break;
315 case nsIEditor::eNewlinesReplaceWithCommas:
316 // Default of Thunderbird:
317 aString.Trim(LFSTR, true, true);
318 aString.ReplaceChar(kLF, ',');
319 break;
320 case nsIEditor::eNewlinesStripSurroundingWhitespace: {
321 nsAutoString result;
322 uint32_t offset = 0;
323 while (offset < aString.Length()) {
324 int32_t nextLF = !offset ? firstLF : aString.FindChar(kLF, offset);
325 if (nextLF < 0) {
326 result.Append(nsDependentSubstring(aString, offset));
327 break;
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])) {
332 --wsBegin;
334 result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
335 offset = nextLF + 1;
336 while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) {
337 ++offset;
340 aString = result;
341 break;
343 case nsIEditor::eNewlinesPasteIntact:
344 // even if we're pasting newlines, don't paste leading/trailing ones
345 aString.Trim(LFSTR, true, true);
346 break;
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())) {
366 NS_WARNING(
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();
380 uint32_t start = 0;
381 if (IsPasswordEditor()) {
382 if (GetComposition() && !GetComposition()->String().IsEmpty()) {
383 start = GetComposition()->XPOffsetInTextNode();
384 } else {
385 uint32_t end = 0;
386 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
387 start, end);
391 // if the selection isn't collapsed, delete it.
392 if (!SelectionRef().IsCollapsed() &&
393 aSelectionHandling == SelectionHandling::Delete) {
394 nsresult rv =
395 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
396 if (NS_FAILED(rv)) {
397 NS_WARNING(
398 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
399 return Err(rv);
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
424 // 3. strip newlines
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
437 // `maxlength`?
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});
473 if (NS_FAILED(rv)) {
474 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
475 return Err(rv);
477 NS_WARNING_ASSERTION(
478 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
479 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
480 } else {
481 MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
483 Result<InsertTextResult, nsresult> insertTextResult =
484 InsertTextWithTransaction(*document, insertionString,
485 atStartOfSelection);
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(
512 NS_SUCCEEDED(rv),
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());
521 if (NS_FAILED(rv)) {
522 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
523 return Err(rv);
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
556 // usual condition.
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);
576 if (NS_FAILED(rv)) {
577 NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
578 return Err(rv);
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
594 if (IsEmpty()) {
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(
605 result.isOk(),
606 "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
607 return result;
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()) {
627 MaskAllCharacters();
628 } else {
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);
638 if (NS_FAILED(rv)) {
639 NS_WARNING(
640 "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed");
641 return Err(rv);
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});
681 if (NS_FAILED(rv)) {
682 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
683 return Err(rv);
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
699 aValue.Truncate();
700 return EditActionResult::HandledResult();
703 Text* textNode = Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
704 MOZ_ASSERT(textNode);
706 if (!textNode->Length()) {
707 aValue.Truncate();
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
746 // javascript.
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();
754 [[fallthrough]];
755 default:
756 break;
759 uint32_t currentLength = UINT32_MAX;
760 nsresult rv = GetTextLength(&currentLength);
761 if (NS_FAILED(rv)) {
762 NS_WARNING("TextEditor::GetTextLength() failed");
763 return Err(rv);
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()) {
808 return false;
811 return GetEditAction() != EditAction::eDrop &&
812 GetEditAction() != EditAction::ePaste;
815 } // namespace mozilla