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(¤tLength); 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