tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TextEditSubActionHandler.cpp (30486B)


      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/. */
      5 
      6 #include "ErrorList.h"
      7 #include "TextEditor.h"
      8 
      9 #include "AutoClonedRangeArray.h"
     10 #include "EditAction.h"
     11 #include "EditorDOMPoint.h"
     12 #include "EditorUtils.h"
     13 #include "HTMLEditor.h"
     14 
     15 #include "mozilla/Assertions.h"
     16 #include "mozilla/Logging.h"
     17 #include "mozilla/LookAndFeel.h"
     18 #include "mozilla/Preferences.h"
     19 #include "mozilla/StaticPrefs_editor.h"
     20 #include "mozilla/TextComposition.h"
     21 #include "mozilla/dom/Element.h"
     22 #include "mozilla/dom/HTMLBRElement.h"
     23 #include "mozilla/dom/NodeFilterBinding.h"
     24 #include "mozilla/dom/NodeIterator.h"
     25 #include "mozilla/dom/Selection.h"
     26 
     27 #include "nsAString.h"
     28 #include "nsCOMPtr.h"
     29 #include "nsCRT.h"
     30 #include "nsCRTGlue.h"
     31 #include "nsComponentManagerUtils.h"
     32 #include "nsContentUtils.h"
     33 #include "nsDebug.h"
     34 #include "nsError.h"
     35 #include "nsGkAtoms.h"
     36 #include "nsIContent.h"
     37 #include "nsIHTMLCollection.h"
     38 #include "nsINode.h"
     39 #include "nsISupports.h"
     40 #include "nsLiteralString.h"
     41 #include "nsNameSpaceManager.h"
     42 #include "nsPrintfCString.h"
     43 #include "nsTextNode.h"
     44 #include "nsUnicharUtils.h"
     45 
     46 namespace mozilla {
     47 
     48 extern LazyLogModule gTextInputLog;  // Defined in EditorBase.cpp
     49 
     50 using namespace dom;
     51 
     52 #define CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY \
     53  if (IsReadonly()) {                                              \
     54    return EditActionResult::CanceledResult();                     \
     55  }
     56 
     57 void TextEditor::OnStartToHandleTopLevelEditSubAction(
     58    EditSubAction aTopLevelEditSubAction,
     59    nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
     60  MOZ_ASSERT(IsEditActionDataAvailable());
     61  MOZ_ASSERT(!aRv.Failed());
     62 
     63  EditorBase::OnStartToHandleTopLevelEditSubAction(
     64      aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
     65 
     66  MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
     67  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
     68             aDirectionOfTopLevelEditSubAction);
     69 
     70  if (NS_WARN_IF(Destroyed())) {
     71    aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
     72    return;
     73  }
     74 
     75  if (NS_WARN_IF(!mInitSucceeded)) {
     76    return;
     77  }
     78 
     79  if (aTopLevelEditSubAction == EditSubAction::eSetText) {
     80    // SetText replaces all text, so spell checker handles starting from the
     81    // start of new value.
     82    SetSpellCheckRestartPoint(EditorDOMPoint(mRootElement, 0));
     83    return;
     84  }
     85 
     86  if (aTopLevelEditSubAction == EditSubAction::eInsertText ||
     87      aTopLevelEditSubAction == EditSubAction::eInsertTextComingFromIME) {
     88    // For spell checker, previous selected node should be text node if
     89    // possible. If anchor is root of editor, it may become invalid offset
     90    // after inserting text.
     91    const EditorRawDOMPoint point =
     92        FindBetterInsertionPoint(EditorRawDOMPoint(SelectionRef().AnchorRef()));
     93    if (point.IsSet()) {
     94      SetSpellCheckRestartPoint(point);
     95      return;
     96    }
     97    NS_WARNING("TextEditor::FindBetterInsertionPoint() failed, but ignored");
     98  }
     99  if (SelectionRef().AnchorRef().IsSet()) {
    100    SetSpellCheckRestartPoint(EditorRawDOMPoint(SelectionRef().AnchorRef()));
    101  }
    102 }
    103 
    104 nsresult TextEditor::OnEndHandlingTopLevelEditSubAction() {
    105  MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
    106 
    107  nsresult rv;
    108  while (true) {
    109    if (NS_WARN_IF(Destroyed())) {
    110      rv = NS_ERROR_EDITOR_DESTROYED;
    111      break;
    112    }
    113 
    114    // XXX Probably, we should spellcheck again after edit action (not top-level
    115    //     sub-action) is handled because the ranges can be referred only by
    116    //     users.
    117    if (NS_FAILED(rv = HandleInlineSpellCheckAfterEdit())) {
    118      NS_WARNING("TextEditor::HandleInlineSpellCheckAfterEdit() failed");
    119      break;
    120    }
    121 
    122    if (!IsSingleLineEditor() &&
    123        NS_FAILED(rv = EnsurePaddingBRElementInMultilineEditor())) {
    124      NS_WARNING(
    125          "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
    126      break;
    127    }
    128 
    129    rv = EnsureCaretNotAtEndOfTextNode();
    130    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    131      break;
    132    }
    133    NS_WARNING_ASSERTION(
    134        NS_SUCCEEDED(rv),
    135        "TextEditor::EnsureCaretNotAtEndOfTextNode() failed, but ignored");
    136    rv = NS_OK;
    137    break;
    138  }
    139  DebugOnly<nsresult> rvIgnored =
    140      EditorBase::OnEndHandlingTopLevelEditSubAction();
    141  NS_WARNING_ASSERTION(
    142      NS_SUCCEEDED(rvIgnored),
    143      "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
    144  MOZ_ASSERT(!GetTopLevelEditSubAction());
    145  MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
    146  return rv;
    147 }
    148 
    149 nsresult TextEditor::InsertLineBreakAsSubAction() {
    150  MOZ_ASSERT(IsEditActionDataAvailable());
    151 
    152  if (NS_WARN_IF(!mInitSucceeded)) {
    153    return NS_ERROR_NOT_INITIALIZED;
    154  }
    155 
    156  IgnoredErrorResult ignoredError;
    157  AutoEditSubActionNotifier startToHandleEditSubAction(
    158      *this, EditSubAction::eInsertLineBreak, nsIEditor::eNext, ignoredError);
    159  if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
    160    return ignoredError.StealNSResult();
    161  }
    162  NS_WARNING_ASSERTION(
    163      !ignoredError.Failed(),
    164      "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
    165 
    166  Result<EditActionResult, nsresult> result =
    167      InsertLineFeedCharacterAtSelection();
    168  if (MOZ_UNLIKELY(result.isErr())) {
    169    NS_WARNING(
    170        "TextEditor::InsertLineFeedCharacterAtSelection() failed, but ignored");
    171    return result.unwrapErr();
    172  }
    173  return NS_OK;
    174 }
    175 
    176 Result<EditActionResult, nsresult>
    177 TextEditor::InsertLineFeedCharacterAtSelection() {
    178  MOZ_ASSERT(IsEditActionDataAvailable());
    179  MOZ_ASSERT(!IsSingleLineEditor());
    180 
    181  UndefineCaretBidiLevel();
    182 
    183  CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
    184 
    185  if (mMaxTextLength >= 0) {
    186    nsAutoString insertionString(u"\n"_ns);
    187    Result<EditActionResult, nsresult> result =
    188        MaybeTruncateInsertionStringForMaxLength(insertionString);
    189    if (MOZ_UNLIKELY(result.isErr())) {
    190      NS_WARNING(
    191          "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
    192      return result;
    193    }
    194    if (result.inspect().Handled()) {
    195      // Don't return as handled since we stopped inserting the line break.
    196      return EditActionResult::CanceledResult();
    197    }
    198  }
    199 
    200  // if the selection isn't collapsed, delete it.
    201  if (!SelectionRef().IsCollapsed()) {
    202    nsresult rv =
    203        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
    204    if (NS_FAILED(rv)) {
    205      NS_WARNING(
    206          "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
    207      return Err(rv);
    208    }
    209  }
    210 
    211  const auto pointToInsert = GetFirstSelectionStartPoint<EditorDOMPoint>();
    212  if (NS_WARN_IF(!pointToInsert.IsSet())) {
    213    return Err(NS_ERROR_FAILURE);
    214  }
    215  MOZ_ASSERT(pointToInsert.IsSetAndValid());
    216  MOZ_ASSERT(!pointToInsert.IsContainerHTMLElement(nsGkAtoms::br));
    217 
    218  // Insert a linefeed character.
    219  Result<InsertTextResult, nsresult> insertTextResult =
    220      InsertTextWithTransaction(u"\n"_ns, pointToInsert,
    221                                InsertTextTo::ExistingTextNodeIfAvailable);
    222  if (MOZ_UNLIKELY(insertTextResult.isErr())) {
    223    NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed");
    224    return insertTextResult.propagateErr();
    225  }
    226  insertTextResult.inspect().IgnoreCaretPointSuggestion();
    227  EditorDOMPoint pointToPutCaret =
    228      insertTextResult.inspect().Handled()
    229          ? insertTextResult.inspect().EndOfInsertedTextRef()
    230          : pointToInsert;
    231  if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) {
    232    return Err(NS_ERROR_FAILURE);
    233  }
    234  // XXX I don't think we still need this.  This must have been required when
    235  //     `<textarea>` was implemented with text nodes and `<br>` elements.
    236  // We want the caret to stick to the content on the "right".  We want the
    237  // caret to stick to whatever is past the break.  This is because the break is
    238  // on the same line we were on, but the next content will be on the following
    239  // line.
    240  pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
    241  nsresult rv = CollapseSelectionTo(pointToPutCaret);
    242  if (NS_FAILED(rv)) {
    243    NS_WARNING("EditorBase::CollapseSelectionTo() failed");
    244    return Err(rv);
    245  }
    246  return EditActionResult::HandledResult();
    247 }
    248 
    249 nsresult TextEditor::EnsureCaretNotAtEndOfTextNode() {
    250  MOZ_ASSERT(IsEditActionDataAvailable());
    251 
    252  // If there is no selection ranges, we should set to the end of the editor.
    253  // This is usually performed in InitEditorContentAndSelection(), however,
    254  // if the editor is reframed, this may be called by
    255  // OnEndHandlingTopLevelEditSubAction().
    256  if (SelectionRef().RangeCount()) {
    257    return NS_OK;
    258  }
    259 
    260  nsresult rv = CollapseSelectionToEndOfTextNode();
    261  if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
    262    NS_WARNING(
    263        "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the "
    264        "editor");
    265    return NS_ERROR_EDITOR_DESTROYED;
    266  }
    267  NS_WARNING_ASSERTION(
    268      NS_SUCCEEDED(rv),
    269      "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
    270 
    271  return NS_OK;
    272 }
    273 
    274 void TextEditor::HandleNewLinesInStringForSingleLineEditor(
    275    nsString& aString) const {
    276  static const char16_t kLF = static_cast<char16_t>('\n');
    277  MOZ_ASSERT(IsEditActionDataAvailable());
    278  MOZ_ASSERT(aString.FindChar(static_cast<uint16_t>('\r')) == kNotFound);
    279 
    280  // First of all, check if aString contains '\n' since if the string
    281  // does not include it, we don't need to do nothing here.
    282  int32_t firstLF = aString.FindChar(kLF, 0);
    283  if (firstLF == kNotFound) {
    284    return;
    285  }
    286 
    287  switch (mNewlineHandling) {
    288    case nsIEditor::eNewlinesReplaceWithSpaces:
    289      // Default of Firefox:
    290      // Strip trailing newlines first so we don't wind up with trailing spaces
    291      aString.Trim(LFSTR, false, true);
    292      aString.ReplaceChar(kLF, ' ');
    293      break;
    294    case nsIEditor::eNewlinesStrip:
    295      aString.StripChar(kLF);
    296      break;
    297    case nsIEditor::eNewlinesPasteToFirst:
    298    default: {
    299      // we get first *non-empty* line.
    300      int32_t offset = 0;
    301      while (firstLF == offset) {
    302        offset++;
    303        firstLF = aString.FindChar(kLF, offset);
    304      }
    305      if (firstLF > 0) {
    306        aString.Truncate(firstLF);
    307      }
    308      if (offset > 0) {
    309        aString.Cut(0, offset);
    310      }
    311      break;
    312    }
    313    case nsIEditor::eNewlinesReplaceWithCommas:
    314      // Default of Thunderbird:
    315      aString.Trim(LFSTR, true, true);
    316      aString.ReplaceChar(kLF, ',');
    317      break;
    318    case nsIEditor::eNewlinesStripSurroundingWhitespace: {
    319      nsAutoString result;
    320      uint32_t offset = 0;
    321      while (offset < aString.Length()) {
    322        int32_t nextLF = !offset ? firstLF : aString.FindChar(kLF, offset);
    323        if (nextLF < 0) {
    324          result.Append(nsDependentSubstring(aString, offset));
    325          break;
    326        }
    327        uint32_t wsBegin = nextLF;
    328        // look backwards for the first non-white-space char
    329        while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1])) {
    330          --wsBegin;
    331        }
    332        result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
    333        offset = nextLF + 1;
    334        while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) {
    335          ++offset;
    336        }
    337      }
    338      aString = result;
    339      break;
    340    }
    341    case nsIEditor::eNewlinesPasteIntact:
    342      // even if we're pasting newlines, don't paste leading/trailing ones
    343      aString.Trim(LFSTR, true, true);
    344      break;
    345  }
    346 }
    347 
    348 Result<EditActionResult, nsresult> TextEditor::HandleInsertText(
    349    const nsAString& aInsertionString, InsertTextFor aPurpose) {
    350  MOZ_ASSERT(IsEditActionDataAvailable());
    351 
    352  MOZ_LOG(
    353      gTextInputLog, LogLevel::Info,
    354      ("%p TextEditor::HandleInsertText(aInsertionString=\"%s\", aPurpose=%s)",
    355       this, NS_ConvertUTF16toUTF8(aInsertionString).get(),
    356       ToString(aPurpose).c_str()));
    357 
    358  UndefineCaretBidiLevel();
    359 
    360  nsAutoString insertionString(aInsertionString);
    361  if (!aInsertionString.IsEmpty() && mMaxTextLength >= 0) {
    362    Result<EditActionResult, nsresult> result =
    363        MaybeTruncateInsertionStringForMaxLength(insertionString);
    364    if (MOZ_UNLIKELY(result.isErr())) {
    365      NS_WARNING(
    366          "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
    367      EditActionResult unwrappedResult = result.unwrap();
    368      unwrappedResult.MarkAsHandled();
    369      return unwrappedResult;
    370    }
    371    // If we're exceeding the maxlength when composing IME, we need to clean up
    372    // the composing text, so we shouldn't return early.
    373    if (result.inspect().Handled() && insertionString.IsEmpty() &&
    374        NothingToDoIfInsertingEmptyText(aPurpose)) {
    375      return EditActionResult::CanceledResult();
    376    }
    377  }
    378 
    379  uint32_t start = 0;
    380  if (IsPasswordEditor()) {
    381    if (GetComposition() && !GetComposition()->String().IsEmpty()) {
    382      start = GetComposition()->XPOffsetInTextNode();
    383    } else {
    384      uint32_t end = 0;
    385      nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
    386                                                start, end);
    387    }
    388  }
    389 
    390  // if the selection isn't collapsed, delete it.
    391  if (!SelectionRef().IsCollapsed() &&
    392      !InsertingTextForExtantComposition(aPurpose)) {
    393    nsresult rv =
    394        DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
    395    if (NS_FAILED(rv)) {
    396      NS_WARNING(
    397          "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
    398      return Err(rv);
    399    }
    400  }
    401 
    402  if (aInsertionString.IsEmpty() && NothingToDoIfInsertingEmptyText(aPurpose)) {
    403    // HACK: this is a fix for bug 19395
    404    // I can't outlaw all empty insertions
    405    // because IME transaction depend on them
    406    // There is more work to do to make the
    407    // world safe for IME.
    408    return EditActionResult::CanceledResult();
    409  }
    410 
    411  // XXX Why don't we cancel here?  Shouldn't we do this first?
    412  CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
    413 
    414  MaybeDoAutoPasswordMasking();
    415 
    416  // People have lots of different ideas about what text fields
    417  // should do with multiline pastes.  See bugs 21032, 23485, 23485, 50935.
    418  // The six possible options are:
    419  // 0. paste newlines intact
    420  // 1. paste up to the first newline (default)
    421  // 2. replace newlines with spaces
    422  // 3. strip newlines
    423  // 4. replace with commas
    424  // 5. strip newlines and surrounding white-space
    425  // So find out what we're expected to do:
    426  if (IsSingleLineEditor()) {
    427    // XXX Some callers of TextEditor::InsertTextAsAction()  already make the
    428    //     string use only \n as a linebreaker.  However, they are not hot
    429    //     path and nsContentUtils::PlatformToDOMLineBreaks() does nothing
    430    //     if the string doesn't include \r.  So, let's convert linebreakers
    431    //     here.  Note that there are too many callers of
    432    //     TextEditor::InsertTextAsAction().  So, it's difficult to keep
    433    //     maintaining all of them won't reach here without \r nor \r\n.
    434    // XXX Should we handle do this before truncating the string for
    435    //     `maxlength`?
    436    nsContentUtils::PlatformToDOMLineBreaks(insertionString);
    437    HandleNewLinesInStringForSingleLineEditor(insertionString);
    438  }
    439 
    440  const auto atStartOfSelection = GetFirstSelectionStartPoint<EditorDOMPoint>();
    441  if (NS_WARN_IF(!atStartOfSelection.IsSetAndValid())) {
    442    return Err(NS_ERROR_FAILURE);
    443  }
    444  MOZ_ASSERT(!atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::br));
    445 
    446  if (InsertingTextForComposition(aPurpose)) {
    447    EditorDOMPoint compositionStartPoint =
    448        GetFirstIMESelectionStartPoint<EditorDOMPoint>();
    449    if (!compositionStartPoint.IsSet()) {
    450      compositionStartPoint = FindBetterInsertionPoint(atStartOfSelection);
    451      NS_WARNING_ASSERTION(
    452          compositionStartPoint.IsSet(),
    453          "TextEditor::FindBetterInsertionPoint() failed, but ignored");
    454    }
    455    Result<InsertTextResult, nsresult> insertTextResult =
    456        InsertTextWithTransaction(insertionString, compositionStartPoint,
    457                                  InsertTextTo::ExistingTextNodeIfAvailable);
    458    if (MOZ_UNLIKELY(insertTextResult.isErr())) {
    459      NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
    460      return insertTextResult.propagateErr();
    461    }
    462    nsresult rv = insertTextResult.unwrap().SuggestCaretPointTo(
    463        *this, {SuggestCaret::OnlyIfHasSuggestion,
    464                SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
    465                SuggestCaret::AndIgnoreTrivialError});
    466    if (NS_FAILED(rv)) {
    467      NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    468      return Err(rv);
    469    }
    470    NS_WARNING_ASSERTION(
    471        rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    472        "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    473  } else {
    474    MOZ_ASSERT(!InsertingTextForComposition(aPurpose));
    475 
    476    Result<InsertTextResult, nsresult> insertTextResult =
    477        InsertTextWithTransaction(insertionString, atStartOfSelection,
    478                                  InsertTextTo::ExistingTextNodeIfAvailable);
    479    if (MOZ_UNLIKELY(insertTextResult.isErr())) {
    480      NS_WARNING("EditorBase::InsertTextWithTransaction() failed");
    481      return insertTextResult.propagateErr();
    482    }
    483    // Ignore caret suggestion because there was
    484    // AutoTransactionsConserveSelection.
    485    insertTextResult.inspect().IgnoreCaretPointSuggestion();
    486    if (insertTextResult.inspect().Handled()) {
    487      // Make the caret attach to the inserted text, unless this text ends with
    488      // a LF, in which case make the caret attach to the next line.
    489      const bool endsWithLF =
    490          !insertionString.IsEmpty() && insertionString.Last() == nsCRT::LF;
    491      EditorDOMPoint pointToPutCaret =
    492          insertTextResult.inspect().EndOfInsertedTextRef();
    493      pointToPutCaret.SetInterlinePosition(
    494          endsWithLF ? InterlinePosition::StartOfNextLine
    495                     : InterlinePosition::EndOfLine);
    496      MOZ_ASSERT(pointToPutCaret.IsInTextNode(),
    497                 "After inserting text into a text node, insertTextResult "
    498                 "should return a point in a text node");
    499      nsresult rv = CollapseSelectionTo(pointToPutCaret);
    500      if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    501        return Err(NS_ERROR_EDITOR_DESTROYED);
    502      }
    503      NS_WARNING_ASSERTION(
    504          NS_SUCCEEDED(rv),
    505          "EditorBase::CollapseSelectionTo() failed, but ignored");
    506    }
    507  }
    508 
    509  // Unmask inputted character(s) if necessary.
    510  if (IsPasswordEditor() && IsMaskingPassword() && CanEchoPasswordNow()) {
    511    nsresult rv = SetUnmaskRangeAndNotify(start, insertionString.Length(),
    512                                          LookAndFeel::GetPasswordMaskDelay());
    513    if (NS_FAILED(rv)) {
    514      NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
    515      return Err(rv);
    516    }
    517    return EditActionResult::HandledResult();
    518  }
    519 
    520  return EditActionResult::HandledResult();
    521 }
    522 
    523 Result<EditActionResult, nsresult> TextEditor::SetTextWithoutTransaction(
    524    const nsAString& aValue) {
    525  MOZ_ASSERT(IsEditActionDataAvailable());
    526  MOZ_ASSERT(!IsIMEComposing());
    527  MOZ_ASSERT(!IsUndoRedoEnabled());
    528  MOZ_ASSERT(GetEditAction() != EditAction::eReplaceText);
    529  MOZ_ASSERT(mMaxTextLength < 0);
    530  MOZ_ASSERT(aValue.FindChar(static_cast<char16_t>('\r')) == kNotFound);
    531 
    532  UndefineCaretBidiLevel();
    533 
    534  // XXX If we're setting value, shouldn't we keep setting the new value here?
    535  CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
    536 
    537  MaybeDoAutoPasswordMasking();
    538 
    539  const RefPtr<Text> textNode = GetTextNode();
    540  MOZ_ASSERT(textNode);
    541 
    542  // We can use this fast path only when:
    543  //  - we need to insert a text node.
    544  //  - we need to replace content of existing text node.
    545  // Additionally, for avoiding odd result, we should check whether we're in
    546  // usual condition.
    547  if (!IsSingleLineEditor()) {
    548    // If we're a multiline text editor, i.e., <textarea>, there is a padding
    549    // <br> element for empty last line followed by scrollbar/resizer elements.
    550    // Otherwise, a text node is followed by them.
    551    if (!textNode->GetNextSibling() ||
    552        !EditorUtils::IsPaddingBRElementForEmptyLastLine(
    553            *textNode->GetNextSibling())) {
    554      return EditActionResult::IgnoredResult();
    555    }
    556  }
    557 
    558  // XXX Password fields accept line breaks as normal characters with this code.
    559  //     Is this intentional?
    560  nsAutoString sanitizedValue(aValue);
    561  if (IsSingleLineEditor() && !IsPasswordEditor()) {
    562    HandleNewLinesInStringForSingleLineEditor(sanitizedValue);
    563  }
    564 
    565  nsresult rv = SetTextNodeWithoutTransaction(sanitizedValue, *textNode);
    566  if (NS_FAILED(rv)) {
    567    NS_WARNING("EditorBase::SetTextNodeWithoutTransaction() failed");
    568    return Err(rv);
    569  }
    570 
    571  return EditActionResult::HandledResult();
    572 }
    573 
    574 Result<EditActionResult, nsresult> TextEditor::HandleDeleteSelection(
    575    nsIEditor::EDirection aDirectionAndAmount,
    576    nsIEditor::EStripWrappers aStripWrappers) {
    577  MOZ_ASSERT(IsEditActionDataAvailable());
    578  MOZ_ASSERT(aStripWrappers == nsIEditor::eNoStrip);
    579 
    580  UndefineCaretBidiLevel();
    581 
    582  CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
    583 
    584  if (IsEmpty()) {
    585    return EditActionResult::CanceledResult();
    586  }
    587  Result<EditActionResult, nsresult> result =
    588      HandleDeleteSelectionInternal(aDirectionAndAmount, nsIEditor::eNoStrip);
    589  // HandleDeleteSelectionInternal() creates SelectionBatcher.  Therefore,
    590  // quitting from it might cause having destroyed the editor.
    591  if (NS_WARN_IF(Destroyed())) {
    592    return Err(NS_ERROR_EDITOR_DESTROYED);
    593  }
    594  NS_WARNING_ASSERTION(
    595      result.isOk(),
    596      "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed");
    597  return result;
    598 }
    599 
    600 Result<EditActionResult, nsresult> TextEditor::HandleDeleteSelectionInternal(
    601    nsIEditor::EDirection aDirectionAndAmount,
    602    nsIEditor::EStripWrappers aStripWrappers) {
    603  MOZ_ASSERT(IsEditActionDataAvailable());
    604  MOZ_ASSERT(aStripWrappers == nsIEditor::eNoStrip);
    605 
    606  // If the current selection is empty (e.g the user presses backspace with
    607  // a collapsed selection), then we want to avoid sending the selectstart
    608  // event to the user, so we hide selection changes. However, we still
    609  // want to send a single selectionchange event to the document, so we
    610  // batch the selectionchange events, such that a single event fires after
    611  // the AutoHideSelectionChanges destructor has been run.
    612  SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
    613  AutoHideSelectionChanges hideSelection(SelectionRef());
    614  nsAutoScriptBlocker scriptBlocker;
    615 
    616  if (IsPasswordEditor() && IsMaskingPassword()) {
    617    MaskAllCharacters();
    618  } else {
    619    const auto selectionStartPoint =
    620        GetFirstSelectionStartPoint<EditorRawDOMPoint>();
    621    if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
    622      return Err(NS_ERROR_FAILURE);
    623    }
    624 
    625    if (!SelectionRef().IsCollapsed()) {
    626      AutoClonedSelectionRangeArray rangesToDelete(SelectionRef());
    627      if (NS_WARN_IF(rangesToDelete.Ranges().IsEmpty())) {
    628        NS_ASSERTION(false,
    629                     "For avoiding to throw incompatible exception for "
    630                     "`execCommand`, fix the caller");
    631        return Err(NS_ERROR_FAILURE);
    632      }
    633 
    634      if (const Text* const textNode = GetTextNode()) {
    635        rangesToDelete.EnsureRangesInTextNode(*textNode);
    636      }
    637 
    638      Result<CaretPoint, nsresult> caretPointOrError =
    639          DeleteRangesWithTransaction(aDirectionAndAmount, aStripWrappers,
    640                                      rangesToDelete);
    641      if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
    642        NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
    643        return caretPointOrError.propagateErr();
    644      }
    645      nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
    646          *this, {SuggestCaret::OnlyIfHasSuggestion,
    647                  SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
    648                  SuggestCaret::AndIgnoreTrivialError});
    649      if (NS_FAILED(rv)) {
    650        NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    651      }
    652      NS_WARNING_ASSERTION(
    653          rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    654          "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    655      return EditActionResult::HandledResult();
    656    }
    657 
    658    // Test for distance between caret and text that will be deleted
    659    AutoCaretBidiLevelManager bidiLevelManager(*this, aDirectionAndAmount,
    660                                               selectionStartPoint);
    661    if (MOZ_UNLIKELY(bidiLevelManager.Failed())) {
    662      NS_WARNING("EditorBase::AutoCaretBidiLevelManager() failed");
    663      return Err(NS_ERROR_FAILURE);
    664    }
    665    bidiLevelManager.MaybeUpdateCaretBidiLevel(*this);
    666    if (bidiLevelManager.Canceled()) {
    667      return EditActionResult::CanceledResult();
    668    }
    669  }
    670 
    671  AutoClonedSelectionRangeArray rangesToDelete(SelectionRef());
    672  Result<nsIEditor::EDirection, nsresult> result =
    673      rangesToDelete.ExtendAnchorFocusRangeFor(*this, aDirectionAndAmount);
    674  if (result.isErr()) {
    675    NS_WARNING(
    676        "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed");
    677    return result.propagateErr();
    678  }
    679  if (const Text* theTextNode = GetTextNode()) {
    680    rangesToDelete.EnsureRangesInTextNode(*theTextNode);
    681  }
    682 
    683  Result<CaretPoint, nsresult> caretPointOrError = DeleteRangesWithTransaction(
    684      result.unwrap(), nsIEditor::eNoStrip, rangesToDelete);
    685  if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
    686    NS_WARNING("EditorBase::DeleteRangesWithTransaction(eNoStrip) failed");
    687    return caretPointOrError.propagateErr();
    688  }
    689 
    690  nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
    691      *this, {SuggestCaret::OnlyIfHasSuggestion,
    692              SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
    693              SuggestCaret::AndIgnoreTrivialError});
    694  if (NS_FAILED(rv)) {
    695    NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
    696    return Err(rv);
    697  }
    698  NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
    699                       "CaretPoint::SuggestCaretPointTo() failed, but ignored");
    700 
    701  return EditActionResult::HandledResult();
    702 }
    703 
    704 Result<EditActionResult, nsresult>
    705 TextEditor::ComputeValueFromTextNodeAndBRElement(nsAString& aValue) const {
    706  MOZ_ASSERT(IsEditActionDataAvailable());
    707  MOZ_ASSERT(!IsHTMLEditor());
    708 
    709  Element* anonymousDivElement = GetRoot();
    710  if (MOZ_UNLIKELY(!anonymousDivElement)) {
    711    // Don't warn this case, this is possible, e.g., 997805.html
    712    aValue.Truncate();
    713    return EditActionResult::HandledResult();
    714  }
    715 
    716  const Text* const textNode = GetTextNode();
    717  MOZ_ASSERT(textNode);
    718 
    719  if (!textNode->Length()) {
    720    aValue.Truncate();
    721    return EditActionResult::HandledResult();
    722  }
    723 
    724  nsIContent* firstChildExceptText = textNode->GetNextSibling();
    725  // If the DOM tree is unexpected, fall back to the expensive path.
    726  bool isInput = IsSingleLineEditor();
    727  bool isTextarea = !isInput;
    728  if (NS_WARN_IF(isInput && firstChildExceptText) ||
    729      NS_WARN_IF(isTextarea && !firstChildExceptText) ||
    730      NS_WARN_IF(isTextarea &&
    731                 !EditorUtils::IsPaddingBRElementForEmptyLastLine(
    732                     *firstChildExceptText) &&
    733                 !firstChildExceptText->IsXULElement(nsGkAtoms::scrollbar))) {
    734    return EditActionResult::IgnoredResult();
    735  }
    736 
    737  // Otherwise, the text data is the value.
    738  textNode->GetData(aValue);
    739  return EditActionResult::HandledResult();
    740 }
    741 
    742 Result<EditActionResult, nsresult>
    743 TextEditor::MaybeTruncateInsertionStringForMaxLength(
    744    nsAString& aInsertionString) {
    745  MOZ_ASSERT(IsEditActionDataAvailable());
    746  MOZ_ASSERT(mMaxTextLength >= 0);
    747 
    748  if (IsIMEComposing()) {
    749    return EditActionResult::IgnoredResult();
    750  }
    751 
    752  // Ignore user pastes
    753  switch (GetEditAction()) {
    754    case EditAction::ePaste:
    755    case EditAction::ePasteAsQuotation:
    756    case EditAction::eDrop:
    757    case EditAction::eReplaceText:
    758      // EditActionPrinciple() is non-null iff the edit was requested by
    759      // javascript.
    760      if (!GetEditActionPrincipal()) {
    761        // By now we are certain that this is a user paste, before we ignore it,
    762        // lets check if the user explictly enabled truncating user pastes.
    763        if (!StaticPrefs::editor_truncate_user_pastes()) {
    764          return EditActionResult::IgnoredResult();
    765        }
    766      }
    767      [[fallthrough]];
    768    default:
    769      break;
    770  }
    771 
    772  uint32_t currentLength = UINT32_MAX;
    773  nsresult rv = GetTextLength(&currentLength);
    774  if (NS_FAILED(rv)) {
    775    NS_WARNING("TextEditor::GetTextLength() failed");
    776    return Err(rv);
    777  }
    778 
    779  uint32_t selectionStart, selectionEnd;
    780  nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
    781                                            selectionStart, selectionEnd);
    782 
    783  TextComposition* composition = GetComposition();
    784  const uint32_t kOldCompositionStringLength =
    785      composition ? composition->String().Length() : 0;
    786 
    787  const uint32_t kSelectionLength = selectionEnd - selectionStart;
    788  // XXX This computation must be wrong.  If we'll support non-collapsed
    789  //     selection even during composition for Korean IME, kSelectionLength
    790  //     is part of kOldCompositionStringLength.
    791  const uint32_t kNewLength =
    792      currentLength - kSelectionLength - kOldCompositionStringLength;
    793  if (kNewLength >= AssertedCast<uint32_t>(mMaxTextLength)) {
    794    aInsertionString.Truncate();  // Too long, we cannot accept new character.
    795    return EditActionResult::HandledResult();
    796  }
    797 
    798  if (aInsertionString.Length() + kNewLength <=
    799      AssertedCast<uint32_t>(mMaxTextLength)) {
    800    return EditActionResult::IgnoredResult();  // Enough short string.
    801  }
    802 
    803  int32_t newInsertionStringLength = mMaxTextLength - kNewLength;
    804  MOZ_ASSERT(newInsertionStringLength > 0);
    805  char16_t maybeHighSurrogate =
    806      aInsertionString.CharAt(newInsertionStringLength - 1);
    807  char16_t maybeLowSurrogate =
    808      aInsertionString.CharAt(newInsertionStringLength);
    809  // Don't split the surrogate pair.
    810  if (NS_IS_SURROGATE_PAIR(maybeHighSurrogate, maybeLowSurrogate)) {
    811    newInsertionStringLength--;
    812  }
    813  // XXX What should we do if we're removing IVS but its preceding
    814  //     character won't be removed?
    815  aInsertionString.Truncate(newInsertionStringLength);
    816  return EditActionResult::HandledResult();
    817 }
    818 
    819 bool TextEditor::CanEchoPasswordNow() const {
    820  if (!LookAndFeel::GetEchoPassword() || EchoingPasswordPrevented()) {
    821    return false;
    822  }
    823 
    824  return GetEditAction() != EditAction::eDrop &&
    825         GetEditAction() != EditAction::ePaste;
    826 }
    827 
    828 }  // namespace mozilla