TextEditor.cpp (49760B)
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 "TextEditor.h" 7 8 #include <algorithm> 9 10 #include "EditAction.h" 11 #include "EditAggregateTransaction.h" 12 #include "EditorDOMPoint.h" 13 #include "HTMLEditor.h" 14 #include "HTMLEditUtils.h" 15 #include "InternetCiter.h" 16 #include "PlaceholderTransaction.h" 17 #include "gfxFontUtils.h" 18 19 #include "mozilla/dom/DocumentInlines.h" 20 #include "mozilla/Assertions.h" 21 #include "mozilla/ContentIterator.h" 22 #include "mozilla/IMEStateManager.h" 23 #include "mozilla/Logging.h" 24 #include "mozilla/LookAndFeel.h" 25 #include "mozilla/mozalloc.h" 26 #include "mozilla/Preferences.h" 27 #include "mozilla/PresShell.h" 28 #include "mozilla/StaticPrefs_dom.h" 29 #include "mozilla/StaticPrefs_editor.h" 30 #include "mozilla/TextComposition.h" 31 #include "mozilla/TextEvents.h" 32 #include "mozilla/TextServicesDocument.h" 33 #include "mozilla/Try.h" 34 #include "mozilla/dom/CharacterDataBuffer.h" 35 #include "mozilla/dom/Event.h" 36 #include "mozilla/dom/Element.h" 37 #include "mozilla/dom/Selection.h" 38 #include "mozilla/dom/StaticRange.h" 39 40 #include "nsAString.h" 41 #include "nsCRT.h" 42 #include "nsCaret.h" 43 #include "nsCharTraits.h" 44 #include "nsComponentManagerUtils.h" 45 #include "nsContentList.h" 46 #include "nsDebug.h" 47 #include "nsDependentSubstring.h" 48 #include "nsError.h" 49 #include "nsFocusManager.h" 50 #include "nsGkAtoms.h" 51 #include "nsIContent.h" 52 #include "nsINode.h" 53 #include "nsIPrincipal.h" 54 #include "nsISelectionController.h" 55 #include "nsISupportsPrimitives.h" 56 #include "nsITransferable.h" 57 #include "nsIWeakReferenceUtils.h" 58 #include "nsNameSpaceManager.h" 59 #include "nsLiteralString.h" 60 #include "nsPresContext.h" 61 #include "nsReadableUtils.h" 62 #include "nsServiceManagerUtils.h" 63 #include "nsString.h" 64 #include "nsStringFwd.h" 65 #include "nsTextNode.h" 66 #include "nsUnicharUtils.h" 67 #include "nsXPCOM.h" 68 69 class nsIOutputStream; 70 class nsISupports; 71 72 namespace mozilla { 73 74 // This logs the important things for the lifecycle of the TextEditor. 75 LazyLogModule gTextEditorLog("TextEditor"); 76 77 static void LogOrWarn(const TextEditor* aTextEditor, LazyLogModule& aLog, 78 LogLevel aLogLevel, const char* aStr) { 79 #ifdef DEBUG 80 if (MOZ_LOG_TEST(aLog, aLogLevel)) { 81 MOZ_LOG(aLog, aLogLevel, ("%p: %s", aTextEditor, aStr)); 82 } else { 83 NS_WARNING(aStr); 84 } 85 #else 86 MOZ_LOG(aLog, aLogLevel, ("%p: %s", aTextEditor, aStr)); 87 #endif 88 } 89 90 using namespace dom; 91 92 using LeafNodeType = HTMLEditUtils::LeafNodeType; 93 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 94 95 template EditorDOMPoint TextEditor::FindBetterInsertionPoint( 96 const EditorDOMPoint& aPoint) const; 97 template EditorRawDOMPoint TextEditor::FindBetterInsertionPoint( 98 const EditorRawDOMPoint& aPoint) const; 99 100 TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text) { 101 // printf("Size of TextEditor: %zu\n", sizeof(TextEditor)); 102 static_assert( 103 sizeof(TextEditor) <= 512, 104 "TextEditor instance should be allocatable in the quantum class bins"); 105 MOZ_LOG(gTextEditorLog, LogLevel::Info, 106 ("%p: New instance is created", this)); 107 } 108 109 TextEditor::~TextEditor() { 110 // Remove event listeners. Note that if we had an HTML editor, 111 // it installed its own instead of these 112 RemoveEventListeners(); 113 114 MOZ_LOG(gTextEditorLog, LogLevel::Info, ("%p: Deleted", this)); 115 } 116 117 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor) 118 119 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase) 120 if (tmp->mPasswordMaskData) { 121 tmp->mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No); 122 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPasswordMaskData->mTimer) 123 } 124 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 125 126 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase) 127 if (tmp->mPasswordMaskData) { 128 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPasswordMaskData->mTimer) 129 } 130 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 131 132 NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase) 133 NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase) 134 135 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor) 136 NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 137 NS_INTERFACE_MAP_ENTRY(nsINamed) 138 NS_INTERFACE_MAP_END_INHERITING(EditorBase) 139 140 NS_IMETHODIMP TextEditor::EndOfDocument() { 141 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 142 if (NS_WARN_IF(!editActionData.CanHandle())) { 143 return NS_ERROR_NOT_INITIALIZED; 144 } 145 nsresult rv = CollapseSelectionToEndOfTextNode(); 146 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 147 "TextEditor::CollapseSelectionToEndOfTextNode() failed"); 148 // This is low level API for embedders and chrome script so that we can return 149 // raw error code here. 150 return rv; 151 } 152 153 nsresult TextEditor::CollapseSelectionToEndOfTextNode() { 154 MOZ_ASSERT(IsEditActionDataAvailable()); 155 156 Element* anonymousDivElement = GetRoot(); 157 if (NS_WARN_IF(!anonymousDivElement)) { 158 return NS_ERROR_NULL_POINTER; 159 } 160 161 RefPtr<Text> textNode = 162 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild()); 163 MOZ_ASSERT(textNode); 164 nsresult rv = CollapseSelectionToEndOf(*textNode); 165 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 166 "EditorBase::CollapseSelectionToEndOf() failed"); 167 return rv; 168 } 169 170 nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement, 171 nsISelectionController& aSelectionController, 172 uint32_t aFlags, 173 UniquePtr<PasswordMaskData>&& aPasswordMaskData) { 174 MOZ_ASSERT(!mInitSucceeded, 175 "TextEditor::Init() called again without calling PreDestroy()?"); 176 MOZ_ASSERT(!(aFlags & nsIEditor::eEditorPasswordMask) == !aPasswordMaskData); 177 178 MOZ_LOG(gTextEditorLog, LogLevel::Info, 179 ("%p: Init(aDocument=%p, aAnonymousDivElement=%s, " 180 "aSelectionController=%p, aPasswordMaskData=%p)", 181 this, &aDocument, ToString(RefPtr{&aAnonymousDivElement}).c_str(), 182 &aSelectionController, aPasswordMaskData.get())); 183 184 mPasswordMaskData = std::move(aPasswordMaskData); 185 186 // Init the base editor 187 nsresult rv = InitInternal(aDocument, &aAnonymousDivElement, 188 aSelectionController, aFlags); 189 if (NS_FAILED(rv)) { 190 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 191 "EditorBase::InitInternal() failed"); 192 return rv; 193 } 194 195 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); 196 if (MOZ_UNLIKELY(!editActionData.CanHandle())) { 197 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 198 "AutoEditActionDataSetter::CanHandle() failed"); 199 return NS_ERROR_FAILURE; 200 } 201 202 // We set the initialized state here rather than at the end of the function, 203 // since InitEditorContentAndSelection() can perform some transactions 204 // and can warn if mInitSucceeded is still false. 205 MOZ_ASSERT(!mInitSucceeded, "TextEditor::Init() shouldn't be nested"); 206 mInitSucceeded = true; 207 editActionData.OnEditorInitialized(); 208 209 rv = InitEditorContentAndSelection(); 210 if (NS_FAILED(rv)) { 211 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 212 "TextEditor::InitEditorContentAndSelection() failed"); 213 // XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this 214 // is a public method? 215 mInitSucceeded = false; 216 editActionData.OnEditorDestroy(); 217 return EditorBase::ToGenericNSResult(rv); 218 } 219 220 // Throw away the old transaction manager if this is not the first time that 221 // we're initializing the editor. 222 ClearUndoRedo(); 223 EnableUndoRedo(); 224 return NS_OK; 225 } 226 227 nsresult TextEditor::InitEditorContentAndSelection() { 228 MOZ_ASSERT(IsEditActionDataAvailable()); 229 230 MOZ_TRY(EnsureEmptyTextFirstChild()); 231 232 // If the selection hasn't been set up yet, set it up collapsed to the end of 233 // our editable content. 234 if (!SelectionRef().RangeCount()) { 235 nsresult rv = CollapseSelectionToEndOfTextNode(); 236 if (NS_FAILED(rv)) { 237 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 238 "EditorBase::CollapseSelectionToEndOfTextNode() failed"); 239 return rv; 240 } 241 } 242 243 if (!IsSingleLineEditor()) { 244 nsresult rv = EnsurePaddingBRElementInMultilineEditor(); 245 if (NS_FAILED(rv)) { 246 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 247 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed"); 248 return rv; 249 } 250 } 251 252 return NS_OK; 253 } 254 255 nsresult TextEditor::PostCreate() { 256 MOZ_LOG(gTextEditorLog, LogLevel::Info, 257 ("%p: PostCreate(), mDidPostCreate=%s", this, 258 TrueOrFalse(mDidPostCreate))); 259 260 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 261 if (MOZ_UNLIKELY(!editActionData.CanHandle())) { 262 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 263 "AutoEditActionDataSetter::CanHandle() failed"); 264 return NS_ERROR_NOT_INITIALIZED; 265 } 266 267 nsresult rv = PostCreateInternal(); 268 269 // Restore unmasked range if there is. 270 if (IsPasswordEditor() && !IsAllMasked()) { 271 DebugOnly<nsresult> rvIgnored = 272 SetUnmaskRangeAndNotify(UnmaskedStart(), UnmaskedLength()); 273 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 274 "TextEditor::SetUnmaskRangeAndNotify() failed to " 275 "restore unmasked range, but ignored"); 276 } 277 278 if (NS_FAILED(rv)) { 279 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 280 "EditorBase::PostCreateInternal() failed"); 281 return rv; 282 } 283 284 return NS_OK; 285 } 286 287 UniquePtr<PasswordMaskData> TextEditor::PreDestroy() { 288 MOZ_LOG(gTextEditorLog, LogLevel::Info, 289 ("%p: PreDestroy() mDidPreDestroy=%s", this, 290 TrueOrFalse(mDidPreDestroy))); 291 292 if (mDidPreDestroy) { 293 return nullptr; 294 } 295 296 UniquePtr<PasswordMaskData> passwordMaskData = std::move(mPasswordMaskData); 297 if (passwordMaskData) { 298 // Disable auto-masking timer since nobody can catch the notification 299 // from the timer and canceling the unmasking. 300 passwordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes); 301 // Similary, keeping preventing echoing password temporarily across 302 // TextEditor instances is hard. So, we should forget it. 303 passwordMaskData->mEchoingPasswordPrevented = false; 304 } 305 306 PreDestroyInternal(); 307 308 return passwordMaskData; 309 } 310 311 Result<widget::IMEState, nsresult> TextEditor::GetPreferredIMEState() const { 312 using IMEState = widget::IMEState; 313 using IMEEnabled = widget::IMEEnabled; 314 315 if (IsReadonly()) { 316 return IMEState{IMEEnabled::Disabled, IMEState::DONT_CHANGE_OPEN_STATE}; 317 } 318 319 Element* const textControlElement = GetExposedRoot(); 320 if (NS_WARN_IF(!textControlElement)) { 321 return Err(NS_ERROR_FAILURE); 322 } 323 MOZ_ASSERT(textControlElement->IsTextControlElement()); 324 325 nsIFrame* const textControlFrame = textControlElement->GetPrimaryFrame(); 326 if (NS_WARN_IF(!textControlFrame)) { 327 return Err(NS_ERROR_FAILURE); 328 } 329 330 switch (textControlFrame->StyleUIReset()->mIMEMode) { 331 case StyleImeMode::Auto: 332 default: 333 return IMEState{ 334 IsPasswordEditor() ? IMEEnabled::Password : IMEEnabled::Enabled, 335 IMEState::DONT_CHANGE_OPEN_STATE}; 336 case StyleImeMode::Disabled: 337 // we should use password state for |ime-mode: disabled;|. 338 return IMEState{IMEEnabled::Password, IMEState::DONT_CHANGE_OPEN_STATE}; 339 case StyleImeMode::Active: 340 return IMEState{IMEEnabled::Enabled, IMEState::OPEN}; 341 case StyleImeMode::Inactive: 342 return IMEState{IMEEnabled::Enabled, IMEState::CLOSED}; 343 case StyleImeMode::Normal: 344 return IMEState{IMEEnabled::Enabled, IMEState::DONT_CHANGE_OPEN_STATE}; 345 } 346 } 347 348 nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { 349 // NOTE: When you change this method, you should also change: 350 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html 351 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html 352 // 353 // And also when you add new key handling, you need to change the subclass's 354 // HandleKeyPressEvent()'s switch statement. 355 356 if (NS_WARN_IF(!aKeyboardEvent)) { 357 return NS_ERROR_UNEXPECTED; 358 } 359 360 if (IsReadonly()) { 361 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent); 362 return NS_OK; 363 } 364 365 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress, 366 "HandleKeyPressEvent gets non-keypress event"); 367 368 switch (aKeyboardEvent->mKeyCode) { 369 case NS_VK_META: 370 case NS_VK_WIN: 371 case NS_VK_SHIFT: 372 case NS_VK_CONTROL: 373 case NS_VK_ALT: 374 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress 375 // event. 376 aKeyboardEvent->PreventDefault(); 377 return NS_OK; 378 379 case NS_VK_BACK: 380 case NS_VK_DELETE: 381 case NS_VK_TAB: { 382 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent); 383 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 384 "EditorBase::HandleKeyPressEvent() failed"); 385 return rv; 386 } 387 case NS_VK_RETURN: { 388 if (!aKeyboardEvent->IsInputtingLineBreak()) { 389 return NS_OK; 390 } 391 if (!IsSingleLineEditor()) { 392 aKeyboardEvent->PreventDefault(); 393 } 394 // We need to dispatch "beforeinput" event at least even if we're a 395 // single line text editor. 396 nsresult rv = InsertLineBreakAsAction(); 397 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 398 "TextEditor::InsertLineBreakAsAction() failed"); 399 return rv; 400 } 401 } 402 403 if (!aKeyboardEvent->IsInputtingText()) { 404 // we don't PreventDefault() here or keybindings like control-x won't work 405 return NS_OK; 406 } 407 aKeyboardEvent->PreventDefault(); 408 // If we dispatch 2 keypress events for a surrogate pair and we set only 409 // first `.key` value to the surrogate pair, the preceding one has it and the 410 // other has empty string. In this case, we should handle only the first one 411 // with the key value. 412 if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() && 413 !StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() && 414 aKeyboardEvent->mKeyValue.IsEmpty() && 415 IS_SURROGATE(aKeyboardEvent->mCharCode)) { 416 return NS_OK; 417 } 418 // Our widget shouldn't set `\r` to `mKeyValue`, but it may be synthesized 419 // keyboard event and its value may be `\r`. In such case, we should treat 420 // it as `\n` for the backward compatibility because we stopped converting 421 // `\r` and `\r\n` to `\n` at getting `HTMLInputElement.value` and 422 // `HTMLTextAreaElement.value` for the performance (i.e., we don't need to 423 // take care in `HTMLEditor`). 424 nsAutoString str(aKeyboardEvent->mKeyValue); 425 if (str.IsEmpty()) { 426 MOZ_ASSERT(aKeyboardEvent->mCharCode <= 0xFFFF, 427 "Non-BMP character needs special handling"); 428 str.Assign(aKeyboardEvent->mCharCode == nsCRT::CR 429 ? static_cast<char16_t>(nsCRT::LF) 430 : static_cast<char16_t>(aKeyboardEvent->mCharCode)); 431 } else { 432 MOZ_ASSERT(str.Find(u"\r\n"_ns) == kNotFound, 433 "This assumes that typed text does not include CRLF"); 434 str.ReplaceChar('\r', '\n'); 435 } 436 nsresult rv = OnInputText(str); 437 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed"); 438 return rv; 439 } 440 441 NS_IMETHODIMP TextEditor::InsertLineBreak() { 442 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak); 443 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 444 if (NS_FAILED(rv)) { 445 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 446 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 447 return EditorBase::ToGenericNSResult(rv); 448 } 449 450 if (NS_WARN_IF(IsSingleLineEditor())) { 451 return NS_ERROR_FAILURE; 452 } 453 454 AutoPlaceholderBatch treatAsOneTransaction( 455 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 456 rv = InsertLineBreakAsSubAction(); 457 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 458 "TextEditor::InsertLineBreakAsSubAction() failed"); 459 return EditorBase::ToGenericNSResult(rv); 460 } 461 462 nsresult TextEditor::ComputeTextValue(nsAString& aString) const { 463 // This is a public method so that anytime this may be called, e.g., before 464 // initialized or after destroyed. However, GetTextNode() is an internal API 465 // for friend classes and internal use. Therefore, it asserts the conditions 466 // whether the anonymous subtree is available. Therefore, this method needs 467 // to get the Text with the anonymous <div>. 468 const Element* const anonymousDivElement = GetRoot(); 469 if (MOZ_UNLIKELY(!anonymousDivElement)) { 470 // If we're temporarily destroyed while we're handling something, we can get 471 // the value from the cached text node which was maybe modified by us. This 472 // helps TextControlState and TextInputListener to notify the text control 473 // element of value changes, etc, at UnbindFromFrame(). 474 const Text* const cachedTextNode = GetCachedTextNode(); 475 if (NS_WARN_IF(!cachedTextNode)) { 476 return NS_ERROR_NOT_INITIALIZED; 477 } 478 cachedTextNode->GetData(aString); 479 return NS_OK; 480 } 481 482 const auto* const text = 483 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild()); 484 if (MOZ_UNLIKELY(!text)) { 485 MOZ_ASSERT_UNREACHABLE("how?"); 486 return NS_ERROR_UNEXPECTED; 487 } 488 489 text->GetData(aString); 490 return NS_OK; 491 } 492 493 nsresult TextEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) { 494 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak, 495 aPrincipal); 496 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 497 if (NS_FAILED(rv)) { 498 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 499 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 500 return EditorBase::ToGenericNSResult(rv); 501 } 502 503 if (IsSingleLineEditor()) { 504 return NS_OK; 505 } 506 507 // XXX This may be called by execCommand() with "insertParagraph". 508 // In such case, naming the transaction "TypingTxnName" is odd. 509 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName, 510 ScrollSelectionIntoView::Yes, 511 __FUNCTION__); 512 rv = InsertLineBreakAsSubAction(); 513 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 514 "EditorBase::InsertLineBreakAsSubAction() failed"); 515 return EditorBase::ToGenericNSResult(rv); 516 } 517 518 nsresult TextEditor::SetTextAsAction( 519 const nsAString& aString, 520 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable, 521 nsIPrincipal* aPrincipal) { 522 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound); 523 524 AutoEditActionDataSetter editActionData(*this, EditAction::eSetText, 525 aPrincipal); 526 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) { 527 editActionData.MakeBeforeInputEventNonCancelable(); 528 } 529 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 530 if (NS_FAILED(rv)) { 531 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 532 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 533 return EditorBase::ToGenericNSResult(rv); 534 } 535 536 AutoPlaceholderBatch treatAsOneTransaction( 537 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 538 rv = SetTextAsSubAction(aString); 539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 540 "TextEditor::SetTextAsSubAction() failed"); 541 return EditorBase::ToGenericNSResult(rv); 542 } 543 544 nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) { 545 MOZ_ASSERT(IsEditActionDataAvailable()); 546 MOZ_ASSERT(mPlaceholderBatch); 547 548 if (NS_WARN_IF(!mInitSucceeded)) { 549 return NS_ERROR_NOT_INITIALIZED; 550 } 551 552 IgnoredErrorResult ignoredError; 553 AutoEditSubActionNotifier startToHandleEditSubAction( 554 *this, EditSubAction::eSetText, nsIEditor::eNext, ignoredError); 555 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 556 return ignoredError.StealNSResult(); 557 } 558 NS_WARNING_ASSERTION( 559 !ignoredError.Failed(), 560 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 561 562 if (!IsIMEComposing() && !IsUndoRedoEnabled() && 563 GetEditAction() != EditAction::eReplaceText && mMaxTextLength < 0) { 564 Result<EditActionResult, nsresult> result = 565 SetTextWithoutTransaction(aString); 566 if (MOZ_UNLIKELY(result.isErr())) { 567 NS_WARNING("TextEditor::SetTextWithoutTransaction() failed"); 568 return result.unwrapErr(); 569 } 570 if (!result.inspect().Ignored()) { 571 return NS_OK; 572 } 573 } 574 575 { 576 // Note that do not notify selectionchange caused by selecting all text 577 // because it's preparation of our delete implementation so web apps 578 // shouldn't receive such selectionchange before the first mutation. 579 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__); 580 581 // XXX We should make ReplaceSelectionAsSubAction() take range. Then, 582 // we can saving the expensive cost of modifying `Selection` here. 583 if (NS_SUCCEEDED(SelectEntireDocument())) { 584 DebugOnly<nsresult> rvIgnored = ReplaceSelectionAsSubAction(aString); 585 NS_WARNING_ASSERTION( 586 NS_SUCCEEDED(rvIgnored), 587 "EditorBase::ReplaceSelectionAsSubAction() failed, but ignored"); 588 } 589 } 590 591 // Destroying AutoUpdateViewBatch may cause destroying us. 592 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK; 593 } 594 595 already_AddRefed<Element> TextEditor::GetInputEventTargetElement() const { 596 RefPtr<Element> target = Element::FromEventTargetOrNull(mEventTarget); 597 return target.forget(); 598 } 599 600 bool TextEditor::IsEmpty() const { 601 // This is a public method so that anytime this may be called, e.g., before 602 // initialized or after destroyed. However, GetTextNode() is an internal API 603 // for friend classes and internal use. Therefore, it asserts the conditions 604 // whether the anonymous subtree is available. Therefore, this method needs 605 // to get the Text with the anonymous <div>. 606 if (MOZ_UNLIKELY(!mInitSucceeded)) { 607 // If we're temporarily destroyed while we're handling something, we can get 608 // the value from the cached text node which was maybe modified by us. This 609 // helps TextControlState and TextInputListener to notify the text control 610 // element of value changes, etc, at UnbindFromFrame(). 611 const Text* const cachedTextNode = GetCachedTextNode(); 612 return NS_WARN_IF(!cachedTextNode) || !cachedTextNode->TextDataLength(); 613 } 614 const Text* const textNode = GetTextNode(); 615 return !textNode || !textNode->TextDataLength(); 616 } 617 618 NS_IMETHODIMP TextEditor::GetTextLength(uint32_t* aCount) { 619 MOZ_ASSERT(aCount); 620 // This is a public method so that anytime this may be called, e.g., before 621 // initialized or after destroyed. However, GetTextNode() is an internal API 622 // for friend classes and internal use. Therefore, it asserts the conditions 623 // whether the anonymous subtree is available. Therefore, this method needs 624 // to get the Text with the anonymous <div>. 625 if (MOZ_UNLIKELY(!mInitSucceeded)) { 626 // If we're temporarily destroyed while we're handling something, we can get 627 // the value from the cached text node which was maybe modified by us. 628 const Text* const textNode = GetCachedTextNode(); 629 if (NS_WARN_IF(!textNode)) { 630 return NS_ERROR_FAILURE; 631 } 632 *aCount = textNode->TextDataLength(); 633 return NS_OK; 634 } 635 636 const Text* const textNode = GetTextNode(); 637 *aCount = textNode ? textNode->TextDataLength() : 0u; 638 return NS_OK; 639 } 640 641 bool TextEditor::IsCopyToClipboardAllowedInternal() const { 642 MOZ_ASSERT(IsEditActionDataAvailable()); 643 if (!EditorBase::IsCopyToClipboardAllowedInternal()) { 644 return false; 645 } 646 647 if (!IsSingleLineEditor() || !IsPasswordEditor() || 648 NS_WARN_IF(!mPasswordMaskData)) { 649 return true; 650 } 651 652 // If we're a password editor, we should allow selected text to be copied 653 // to the clipboard only when selection range is in unmasked range. 654 if (IsAllMasked() || IsMaskingPassword() || !UnmaskedLength()) { 655 return false; 656 } 657 658 // If there are 2 or more ranges, we don't allow to copy/cut for now since 659 // we need to check whether all ranges are in unmasked range or not. 660 // Anyway, such operation in password field does not make sense. 661 if (SelectionRef().RangeCount() > 1) { 662 return false; 663 } 664 665 uint32_t selectionStart = 0, selectionEnd = 0; 666 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), mRootElement, 667 selectionStart, selectionEnd); 668 return UnmaskedStart() <= selectionStart && UnmaskedEnd() >= selectionEnd; 669 } 670 671 nsresult TextEditor::HandlePasteAsQuotation( 672 AutoEditActionDataSetter& aEditActionData, 673 nsIClipboard::ClipboardType aClipboardType, DataTransfer* aDataTransfer) { 674 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard || 675 aClipboardType == nsIClipboard::kSelectionClipboard); 676 if (NS_WARN_IF(!GetDocument())) { 677 return NS_OK; 678 } 679 680 // XXX Why don't we dispatch ePaste event here? 681 682 // Get the nsITransferable interface for getting the data from the clipboard 683 Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable = 684 EditorUtils::CreateTransferableForPlainText(*GetDocument()); 685 if (maybeTransferable.isErr()) { 686 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed"); 687 return maybeTransferable.unwrapErr(); 688 } 689 nsCOMPtr<nsITransferable> trans(maybeTransferable.unwrap()); 690 if (!trans) { 691 NS_WARNING( 692 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but " 693 "ignored"); 694 return NS_OK; 695 } 696 697 // Get the Data from the clipboard 698 nsresult rv = 699 GetDataFromDataTransferOrClipboard(aDataTransfer, trans, aClipboardType); 700 701 // Now we ask the transferable for the data 702 // it still owns the data, we just have a pointer to it. 703 // If it can't support a "text" output of the data the call will fail 704 nsCOMPtr<nsISupports> genericDataObj; 705 nsAutoCString flavor; 706 rv = trans->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj)); 707 if (NS_FAILED(rv)) { 708 NS_WARNING("nsITransferable::GetAnyTransferData() failed"); 709 return rv; 710 } 711 712 if (!flavor.EqualsLiteral(kTextMime) && 713 !flavor.EqualsLiteral(kMozTextInternal)) { 714 return NS_OK; 715 } 716 717 nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj); 718 if (!text) { 719 return NS_OK; 720 } 721 722 nsString stuffToPaste; 723 DebugOnly<nsresult> rvIgnored = text->GetData(stuffToPaste); 724 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 725 "nsISupportsString::GetData() failed, but ignored"); 726 if (stuffToPaste.IsEmpty()) { 727 return NS_OK; 728 } 729 730 aEditActionData.SetData(stuffToPaste); 731 if (!stuffToPaste.IsEmpty()) { 732 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste); 733 } 734 rv = aEditActionData.MaybeDispatchBeforeInputEvent(); 735 if (NS_FAILED(rv)) { 736 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 737 "MaybeDispatchBeforeInputEvent() failed"); 738 return rv; 739 } 740 741 AutoPlaceholderBatch treatAsOneTransaction( 742 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 743 rv = InsertWithQuotationsAsSubAction(stuffToPaste); 744 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 745 "TextEditor::InsertWithQuotationsAsSubAction() failed"); 746 return rv; 747 } 748 749 nsresult TextEditor::InsertWithQuotationsAsSubAction( 750 const nsAString& aQuotedText) { 751 MOZ_ASSERT(IsEditActionDataAvailable()); 752 753 if (IsReadonly()) { 754 return NS_OK; 755 } 756 757 // Let the citer quote it for us: 758 nsString quotedStuff; 759 InternetCiter::GetCiteString(aQuotedText, quotedStuff); 760 761 // It's best to put a blank line after the quoted text so that mails 762 // written without thinking won't be so ugly. 763 if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) { 764 quotedStuff.Append(char16_t('\n')); 765 } 766 767 IgnoredErrorResult ignoredError; 768 AutoEditSubActionNotifier startToHandleEditSubAction( 769 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); 770 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 771 return ignoredError.StealNSResult(); 772 } 773 NS_WARNING_ASSERTION( 774 !ignoredError.Failed(), 775 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 776 777 // XXX Do we need to support paste-as-quotation in password editor (and 778 // also in single line editor)? 779 MaybeDoAutoPasswordMasking(); 780 781 nsresult rv = InsertTextAsSubAction(quotedStuff, InsertTextFor::NormalText); 782 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 783 "EditorBase::InsertTextAsSubAction() failed"); 784 return rv; 785 } 786 787 nsresult TextEditor::SelectEntireDocument() { 788 MOZ_ASSERT(IsEditActionDataAvailable()); 789 790 if (NS_WARN_IF(!mInitSucceeded)) { 791 return NS_ERROR_NOT_INITIALIZED; 792 } 793 794 RefPtr<Element> anonymousDivElement = GetRoot(); 795 if (NS_WARN_IF(!anonymousDivElement)) { 796 return NS_ERROR_NOT_INITIALIZED; 797 } 798 799 RefPtr<Text> text = 800 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild()); 801 MOZ_ASSERT(text); 802 803 MOZ_TRY(SelectionRef().SetStartAndEndInLimiter( 804 *text, 0, *text, text->TextDataLength(), eDirNext, 805 nsISelectionListener::SELECTALL_REASON)); 806 807 return NS_OK; 808 } 809 810 EventTarget* TextEditor::GetDOMEventTarget() const { return mEventTarget; } 811 812 void TextEditor::ReinitializeSelection(Element& aElement) { 813 MOZ_LOG(gTextEditorLog, LogLevel::Info, 814 ("%p: ReinitializeSelection(aElement=%s)", this, 815 ToString(RefPtr{&aElement}).c_str())); 816 817 if (MOZ_UNLIKELY(Destroyed())) { 818 LogOrWarn(this, gTextEditorLog, LogLevel::Error, "Destroyed() failed"); 819 return; 820 } 821 822 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 823 if (MOZ_UNLIKELY(!editActionData.CanHandle())) { 824 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 825 "AutoEditActionDataSetter::CanHandle() failed"); 826 return; 827 } 828 829 // We don't need to flush pending notifications here and we don't need to 830 // handle spellcheck at first focus. Therefore, we don't need to call 831 // `TextEditor::OnFocus` here. 832 EditorBase::OnFocus(aElement); 833 834 // If previous focused editor turn on spellcheck and this editor doesn't 835 // turn on it, spellcheck state is mismatched. So we need to re-sync it. 836 SyncRealTimeSpell(); 837 } 838 839 nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) { 840 MOZ_LOG(gTextEditorLog, LogLevel::Info, 841 ("%p: OnFocus(aOriginalEventTargetNode=%s)", this, 842 ToString(RefPtr{&aOriginalEventTargetNode}).c_str())); 843 844 RefPtr<PresShell> presShell = GetPresShell(); 845 if (MOZ_UNLIKELY(!presShell)) { 846 LogOrWarn(this, gTextEditorLog, LogLevel::Error, "!presShell"); 847 return NS_ERROR_FAILURE; 848 } 849 // Let's update the layout information right now because there are some 850 // pending notifications and flushing them may cause destroying the editor. 851 presShell->FlushPendingNotifications(FlushType::Layout); 852 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) { 853 MOZ_LOG(gTextEditorLog, LogLevel::Debug, 854 ("%p: CanKeepHandlingFocusEvent() returned false", this)); 855 return NS_OK; 856 } 857 858 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 859 if (MOZ_UNLIKELY(!editActionData.CanHandle())) { 860 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 861 "AutoEditActionDataSetter::CanHandle() failed"); 862 return NS_ERROR_FAILURE; 863 } 864 865 // Spell check a textarea the first time that it is focused. 866 nsresult rv = FlushPendingSpellCheck(); 867 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 868 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 869 "EditorBase::FlushPendingSpellCheck() failed"); 870 return NS_ERROR_EDITOR_DESTROYED; 871 } 872 NS_WARNING_ASSERTION( 873 NS_SUCCEEDED(rv), 874 "EditorBase::FlushPendingSpellCheck() failed, but ignored"); 875 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) { 876 MOZ_LOG(gTextEditorLog, LogLevel::Debug, 877 ("%p: CanKeepHandlingFocusEvent() returned false after " 878 "FlushPendingSpellCheck()", 879 this)); 880 return NS_OK; 881 } 882 883 return EditorBase::OnFocus(aOriginalEventTargetNode); 884 } 885 886 nsresult TextEditor::OnBlur(const EventTarget* aEventTarget) { 887 MOZ_LOG(gTextEditorLog, LogLevel::Info, 888 ("%p: OnBlur(aEventTarget=%s)", this, 889 ToString(RefPtr{aEventTarget}).c_str())); 890 891 // check if something else is focused. If another element is focused, then 892 // we should not change the selection. If another element already has focus, 893 // we should not maintain the selection because we may not have the rights 894 // doing it. 895 if ([[maybe_unused]] Element* const focusedElement = 896 nsFocusManager::GetFocusedElementStatic()) { 897 MOZ_LOG(gTextEditorLog, LogLevel::Info, 898 ("%p: OnBlur() is ignored because another element already has " 899 "focus (%s)", 900 this, ToString(RefPtr{focusedElement}).c_str())); 901 return NS_OK; 902 } 903 904 nsresult rv = FinalizeSelection(); 905 if (NS_FAILED(rv)) { 906 LogOrWarn(this, gTextEditorLog, LogLevel::Error, 907 "EditorBase::FinalizeSelection() failed"); 908 return rv; 909 } 910 return NS_OK; 911 } 912 913 nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement, 914 nsAtom* aAttribute, 915 const nsAString& aValue, 916 bool aSuppressTransaction) { 917 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) { 918 return NS_ERROR_INVALID_ARG; 919 } 920 921 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute); 922 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 923 if (NS_FAILED(rv)) { 924 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 925 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 926 return EditorBase::ToGenericNSResult(rv); 927 } 928 929 rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue); 930 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 931 "EditorBase::SetAttributeWithTransaction() failed"); 932 return EditorBase::ToGenericNSResult(rv); 933 } 934 935 nsresult TextEditor::RemoveAttributeOrEquivalent(Element* aElement, 936 nsAtom* aAttribute, 937 bool aSuppressTransaction) { 938 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) { 939 return NS_ERROR_INVALID_ARG; 940 } 941 942 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute); 943 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 944 if (NS_FAILED(rv)) { 945 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 946 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 947 return EditorBase::ToGenericNSResult(rv); 948 } 949 950 rv = RemoveAttributeWithTransaction(*aElement, *aAttribute); 951 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 952 "EditorBase::RemoveAttributeWithTransaction() failed"); 953 return EditorBase::ToGenericNSResult(rv); 954 } 955 956 template <typename EditorDOMPointType> 957 EditorDOMPointType TextEditor::FindBetterInsertionPoint( 958 const EditorDOMPointType& aPoint) const { 959 if (MOZ_UNLIKELY(NS_WARN_IF(!aPoint.IsInContentNode()))) { 960 return aPoint; 961 } 962 963 MOZ_ASSERT(aPoint.IsSetAndValid()); 964 965 Element* const anonymousDivElement = GetRoot(); 966 if (aPoint.GetContainer() == anonymousDivElement) { 967 // In some cases, aPoint points start of the anonymous <div>. To avoid 968 // injecting unneeded text nodes, we first look to see if we have one 969 // available. In that case, we'll just adjust node and offset accordingly. 970 if (aPoint.IsStartOfContainer()) { 971 if (aPoint.GetContainer()->HasChildren() && 972 aPoint.GetContainer()->GetFirstChild()->IsText()) { 973 return EditorDOMPointType(aPoint.GetContainer()->GetFirstChild(), 0u); 974 } 975 } 976 // In some other cases, aPoint points the terminating padding <br> element 977 // for empty last line in the anonymous <div>. In that case, we'll adjust 978 // aInOutNode and aInOutOffset to the preceding text node, if any. 979 else { 980 nsIContent* child = aPoint.GetContainer()->GetLastChild(); 981 while (child) { 982 if (child->IsText()) { 983 return EditorDOMPointType::AtEndOf(*child); 984 } 985 child = child->GetPreviousSibling(); 986 } 987 } 988 } 989 990 // Sometimes, aPoint points the padding <br> element. In that case, we'll 991 // adjust the insertion point to the previous text node, if one exists, or to 992 // the parent anonymous DIV. 993 if (EditorUtils::IsPaddingBRElementForEmptyLastLine( 994 *aPoint.template ContainerAs<nsIContent>()) && 995 aPoint.IsStartOfContainer()) { 996 nsIContent* previousSibling = aPoint.GetContainer()->GetPreviousSibling(); 997 if (previousSibling && previousSibling->IsText()) { 998 return EditorDOMPointType::AtEndOf(*previousSibling); 999 } 1000 1001 nsINode* parentOfContainer = aPoint.GetContainerParent(); 1002 if (parentOfContainer && parentOfContainer == anonymousDivElement) { 1003 return EditorDOMPointType(parentOfContainer, 1004 aPoint.template ContainerAs<nsIContent>(), 0u); 1005 } 1006 } 1007 1008 return aPoint; 1009 } 1010 1011 // static 1012 void TextEditor::MaskString(nsString& aString, const Text& aTextNode, 1013 uint32_t aStartOffsetInString, 1014 uint32_t aStartOffsetInText) { 1015 MOZ_ASSERT(aTextNode.HasFlag(NS_MAYBE_MASKED)); 1016 MOZ_ASSERT(aStartOffsetInString == 0 || aStartOffsetInText == 0); 1017 1018 uint32_t unmaskStart = UINT32_MAX, unmaskLength = 0; 1019 const TextEditor* const textEditor = 1020 nsContentUtils::GetExtantTextEditorFromAnonymousNode(&aTextNode); 1021 if (textEditor && textEditor->UnmaskedLength() > 0) { 1022 unmaskStart = textEditor->UnmaskedStart(); 1023 unmaskLength = textEditor->UnmaskedLength(); 1024 // If text is copied from after unmasked range, we can treat this case 1025 // as mask all. 1026 if (aStartOffsetInText >= unmaskStart + unmaskLength) { 1027 unmaskLength = 0; 1028 unmaskStart = UINT32_MAX; 1029 } else { 1030 // If text is copied from middle of unmasked range, reduce the length 1031 // and adjust start offset. 1032 if (aStartOffsetInText > unmaskStart) { 1033 unmaskLength = unmaskStart + unmaskLength - aStartOffsetInText; 1034 unmaskStart = 0; 1035 } 1036 // If text is copied from before start of unmasked range, just adjust 1037 // the start offset. 1038 else { 1039 unmaskStart -= aStartOffsetInText; 1040 } 1041 // Make the range is in the string. 1042 unmaskStart += aStartOffsetInString; 1043 } 1044 } 1045 1046 const char16_t kPasswordMask = TextEditor::PasswordMask(); 1047 for (uint32_t i = aStartOffsetInString; i < aString.Length(); ++i) { 1048 bool isSurrogatePair = NS_IS_HIGH_SURROGATE(aString.CharAt(i)) && 1049 i < aString.Length() - 1 && 1050 NS_IS_LOW_SURROGATE(aString.CharAt(i + 1)); 1051 if (i < unmaskStart || i >= unmaskStart + unmaskLength) { 1052 if (isSurrogatePair) { 1053 aString.SetCharAt(kPasswordMask, i); 1054 aString.SetCharAt(kPasswordMask, i + 1); 1055 } else { 1056 aString.SetCharAt(kPasswordMask, i); 1057 } 1058 } 1059 1060 // Skip the following low surrogate. 1061 if (isSurrogatePair) { 1062 ++i; 1063 } 1064 } 1065 } 1066 1067 nsresult TextEditor::SetUnmaskRangeInternal(uint32_t aStart, uint32_t aLength, 1068 uint32_t aTimeout, bool aNotify, 1069 bool aForceStartMasking) { 1070 if (mPasswordMaskData) { 1071 mPasswordMaskData->mIsMaskingPassword = aForceStartMasking || aTimeout != 0; 1072 1073 // We cannot manage multiple unmasked ranges so that shrink the previous 1074 // range first. 1075 if (!IsAllMasked()) { 1076 mPasswordMaskData->mUnmaskedLength = 0; 1077 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No); 1078 } 1079 } 1080 1081 // If we're not a password editor, return error since this call does not 1082 // make sense. 1083 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) { 1084 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes); 1085 return NS_ERROR_NOT_AVAILABLE; 1086 } 1087 1088 if (NS_WARN_IF(!GetRoot())) { 1089 return NS_ERROR_NOT_INITIALIZED; 1090 } 1091 Text* const text = GetTextNode(); 1092 if (!text || !text->Length()) { 1093 // There is no anonymous text node in the editor. 1094 return aStart > 0 && aStart != UINT32_MAX ? NS_ERROR_INVALID_ARG : NS_OK; 1095 } 1096 1097 if (aStart < UINT32_MAX) { 1098 uint32_t valueLength = text->Length(); 1099 if (aStart >= valueLength) { 1100 return NS_ERROR_INVALID_ARG; // There is no character can be masked. 1101 } 1102 // If aStart is middle of a surrogate pair, expand it to include the 1103 // preceding high surrogate because the caller may want to show a 1104 // character before the character at `aStart + 1`. 1105 const CharacterDataBuffer& characterDataBuffer = text->DataBuffer(); 1106 if (characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(aStart)) { 1107 mPasswordMaskData->mUnmaskedStart = aStart - 1; 1108 // If caller collapses the range, keep it. Otherwise, expand the length. 1109 if (aLength > 0) { 1110 ++aLength; 1111 } 1112 } else { 1113 mPasswordMaskData->mUnmaskedStart = aStart; 1114 } 1115 mPasswordMaskData->mUnmaskedLength = 1116 std::min(valueLength - UnmaskedStart(), aLength); 1117 // If unmasked end is middle of a surrogate pair, expand it to include 1118 // the following low surrogate because the caller may want to show a 1119 // character after the character at `aStart + aLength`. 1120 if (UnmaskedEnd() < valueLength && 1121 characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt( 1122 UnmaskedEnd())) { 1123 mPasswordMaskData->mUnmaskedLength++; 1124 } 1125 // If it's first time to mask the unmasking characters with timer, create 1126 // the timer now. Then, we'll keep using it for saving the creation cost. 1127 if (!HasAutoMaskingTimer() && aLength && aTimeout && UnmaskedLength()) { 1128 mPasswordMaskData->mTimer = NS_NewTimer(); 1129 } 1130 } else { 1131 if (NS_WARN_IF(aLength != 0)) { 1132 return NS_ERROR_INVALID_ARG; 1133 } 1134 mPasswordMaskData->MaskAll(); 1135 } 1136 1137 // Notify nsTextFrame of this update if the caller wants this to do it. 1138 // Only in this case, script may run. 1139 if (aNotify) { 1140 MOZ_ASSERT(IsEditActionDataAvailable()); 1141 1142 RefPtr<Document> document = GetDocument(); 1143 if (NS_WARN_IF(!document)) { 1144 return NS_ERROR_NOT_INITIALIZED; 1145 } 1146 // Notify nsTextFrame of masking range change. 1147 if (RefPtr<PresShell> presShell = document->GetObservingPresShell()) { 1148 nsAutoScriptBlocker blockRunningScript; 1149 uint32_t valueLength = text->Length(); 1150 CharacterDataChangeInfo changeInfo = {false, 0, valueLength, valueLength}; 1151 presShell->CharacterDataChanged(text, changeInfo); 1152 } 1153 1154 // Scroll caret into the view since masking or unmasking character may 1155 // move caret to outside of the view. 1156 nsresult rv = ScrollSelectionFocusIntoView(); 1157 if (NS_FAILED(rv)) { 1158 NS_WARNING("EditorBase::ScrollSelectionFocusIntoView() failed"); 1159 return rv; 1160 } 1161 } 1162 1163 if (!IsAllMasked() && aTimeout != 0) { 1164 // Initialize the timer to mask the range automatically. 1165 MOZ_ASSERT(HasAutoMaskingTimer()); 1166 DebugOnly<nsresult> rvIgnored = mPasswordMaskData->mTimer->InitWithCallback( 1167 this, aTimeout, nsITimer::TYPE_ONE_SHOT); 1168 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1169 "nsITimer::InitWithCallback() failed, but ignored"); 1170 } 1171 1172 return NS_OK; 1173 } 1174 1175 // static 1176 char16_t TextEditor::PasswordMask() { 1177 char16_t ret = LookAndFeel::GetPasswordCharacter(); 1178 if (!ret) { 1179 ret = '*'; 1180 } 1181 return ret; 1182 } 1183 1184 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TextEditor::Notify(nsITimer* aTimer) { 1185 // Check whether our text editor's password flag was changed before this 1186 // "hide password character" timer actually fires. 1187 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) { 1188 return NS_OK; 1189 } 1190 1191 if (IsAllMasked()) { 1192 return NS_OK; 1193 } 1194 1195 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword); 1196 if (NS_WARN_IF(!editActionData.CanHandle())) { 1197 return NS_ERROR_NOT_INITIALIZED; 1198 } 1199 1200 // Mask all characters. 1201 nsresult rv = MaskAllCharactersAndNotify(); 1202 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1203 "TextEditor::MaskAllCharactersAndNotify() failed"); 1204 1205 if (StaticPrefs::editor_password_testing_mask_delay()) { 1206 if (RefPtr<Element> target = GetInputEventTargetElement()) { 1207 RefPtr<Document> document = target->OwnerDoc(); 1208 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchTrustedEvent( 1209 document, target, u"MozLastInputMasked"_ns, CanBubble::eYes, 1210 Cancelable::eNo); 1211 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1212 "nsContentUtils::DispatchTrustedEvent(" 1213 "MozLastInputMasked) failed, but ignored"); 1214 } 1215 } 1216 1217 return EditorBase::ToGenericNSResult(rv); 1218 } 1219 1220 NS_IMETHODIMP TextEditor::GetName(nsACString& aName) { 1221 aName.AssignLiteral("TextEditor"); 1222 return NS_OK; 1223 } 1224 1225 void TextEditor::WillDeleteText(uint32_t aCurrentLength, 1226 uint32_t aRemoveStartOffset, 1227 uint32_t aRemoveLength) { 1228 MOZ_ASSERT(IsEditActionDataAvailable()); 1229 1230 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) { 1231 return; 1232 } 1233 1234 // Adjust unmasked range before deletion since DOM mutation may cause 1235 // layout referring the range in old text. 1236 1237 // If we need to mask automatically, mask all now. 1238 if (IsMaskingPassword()) { 1239 DebugOnly<nsresult> rvIgnored = MaskAllCharacters(); 1240 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1241 "TextEditor::MaskAllCharacters() failed, but ignored"); 1242 return; 1243 } 1244 1245 if (aRemoveStartOffset < UnmaskedStart()) { 1246 // If removing range is before the unmasked range, move it. 1247 if (aRemoveStartOffset + aRemoveLength <= UnmaskedStart()) { 1248 DebugOnly<nsresult> rvIgnored = 1249 SetUnmaskRange(UnmaskedStart() - aRemoveLength, UnmaskedLength()); 1250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1251 "TextEditor::SetUnmaskRange() failed, but ignored"); 1252 return; 1253 } 1254 1255 // If removing range starts before unmasked range, and ends in unmasked 1256 // range, move and shrink the range. 1257 if (aRemoveStartOffset + aRemoveLength < UnmaskedEnd()) { 1258 uint32_t unmaskedLengthInRemovingRange = 1259 aRemoveStartOffset + aRemoveLength - UnmaskedStart(); 1260 DebugOnly<nsresult> rvIgnored = SetUnmaskRange( 1261 aRemoveStartOffset, UnmaskedLength() - unmaskedLengthInRemovingRange); 1262 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1263 "TextEditor::SetUnmaskRange() failed, but ignored"); 1264 return; 1265 } 1266 1267 // If removing range includes all unmasked range, collapse it to the 1268 // remove offset. 1269 DebugOnly<nsresult> rvIgnored = SetUnmaskRange(aRemoveStartOffset, 0); 1270 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1271 "TextEditor::SetUnmaskRange() failed, but ignored"); 1272 return; 1273 } 1274 1275 if (aRemoveStartOffset < UnmaskedEnd()) { 1276 // If removing range is in unmasked range, shrink the range. 1277 if (aRemoveStartOffset + aRemoveLength <= UnmaskedEnd()) { 1278 DebugOnly<nsresult> rvIgnored = 1279 SetUnmaskRange(UnmaskedStart(), UnmaskedLength() - aRemoveLength); 1280 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1281 "TextEditor::SetUnmaskRange() failed, but ignored"); 1282 return; 1283 } 1284 1285 // If removing range starts from unmasked range, and ends after it, 1286 // shrink it. 1287 DebugOnly<nsresult> rvIgnored = 1288 SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset - UnmaskedStart()); 1289 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1290 "TextEditor::SetUnmaskRange() failed, but ignored"); 1291 return; 1292 } 1293 1294 // If removing range is after the unmasked range, keep it. 1295 } 1296 1297 nsresult TextEditor::DidInsertText(uint32_t aNewLength, 1298 uint32_t aInsertedOffset, 1299 uint32_t aInsertedLength) { 1300 MOZ_ASSERT(IsEditActionDataAvailable()); 1301 1302 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) { 1303 return NS_OK; 1304 } 1305 1306 if (IsMaskingPassword()) { 1307 // If we need to mask password, mask all right now. 1308 nsresult rv = MaskAllCharactersAndNotify(); 1309 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1310 "TextEditor::MaskAllCharacters() failed"); 1311 return rv; 1312 } 1313 1314 if (aInsertedOffset < UnmaskedStart()) { 1315 // If insertion point is before unmasked range, expand the unmasked range 1316 // to include the new text. 1317 nsresult rv = SetUnmaskRangeAndNotify( 1318 aInsertedOffset, UnmaskedEnd() + aInsertedLength - aInsertedOffset); 1319 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1320 "TextEditor::SetUnmaskRangeAndNotify() failed"); 1321 return rv; 1322 } 1323 1324 if (aInsertedOffset <= UnmaskedEnd()) { 1325 // If insertion point is in unmasked range, unmask new text. 1326 nsresult rv = SetUnmaskRangeAndNotify(UnmaskedStart(), 1327 UnmaskedLength() + aInsertedLength); 1328 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1329 "TextEditor::SetUnmaskRangeAndNotify() failed"); 1330 return rv; 1331 } 1332 1333 // If insertion point is after unmasked range, extend the unmask range to 1334 // include the new text. 1335 nsresult rv = SetUnmaskRangeAndNotify( 1336 UnmaskedStart(), aInsertedOffset + aInsertedLength - UnmaskedStart()); 1337 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1338 "TextEditor::SetUnmaskRangeAndNotify() failed"); 1339 return rv; 1340 } 1341 1342 } // namespace mozilla