Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / TextEditor.cpp
blob9f6fb86eb3e36f62d619ad5b1391bc0f9fb4eea3
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "TextEditor.h"
8 #include <algorithm>
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"
19 #include "mozilla/dom/DocumentInlines.h"
20 #include "mozilla/Assertions.h"
21 #include "mozilla/ContentIterator.h"
22 #include "mozilla/IMEStateManager.h"
23 #include "mozilla/LookAndFeel.h"
24 #include "mozilla/mozalloc.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/PresShell.h"
27 #include "mozilla/StaticPrefs_dom.h"
28 #include "mozilla/StaticPrefs_editor.h"
29 #include "mozilla/TextComposition.h"
30 #include "mozilla/TextEvents.h"
31 #include "mozilla/TextServicesDocument.h"
32 #include "mozilla/Try.h"
33 #include "mozilla/dom/Event.h"
34 #include "mozilla/dom/Element.h"
35 #include "mozilla/dom/Selection.h"
36 #include "mozilla/dom/StaticRange.h"
38 #include "nsAString.h"
39 #include "nsCRT.h"
40 #include "nsCaret.h"
41 #include "nsCharTraits.h"
42 #include "nsComponentManagerUtils.h"
43 #include "nsContentList.h"
44 #include "nsDebug.h"
45 #include "nsDependentSubstring.h"
46 #include "nsError.h"
47 #include "nsFocusManager.h"
48 #include "nsGkAtoms.h"
49 #include "nsIClipboard.h"
50 #include "nsIContent.h"
51 #include "nsINode.h"
52 #include "nsIPrincipal.h"
53 #include "nsISelectionController.h"
54 #include "nsISupportsPrimitives.h"
55 #include "nsITransferable.h"
56 #include "nsIWeakReferenceUtils.h"
57 #include "nsNameSpaceManager.h"
58 #include "nsLiteralString.h"
59 #include "nsPresContext.h"
60 #include "nsReadableUtils.h"
61 #include "nsServiceManagerUtils.h"
62 #include "nsString.h"
63 #include "nsStringFwd.h"
64 #include "nsTextFragment.h"
65 #include "nsTextNode.h"
66 #include "nsUnicharUtils.h"
67 #include "nsXPCOM.h"
69 class nsIOutputStream;
70 class nsISupports;
72 namespace mozilla {
74 using namespace dom;
76 using LeafNodeType = HTMLEditUtils::LeafNodeType;
77 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
79 template EditorDOMPoint TextEditor::FindBetterInsertionPoint(
80 const EditorDOMPoint& aPoint) const;
81 template EditorRawDOMPoint TextEditor::FindBetterInsertionPoint(
82 const EditorRawDOMPoint& aPoint) const;
84 TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text) {
85 // printf("Size of TextEditor: %zu\n", sizeof(TextEditor));
86 static_assert(
87 sizeof(TextEditor) <= 512,
88 "TextEditor instance should be allocatable in the quantum class bins");
91 TextEditor::~TextEditor() {
92 // Remove event listeners. Note that if we had an HTML editor,
93 // it installed its own instead of these
94 RemoveEventListeners();
97 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
99 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase)
100 if (tmp->mPasswordMaskData) {
101 tmp->mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
102 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPasswordMaskData->mTimer)
104 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase)
107 if (tmp->mPasswordMaskData) {
108 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPasswordMaskData->mTimer)
110 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
112 NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase)
113 NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase)
115 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor)
116 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
117 NS_INTERFACE_MAP_ENTRY(nsINamed)
118 NS_INTERFACE_MAP_END_INHERITING(EditorBase)
120 NS_IMETHODIMP TextEditor::EndOfDocument() {
121 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
122 if (NS_WARN_IF(!editActionData.CanHandle())) {
123 return NS_ERROR_NOT_INITIALIZED;
125 nsresult rv = CollapseSelectionToEndOfTextNode();
126 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
127 "TextEditor::CollapseSelectionToEndOfTextNode() failed");
128 // This is low level API for embedders and chrome script so that we can return
129 // raw error code here.
130 return rv;
133 nsresult TextEditor::CollapseSelectionToEndOfTextNode() {
134 MOZ_ASSERT(IsEditActionDataAvailable());
136 Element* anonymousDivElement = GetRoot();
137 if (NS_WARN_IF(!anonymousDivElement)) {
138 return NS_ERROR_NULL_POINTER;
141 RefPtr<Text> textNode =
142 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
143 MOZ_ASSERT(textNode);
144 nsresult rv = CollapseSelectionToEndOf(*textNode);
145 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
146 "EditorBase::CollapseSelectionToEndOf() failed");
147 return rv;
150 nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement,
151 nsISelectionController& aSelectionController,
152 uint32_t aFlags,
153 UniquePtr<PasswordMaskData>&& aPasswordMaskData) {
154 MOZ_ASSERT(!mInitSucceeded,
155 "TextEditor::Init() called again without calling PreDestroy()?");
156 MOZ_ASSERT(!(aFlags & nsIEditor::eEditorPasswordMask) == !aPasswordMaskData);
157 mPasswordMaskData = std::move(aPasswordMaskData);
159 // Init the base editor
160 nsresult rv = InitInternal(aDocument, &aAnonymousDivElement,
161 aSelectionController, aFlags);
162 if (NS_FAILED(rv)) {
163 NS_WARNING("EditorBase::InitInternal() failed");
164 return rv;
167 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
168 if (NS_WARN_IF(!editActionData.CanHandle())) {
169 return NS_ERROR_FAILURE;
172 // We set mInitSucceeded here rather than at the end of the function,
173 // since InitEditorContentAndSelection() can perform some transactions
174 // and can warn if mInitSucceeded is still false.
175 MOZ_ASSERT(!mInitSucceeded, "TextEditor::Init() shouldn't be nested");
176 mInitSucceeded = true;
178 rv = InitEditorContentAndSelection();
179 if (NS_FAILED(rv)) {
180 NS_WARNING("TextEditor::InitEditorContentAndSelection() failed");
181 // XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
182 // is a public method?
183 mInitSucceeded = false;
184 return EditorBase::ToGenericNSResult(rv);
187 // Throw away the old transaction manager if this is not the first time that
188 // we're initializing the editor.
189 ClearUndoRedo();
190 EnableUndoRedo();
191 return NS_OK;
194 nsresult TextEditor::InitEditorContentAndSelection() {
195 MOZ_ASSERT(IsEditActionDataAvailable());
197 MOZ_TRY(EnsureEmptyTextFirstChild());
199 // If the selection hasn't been set up yet, set it up collapsed to the end of
200 // our editable content.
201 if (!SelectionRef().RangeCount()) {
202 nsresult rv = CollapseSelectionToEndOfTextNode();
203 if (NS_FAILED(rv)) {
204 NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed");
205 return rv;
209 if (!IsSingleLineEditor()) {
210 nsresult rv = EnsurePaddingBRElementInMultilineEditor();
211 if (NS_FAILED(rv)) {
212 NS_WARNING(
213 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
214 return rv;
218 return NS_OK;
221 nsresult TextEditor::PostCreate() {
222 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
223 if (NS_WARN_IF(!editActionData.CanHandle())) {
224 return NS_ERROR_NOT_INITIALIZED;
227 nsresult rv = PostCreateInternal();
229 // Restore unmasked range if there is.
230 if (IsPasswordEditor() && !IsAllMasked()) {
231 DebugOnly<nsresult> rvIgnored =
232 SetUnmaskRangeAndNotify(UnmaskedStart(), UnmaskedLength());
233 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
234 "TextEditor::SetUnmaskRangeAndNotify() failed to "
235 "restore unmasked range, but ignored");
237 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
238 "EditorBase::PostCreateInternal() failed");
239 return rv;
242 UniquePtr<PasswordMaskData> TextEditor::PreDestroy() {
243 if (mDidPreDestroy) {
244 return nullptr;
247 UniquePtr<PasswordMaskData> passwordMaskData = std::move(mPasswordMaskData);
248 if (passwordMaskData) {
249 // Disable auto-masking timer since nobody can catch the notification
250 // from the timer and canceling the unmasking.
251 passwordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes);
252 // Similary, keeping preventing echoing password temporarily across
253 // TextEditor instances is hard. So, we should forget it.
254 passwordMaskData->mEchoingPasswordPrevented = false;
257 PreDestroyInternal();
259 return passwordMaskData;
262 nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
263 // NOTE: When you change this method, you should also change:
264 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
265 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
267 // And also when you add new key handling, you need to change the subclass's
268 // HandleKeyPressEvent()'s switch statement.
270 if (NS_WARN_IF(!aKeyboardEvent)) {
271 return NS_ERROR_UNEXPECTED;
274 if (IsReadonly()) {
275 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent);
276 return NS_OK;
279 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
280 "HandleKeyPressEvent gets non-keypress event");
282 switch (aKeyboardEvent->mKeyCode) {
283 case NS_VK_META:
284 case NS_VK_WIN:
285 case NS_VK_SHIFT:
286 case NS_VK_CONTROL:
287 case NS_VK_ALT:
288 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress
289 // event.
290 aKeyboardEvent->PreventDefault();
291 return NS_OK;
293 case NS_VK_BACK:
294 case NS_VK_DELETE:
295 case NS_VK_TAB: {
296 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
297 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
298 "EditorBase::HandleKeyPressEvent() failed");
299 return rv;
301 case NS_VK_RETURN: {
302 if (!aKeyboardEvent->IsInputtingLineBreak()) {
303 return NS_OK;
305 if (!IsSingleLineEditor()) {
306 aKeyboardEvent->PreventDefault();
308 // We need to dispatch "beforeinput" event at least even if we're a
309 // single line text editor.
310 nsresult rv = InsertLineBreakAsAction();
311 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
312 "TextEditor::InsertLineBreakAsAction() failed");
313 return rv;
317 if (!aKeyboardEvent->IsInputtingText()) {
318 // we don't PreventDefault() here or keybindings like control-x won't work
319 return NS_OK;
321 aKeyboardEvent->PreventDefault();
322 // If we dispatch 2 keypress events for a surrogate pair and we set only
323 // first `.key` value to the surrogate pair, the preceding one has it and the
324 // other has empty string. In this case, we should handle only the first one
325 // with the key value.
326 if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() &&
327 !StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() &&
328 aKeyboardEvent->mKeyValue.IsEmpty() &&
329 IS_SURROGATE(aKeyboardEvent->mCharCode)) {
330 return NS_OK;
332 // Our widget shouldn't set `\r` to `mKeyValue`, but it may be synthesized
333 // keyboard event and its value may be `\r`. In such case, we should treat
334 // it as `\n` for the backward compatibility because we stopped converting
335 // `\r` and `\r\n` to `\n` at getting `HTMLInputElement.value` and
336 // `HTMLTextAreaElement.value` for the performance (i.e., we don't need to
337 // take care in `HTMLEditor`).
338 nsAutoString str(aKeyboardEvent->mKeyValue);
339 if (str.IsEmpty()) {
340 MOZ_ASSERT(aKeyboardEvent->mCharCode <= 0xFFFF,
341 "Non-BMP character needs special handling");
342 str.Assign(aKeyboardEvent->mCharCode == nsCRT::CR
343 ? static_cast<char16_t>(nsCRT::LF)
344 : static_cast<char16_t>(aKeyboardEvent->mCharCode));
345 } else {
346 MOZ_ASSERT(str.Find(u"\r\n"_ns) == kNotFound,
347 "This assumes that typed text does not include CRLF");
348 str.ReplaceChar('\r', '\n');
350 nsresult rv = OnInputText(str);
351 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed");
352 return rv;
355 NS_IMETHODIMP TextEditor::InsertLineBreak() {
356 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak);
357 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
358 if (NS_FAILED(rv)) {
359 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
360 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
361 return EditorBase::ToGenericNSResult(rv);
364 if (NS_WARN_IF(IsSingleLineEditor())) {
365 return NS_ERROR_FAILURE;
368 AutoPlaceholderBatch treatAsOneTransaction(
369 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
370 rv = InsertLineBreakAsSubAction();
371 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
372 "TextEditor::InsertLineBreakAsSubAction() failed");
373 return EditorBase::ToGenericNSResult(rv);
376 nsresult TextEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) {
377 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak,
378 aPrincipal);
379 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
380 if (NS_FAILED(rv)) {
381 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
382 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
383 return EditorBase::ToGenericNSResult(rv);
386 if (IsSingleLineEditor()) {
387 return NS_OK;
390 // XXX This may be called by execCommand() with "insertParagraph".
391 // In such case, naming the transaction "TypingTxnName" is odd.
392 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
393 ScrollSelectionIntoView::Yes,
394 __FUNCTION__);
395 rv = InsertLineBreakAsSubAction();
396 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
397 "EditorBase::InsertLineBreakAsSubAction() failed");
398 return EditorBase::ToGenericNSResult(rv);
401 nsresult TextEditor::SetTextAsAction(
402 const nsAString& aString,
403 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
404 nsIPrincipal* aPrincipal) {
405 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound);
407 AutoEditActionDataSetter editActionData(*this, EditAction::eSetText,
408 aPrincipal);
409 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) {
410 editActionData.MakeBeforeInputEventNonCancelable();
412 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
413 if (NS_FAILED(rv)) {
414 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
415 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
416 return EditorBase::ToGenericNSResult(rv);
419 AutoPlaceholderBatch treatAsOneTransaction(
420 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
421 rv = SetTextAsSubAction(aString);
422 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
423 "TextEditor::SetTextAsSubAction() failed");
424 return EditorBase::ToGenericNSResult(rv);
427 nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
428 MOZ_ASSERT(IsEditActionDataAvailable());
429 MOZ_ASSERT(mPlaceholderBatch);
431 if (NS_WARN_IF(!mInitSucceeded)) {
432 return NS_ERROR_NOT_INITIALIZED;
435 IgnoredErrorResult ignoredError;
436 AutoEditSubActionNotifier startToHandleEditSubAction(
437 *this, EditSubAction::eSetText, nsIEditor::eNext, ignoredError);
438 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
439 return ignoredError.StealNSResult();
441 NS_WARNING_ASSERTION(
442 !ignoredError.Failed(),
443 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
445 if (!IsIMEComposing() && !IsUndoRedoEnabled() &&
446 GetEditAction() != EditAction::eReplaceText && mMaxTextLength < 0) {
447 Result<EditActionResult, nsresult> result =
448 SetTextWithoutTransaction(aString);
449 if (MOZ_UNLIKELY(result.isErr())) {
450 NS_WARNING("TextEditor::SetTextWithoutTransaction() failed");
451 return result.unwrapErr();
453 if (!result.inspect().Ignored()) {
454 return NS_OK;
459 // Note that do not notify selectionchange caused by selecting all text
460 // because it's preparation of our delete implementation so web apps
461 // shouldn't receive such selectionchange before the first mutation.
462 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
464 // XXX We should make ReplaceSelectionAsSubAction() take range. Then,
465 // we can saving the expensive cost of modifying `Selection` here.
466 if (NS_SUCCEEDED(SelectEntireDocument())) {
467 DebugOnly<nsresult> rvIgnored = ReplaceSelectionAsSubAction(aString);
468 NS_WARNING_ASSERTION(
469 NS_SUCCEEDED(rvIgnored),
470 "EditorBase::ReplaceSelectionAsSubAction() failed, but ignored");
474 // Destroying AutoUpdateViewBatch may cause destroying us.
475 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
478 already_AddRefed<Element> TextEditor::GetInputEventTargetElement() const {
479 RefPtr<Element> target = Element::FromEventTargetOrNull(mEventTarget);
480 return target.forget();
483 bool TextEditor::IsEmpty() const {
484 // Even if there is no padding <br> element for empty editor, we should be
485 // detected as empty editor if all the children are text nodes and these
486 // have no content.
487 Element* anonymousDivElement = GetRoot();
488 if (!anonymousDivElement) {
489 return true; // Don't warn it, this is possible, e.g., 997805.html
492 MOZ_ASSERT(anonymousDivElement->GetFirstChild() &&
493 anonymousDivElement->GetFirstChild()->IsText());
495 // Only when there is non-empty text node, we are not empty.
496 return !anonymousDivElement->GetFirstChild()->Length();
499 NS_IMETHODIMP TextEditor::GetTextLength(uint32_t* aCount) {
500 MOZ_ASSERT(aCount);
502 // initialize out params
503 *aCount = 0;
505 // special-case for empty document, to account for the padding <br> element
506 // for empty editor.
507 // XXX This should be overridden by `HTMLEditor` and we should return the
508 // first text node's length from `TextEditor` instead. The following
509 // code is too expensive.
510 if (IsEmpty()) {
511 return NS_OK;
514 Element* rootElement = GetRoot();
515 if (NS_WARN_IF(!rootElement)) {
516 return NS_ERROR_FAILURE;
519 uint32_t totalLength = 0;
520 PostContentIterator postOrderIter;
521 DebugOnly<nsresult> rvIgnored = postOrderIter.Init(rootElement);
522 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
523 "PostContentIterator::Init() failed, but ignored");
524 EditorType editorType = GetEditorType();
525 for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
526 nsINode* currentNode = postOrderIter.GetCurrentNode();
527 if (currentNode && currentNode->IsText() &&
528 EditorUtils::IsEditableContent(*currentNode->AsText(), editorType)) {
529 totalLength += currentNode->Length();
533 *aCount = totalLength;
534 return NS_OK;
537 bool TextEditor::IsCopyToClipboardAllowedInternal() const {
538 MOZ_ASSERT(IsEditActionDataAvailable());
539 if (!EditorBase::IsCopyToClipboardAllowedInternal()) {
540 return false;
543 if (!IsSingleLineEditor() || !IsPasswordEditor() ||
544 NS_WARN_IF(!mPasswordMaskData)) {
545 return true;
548 // If we're a password editor, we should allow selected text to be copied
549 // to the clipboard only when selection range is in unmasked range.
550 if (IsAllMasked() || IsMaskingPassword() || !UnmaskedLength()) {
551 return false;
554 // If there are 2 or more ranges, we don't allow to copy/cut for now since
555 // we need to check whether all ranges are in unmasked range or not.
556 // Anyway, such operation in password field does not make sense.
557 if (SelectionRef().RangeCount() > 1) {
558 return false;
561 uint32_t selectionStart = 0, selectionEnd = 0;
562 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), mRootElement,
563 selectionStart, selectionEnd);
564 return UnmaskedStart() <= selectionStart && UnmaskedEnd() >= selectionEnd;
567 nsresult TextEditor::HandlePasteAsQuotation(
568 AutoEditActionDataSetter& aEditActionData, int32_t aClipboardType) {
569 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
570 aClipboardType == nsIClipboard::kSelectionClipboard);
571 if (NS_WARN_IF(!GetDocument())) {
572 return NS_OK;
575 // Get Clipboard Service
576 nsresult rv;
577 nsCOMPtr<nsIClipboard> clipboard =
578 do_GetService("@mozilla.org/widget/clipboard;1", &rv);
579 if (NS_FAILED(rv)) {
580 NS_WARNING("Failed to get nsIClipboard service");
581 return rv;
584 // XXX Why don't we dispatch ePaste event here?
586 // Get the nsITransferable interface for getting the data from the clipboard
587 Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable =
588 EditorUtils::CreateTransferableForPlainText(*GetDocument());
589 if (maybeTransferable.isErr()) {
590 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
591 return maybeTransferable.unwrapErr();
593 nsCOMPtr<nsITransferable> trans(maybeTransferable.unwrap());
594 if (!trans) {
595 NS_WARNING(
596 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
597 "ignored");
598 return NS_OK;
601 auto* windowContext = GetDocument()->GetWindowContext();
602 if (!windowContext) {
603 NS_WARNING("Editor didn't have document window context");
604 return NS_ERROR_FAILURE;
606 // Get the Data from the clipboard
607 rv = clipboard->GetData(trans, aClipboardType, windowContext);
609 // Now we ask the transferable for the data
610 // it still owns the data, we just have a pointer to it.
611 // If it can't support a "text" output of the data the call will fail
612 nsCOMPtr<nsISupports> genericDataObj;
613 nsAutoCString flavor;
614 rv = trans->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj));
615 if (NS_FAILED(rv)) {
616 NS_WARNING("nsITransferable::GetAnyTransferData() failed");
617 return rv;
620 if (!flavor.EqualsLiteral(kTextMime) &&
621 !flavor.EqualsLiteral(kMozTextInternal)) {
622 return NS_OK;
625 nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj);
626 if (!text) {
627 return NS_OK;
630 nsString stuffToPaste;
631 DebugOnly<nsresult> rvIgnored = text->GetData(stuffToPaste);
632 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
633 "nsISupportsString::GetData() failed, but ignored");
634 if (stuffToPaste.IsEmpty()) {
635 return NS_OK;
638 aEditActionData.SetData(stuffToPaste);
639 if (!stuffToPaste.IsEmpty()) {
640 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
642 rv = aEditActionData.MaybeDispatchBeforeInputEvent();
643 if (NS_FAILED(rv)) {
644 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
645 "MaybeDispatchBeforeInputEvent() failed");
646 return rv;
649 AutoPlaceholderBatch treatAsOneTransaction(
650 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
651 rv = InsertWithQuotationsAsSubAction(stuffToPaste);
652 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
653 "TextEditor::InsertWithQuotationsAsSubAction() failed");
654 return rv;
657 nsresult TextEditor::InsertWithQuotationsAsSubAction(
658 const nsAString& aQuotedText) {
659 MOZ_ASSERT(IsEditActionDataAvailable());
661 if (IsReadonly()) {
662 return NS_OK;
665 // Let the citer quote it for us:
666 nsString quotedStuff;
667 InternetCiter::GetCiteString(aQuotedText, quotedStuff);
669 // It's best to put a blank line after the quoted text so that mails
670 // written without thinking won't be so ugly.
671 if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) {
672 quotedStuff.Append(char16_t('\n'));
675 IgnoredErrorResult ignoredError;
676 AutoEditSubActionNotifier startToHandleEditSubAction(
677 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
678 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
679 return ignoredError.StealNSResult();
681 NS_WARNING_ASSERTION(
682 !ignoredError.Failed(),
683 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
685 // XXX Do we need to support paste-as-quotation in password editor (and
686 // also in single line editor)?
687 MaybeDoAutoPasswordMasking();
689 nsresult rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
690 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
691 "EditorBase::InsertTextAsSubAction() failed");
692 return rv;
695 nsresult TextEditor::SelectEntireDocument() {
696 MOZ_ASSERT(IsEditActionDataAvailable());
698 if (NS_WARN_IF(!mInitSucceeded)) {
699 return NS_ERROR_NOT_INITIALIZED;
702 RefPtr<Element> anonymousDivElement = GetRoot();
703 if (NS_WARN_IF(!anonymousDivElement)) {
704 return NS_ERROR_NOT_INITIALIZED;
707 RefPtr<Text> text =
708 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
709 MOZ_ASSERT(text);
711 MOZ_TRY(SelectionRef().SetStartAndEndInLimiter(
712 *text, 0, *text, text->TextDataLength(), eDirNext,
713 nsISelectionListener::SELECTALL_REASON));
715 return NS_OK;
718 EventTarget* TextEditor::GetDOMEventTarget() const { return mEventTarget; }
720 void TextEditor::ReinitializeSelection(Element& aElement) {
721 if (NS_WARN_IF(Destroyed())) {
722 return;
725 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
726 if (NS_WARN_IF(!editActionData.CanHandle())) {
727 return;
730 // We don't need to flush pending notifications here and we don't need to
731 // handle spellcheck at first focus. Therefore, we don't need to call
732 // `TextEditor::OnFocus` here.
733 EditorBase::OnFocus(aElement);
735 // If previous focused editor turn on spellcheck and this editor doesn't
736 // turn on it, spellcheck state is mismatched. So we need to re-sync it.
737 SyncRealTimeSpell();
740 nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
741 RefPtr<PresShell> presShell = GetPresShell();
742 if (NS_WARN_IF(!presShell)) {
743 return NS_ERROR_FAILURE;
745 // Let's update the layout information right now because there are some
746 // pending notifications and flushing them may cause destroying the editor.
747 presShell->FlushPendingNotifications(FlushType::Layout);
748 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
749 return NS_OK;
752 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
753 if (NS_WARN_IF(!editActionData.CanHandle())) {
754 return NS_ERROR_FAILURE;
757 // Spell check a textarea the first time that it is focused.
758 nsresult rv = FlushPendingSpellCheck();
759 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
760 NS_WARNING("EditorBase::FlushPendingSpellCheck() failed");
761 return NS_ERROR_EDITOR_DESTROYED;
763 NS_WARNING_ASSERTION(
764 NS_SUCCEEDED(rv),
765 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
766 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
767 return NS_OK;
770 return EditorBase::OnFocus(aOriginalEventTargetNode);
773 nsresult TextEditor::OnBlur(const EventTarget* aEventTarget) {
774 // check if something else is focused. If another element is focused, then
775 // we should not change the selection.
776 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
777 if (MOZ_UNLIKELY(!focusManager)) {
778 return NS_OK;
781 // If another element already has focus, we should not maintain the selection
782 // because we may not have the rights doing it.
783 if (focusManager->GetFocusedElement()) {
784 return NS_OK;
787 nsresult rv = FinalizeSelection();
788 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
789 "EditorBase::FinalizeSelection() failed");
790 return rv;
793 nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement,
794 nsAtom* aAttribute,
795 const nsAString& aValue,
796 bool aSuppressTransaction) {
797 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
798 return NS_ERROR_INVALID_ARG;
801 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
802 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
803 if (NS_FAILED(rv)) {
804 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
805 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
806 return EditorBase::ToGenericNSResult(rv);
809 rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
810 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
811 "EditorBase::SetAttributeWithTransaction() failed");
812 return EditorBase::ToGenericNSResult(rv);
815 nsresult TextEditor::RemoveAttributeOrEquivalent(Element* aElement,
816 nsAtom* aAttribute,
817 bool aSuppressTransaction) {
818 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
819 return NS_ERROR_INVALID_ARG;
822 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
823 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
824 if (NS_FAILED(rv)) {
825 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
826 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
827 return EditorBase::ToGenericNSResult(rv);
830 rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
831 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
832 "EditorBase::RemoveAttributeWithTransaction() failed");
833 return EditorBase::ToGenericNSResult(rv);
836 template <typename EditorDOMPointType>
837 EditorDOMPointType TextEditor::FindBetterInsertionPoint(
838 const EditorDOMPointType& aPoint) const {
839 if (MOZ_UNLIKELY(NS_WARN_IF(!aPoint.IsInContentNode()))) {
840 return aPoint;
843 MOZ_ASSERT(aPoint.IsSetAndValid());
845 Element* const anonymousDivElement = GetRoot();
846 if (aPoint.GetContainer() == anonymousDivElement) {
847 // In some cases, aPoint points start of the anonymous <div>. To avoid
848 // injecting unneeded text nodes, we first look to see if we have one
849 // available. In that case, we'll just adjust node and offset accordingly.
850 if (aPoint.IsStartOfContainer()) {
851 if (aPoint.GetContainer()->HasChildren() &&
852 aPoint.GetContainer()->GetFirstChild()->IsText()) {
853 return EditorDOMPointType(aPoint.GetContainer()->GetFirstChild(), 0u);
856 // In some other cases, aPoint points the terminating padding <br> element
857 // for empty last line in the anonymous <div>. In that case, we'll adjust
858 // aInOutNode and aInOutOffset to the preceding text node, if any.
859 else {
860 nsIContent* child = aPoint.GetContainer()->GetLastChild();
861 while (child) {
862 if (child->IsText()) {
863 return EditorDOMPointType::AtEndOf(*child);
865 child = child->GetPreviousSibling();
870 // Sometimes, aPoint points the padding <br> element. In that case, we'll
871 // adjust the insertion point to the previous text node, if one exists, or to
872 // the parent anonymous DIV.
873 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
874 *aPoint.template ContainerAs<nsIContent>()) &&
875 aPoint.IsStartOfContainer()) {
876 nsIContent* previousSibling = aPoint.GetContainer()->GetPreviousSibling();
877 if (previousSibling && previousSibling->IsText()) {
878 return EditorDOMPointType::AtEndOf(*previousSibling);
881 nsINode* parentOfContainer = aPoint.GetContainerParent();
882 if (parentOfContainer && parentOfContainer == anonymousDivElement) {
883 return EditorDOMPointType(parentOfContainer,
884 aPoint.template ContainerAs<nsIContent>(), 0u);
888 return aPoint;
891 // static
892 void TextEditor::MaskString(nsString& aString, const Text& aTextNode,
893 uint32_t aStartOffsetInString,
894 uint32_t aStartOffsetInText) {
895 MOZ_ASSERT(aTextNode.HasFlag(NS_MAYBE_MASKED));
896 MOZ_ASSERT(aStartOffsetInString == 0 || aStartOffsetInText == 0);
898 uint32_t unmaskStart = UINT32_MAX, unmaskLength = 0;
899 TextEditor* textEditor =
900 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(&aTextNode);
901 if (textEditor && textEditor->UnmaskedLength() > 0) {
902 unmaskStart = textEditor->UnmaskedStart();
903 unmaskLength = textEditor->UnmaskedLength();
904 // If text is copied from after unmasked range, we can treat this case
905 // as mask all.
906 if (aStartOffsetInText >= unmaskStart + unmaskLength) {
907 unmaskLength = 0;
908 unmaskStart = UINT32_MAX;
909 } else {
910 // If text is copied from middle of unmasked range, reduce the length
911 // and adjust start offset.
912 if (aStartOffsetInText > unmaskStart) {
913 unmaskLength = unmaskStart + unmaskLength - aStartOffsetInText;
914 unmaskStart = 0;
916 // If text is copied from before start of unmasked range, just adjust
917 // the start offset.
918 else {
919 unmaskStart -= aStartOffsetInText;
921 // Make the range is in the string.
922 unmaskStart += aStartOffsetInString;
926 const char16_t kPasswordMask = TextEditor::PasswordMask();
927 for (uint32_t i = aStartOffsetInString; i < aString.Length(); ++i) {
928 bool isSurrogatePair = NS_IS_HIGH_SURROGATE(aString.CharAt(i)) &&
929 i < aString.Length() - 1 &&
930 NS_IS_LOW_SURROGATE(aString.CharAt(i + 1));
931 if (i < unmaskStart || i >= unmaskStart + unmaskLength) {
932 if (isSurrogatePair) {
933 aString.SetCharAt(kPasswordMask, i);
934 aString.SetCharAt(kPasswordMask, i + 1);
935 } else {
936 aString.SetCharAt(kPasswordMask, i);
940 // Skip the following low surrogate.
941 if (isSurrogatePair) {
942 ++i;
947 nsresult TextEditor::SetUnmaskRangeInternal(uint32_t aStart, uint32_t aLength,
948 uint32_t aTimeout, bool aNotify,
949 bool aForceStartMasking) {
950 if (mPasswordMaskData) {
951 mPasswordMaskData->mIsMaskingPassword = aForceStartMasking || aTimeout != 0;
953 // We cannot manage multiple unmasked ranges so that shrink the previous
954 // range first.
955 if (!IsAllMasked()) {
956 mPasswordMaskData->mUnmaskedLength = 0;
957 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
961 // If we're not a password editor, return error since this call does not
962 // make sense.
963 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) {
964 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes);
965 return NS_ERROR_NOT_AVAILABLE;
968 Element* rootElement = GetRoot();
969 if (NS_WARN_IF(!rootElement)) {
970 return NS_ERROR_NOT_INITIALIZED;
972 Text* text = Text::FromNodeOrNull(rootElement->GetFirstChild());
973 if (!text || !text->Length()) {
974 // There is no anonymous text node in the editor.
975 return aStart > 0 && aStart != UINT32_MAX ? NS_ERROR_INVALID_ARG : NS_OK;
978 if (aStart < UINT32_MAX) {
979 uint32_t valueLength = text->Length();
980 if (aStart >= valueLength) {
981 return NS_ERROR_INVALID_ARG; // There is no character can be masked.
983 // If aStart is middle of a surrogate pair, expand it to include the
984 // preceding high surrogate because the caller may want to show a
985 // character before the character at `aStart + 1`.
986 const nsTextFragment* textFragment = text->GetText();
987 if (textFragment->IsLowSurrogateFollowingHighSurrogateAt(aStart)) {
988 mPasswordMaskData->mUnmaskedStart = aStart - 1;
989 // If caller collapses the range, keep it. Otherwise, expand the length.
990 if (aLength > 0) {
991 ++aLength;
993 } else {
994 mPasswordMaskData->mUnmaskedStart = aStart;
996 mPasswordMaskData->mUnmaskedLength =
997 std::min(valueLength - UnmaskedStart(), aLength);
998 // If unmasked end is middle of a surrogate pair, expand it to include
999 // the following low surrogate because the caller may want to show a
1000 // character after the character at `aStart + aLength`.
1001 if (UnmaskedEnd() < valueLength &&
1002 textFragment->IsLowSurrogateFollowingHighSurrogateAt(UnmaskedEnd())) {
1003 mPasswordMaskData->mUnmaskedLength++;
1005 // If it's first time to mask the unmasking characters with timer, create
1006 // the timer now. Then, we'll keep using it for saving the creation cost.
1007 if (!HasAutoMaskingTimer() && aLength && aTimeout && UnmaskedLength()) {
1008 mPasswordMaskData->mTimer = NS_NewTimer();
1010 } else {
1011 if (NS_WARN_IF(aLength != 0)) {
1012 return NS_ERROR_INVALID_ARG;
1014 mPasswordMaskData->MaskAll();
1017 // Notify nsTextFrame of this update if the caller wants this to do it.
1018 // Only in this case, script may run.
1019 if (aNotify) {
1020 MOZ_ASSERT(IsEditActionDataAvailable());
1022 RefPtr<Document> document = GetDocument();
1023 if (NS_WARN_IF(!document)) {
1024 return NS_ERROR_NOT_INITIALIZED;
1026 // Notify nsTextFrame of masking range change.
1027 if (RefPtr<PresShell> presShell = document->GetObservingPresShell()) {
1028 nsAutoScriptBlocker blockRunningScript;
1029 uint32_t valueLength = text->Length();
1030 CharacterDataChangeInfo changeInfo = {false, 0, valueLength, valueLength,
1031 nullptr};
1032 presShell->CharacterDataChanged(text, changeInfo);
1035 // Scroll caret into the view since masking or unmasking character may
1036 // move caret to outside of the view.
1037 nsresult rv = ScrollSelectionFocusIntoView();
1038 if (NS_FAILED(rv)) {
1039 NS_WARNING("EditorBase::ScrollSelectionFocusIntoView() failed");
1040 return rv;
1044 if (!IsAllMasked() && aTimeout != 0) {
1045 // Initialize the timer to mask the range automatically.
1046 MOZ_ASSERT(HasAutoMaskingTimer());
1047 DebugOnly<nsresult> rvIgnored = mPasswordMaskData->mTimer->InitWithCallback(
1048 this, aTimeout, nsITimer::TYPE_ONE_SHOT);
1049 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1050 "nsITimer::InitWithCallback() failed, but ignored");
1053 return NS_OK;
1056 // static
1057 char16_t TextEditor::PasswordMask() {
1058 char16_t ret = LookAndFeel::GetPasswordCharacter();
1059 if (!ret) {
1060 ret = '*';
1062 return ret;
1065 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TextEditor::Notify(nsITimer* aTimer) {
1066 // Check whether our text editor's password flag was changed before this
1067 // "hide password character" timer actually fires.
1068 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) {
1069 return NS_OK;
1072 if (IsAllMasked()) {
1073 return NS_OK;
1076 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
1077 if (NS_WARN_IF(!editActionData.CanHandle())) {
1078 return NS_ERROR_NOT_INITIALIZED;
1081 // Mask all characters.
1082 nsresult rv = MaskAllCharactersAndNotify();
1083 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1084 "TextEditor::MaskAllCharactersAndNotify() failed");
1086 if (StaticPrefs::editor_password_testing_mask_delay()) {
1087 if (RefPtr<Element> target = GetInputEventTargetElement()) {
1088 RefPtr<Document> document = target->OwnerDoc();
1089 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchTrustedEvent(
1090 document, target, u"MozLastInputMasked"_ns, CanBubble::eYes,
1091 Cancelable::eNo);
1092 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1093 "nsContentUtils::DispatchTrustedEvent("
1094 "MozLastInputMasked) failed, but ignored");
1098 return EditorBase::ToGenericNSResult(rv);
1101 NS_IMETHODIMP TextEditor::GetName(nsACString& aName) {
1102 aName.AssignLiteral("TextEditor");
1103 return NS_OK;
1106 void TextEditor::WillDeleteText(uint32_t aCurrentLength,
1107 uint32_t aRemoveStartOffset,
1108 uint32_t aRemoveLength) {
1109 MOZ_ASSERT(IsEditActionDataAvailable());
1111 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) {
1112 return;
1115 // Adjust unmasked range before deletion since DOM mutation may cause
1116 // layout referring the range in old text.
1118 // If we need to mask automatically, mask all now.
1119 if (IsMaskingPassword()) {
1120 DebugOnly<nsresult> rvIgnored = MaskAllCharacters();
1121 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1122 "TextEditor::MaskAllCharacters() failed, but ignored");
1123 return;
1126 if (aRemoveStartOffset < UnmaskedStart()) {
1127 // If removing range is before the unmasked range, move it.
1128 if (aRemoveStartOffset + aRemoveLength <= UnmaskedStart()) {
1129 DebugOnly<nsresult> rvIgnored =
1130 SetUnmaskRange(UnmaskedStart() - aRemoveLength, UnmaskedLength());
1131 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1132 "TextEditor::SetUnmaskRange() failed, but ignored");
1133 return;
1136 // If removing range starts before unmasked range, and ends in unmasked
1137 // range, move and shrink the range.
1138 if (aRemoveStartOffset + aRemoveLength < UnmaskedEnd()) {
1139 uint32_t unmaskedLengthInRemovingRange =
1140 aRemoveStartOffset + aRemoveLength - UnmaskedStart();
1141 DebugOnly<nsresult> rvIgnored = SetUnmaskRange(
1142 aRemoveStartOffset, UnmaskedLength() - unmaskedLengthInRemovingRange);
1143 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1144 "TextEditor::SetUnmaskRange() failed, but ignored");
1145 return;
1148 // If removing range includes all unmasked range, collapse it to the
1149 // remove offset.
1150 DebugOnly<nsresult> rvIgnored = SetUnmaskRange(aRemoveStartOffset, 0);
1151 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1152 "TextEditor::SetUnmaskRange() failed, but ignored");
1153 return;
1156 if (aRemoveStartOffset < UnmaskedEnd()) {
1157 // If removing range is in unmasked range, shrink the range.
1158 if (aRemoveStartOffset + aRemoveLength <= UnmaskedEnd()) {
1159 DebugOnly<nsresult> rvIgnored =
1160 SetUnmaskRange(UnmaskedStart(), UnmaskedLength() - aRemoveLength);
1161 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1162 "TextEditor::SetUnmaskRange() failed, but ignored");
1163 return;
1166 // If removing range starts from unmasked range, and ends after it,
1167 // shrink it.
1168 DebugOnly<nsresult> rvIgnored =
1169 SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset - UnmaskedStart());
1170 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1171 "TextEditor::SetUnmaskRange() failed, but ignored");
1172 return;
1175 // If removing range is after the unmasked range, keep it.
1178 nsresult TextEditor::DidInsertText(uint32_t aNewLength,
1179 uint32_t aInsertedOffset,
1180 uint32_t aInsertedLength) {
1181 MOZ_ASSERT(IsEditActionDataAvailable());
1183 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) {
1184 return NS_OK;
1187 if (IsMaskingPassword()) {
1188 // If we need to mask password, mask all right now.
1189 nsresult rv = MaskAllCharactersAndNotify();
1190 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1191 "TextEditor::MaskAllCharacters() failed");
1192 return rv;
1195 if (aInsertedOffset < UnmaskedStart()) {
1196 // If insertion point is before unmasked range, expand the unmasked range
1197 // to include the new text.
1198 nsresult rv = SetUnmaskRangeAndNotify(
1199 aInsertedOffset, UnmaskedEnd() + aInsertedLength - aInsertedOffset);
1200 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1201 "TextEditor::SetUnmaskRangeAndNotify() failed");
1202 return rv;
1205 if (aInsertedOffset <= UnmaskedEnd()) {
1206 // If insertion point is in unmasked range, unmask new text.
1207 nsresult rv = SetUnmaskRangeAndNotify(UnmaskedStart(),
1208 UnmaskedLength() + aInsertedLength);
1209 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1210 "TextEditor::SetUnmaskRangeAndNotify() failed");
1211 return rv;
1214 // If insertion point is after unmasked range, extend the unmask range to
1215 // include the new text.
1216 nsresult rv = SetUnmaskRangeAndNotify(
1217 UnmaskedStart(), aInsertedOffset + aInsertedLength - UnmaskedStart());
1218 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1219 "TextEditor::SetUnmaskRangeAndNotify() failed");
1220 return rv;
1223 } // namespace mozilla