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"
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"
41 #include "nsCharTraits.h"
42 #include "nsComponentManagerUtils.h"
43 #include "nsContentList.h"
45 #include "nsDependentSubstring.h"
47 #include "nsFocusManager.h"
48 #include "nsGkAtoms.h"
49 #include "nsIClipboard.h"
50 #include "nsIContent.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"
63 #include "nsStringFwd.h"
64 #include "nsTextFragment.h"
65 #include "nsTextNode.h"
66 #include "nsUnicharUtils.h"
69 class nsIOutputStream
;
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));
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.
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");
150 nsresult
TextEditor::Init(Document
& aDocument
, Element
& aAnonymousDivElement
,
151 nsISelectionController
& aSelectionController
,
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
);
163 NS_WARNING("EditorBase::InitInternal() failed");
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();
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.
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();
204 NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed");
209 if (!IsSingleLineEditor()) {
210 nsresult rv
= EnsurePaddingBRElementInMultilineEditor();
213 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
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");
242 UniquePtr
<PasswordMaskData
> TextEditor::PreDestroy() {
243 if (mDidPreDestroy
) {
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
;
275 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent
);
279 MOZ_ASSERT(aKeyboardEvent
->mMessage
== eKeyPress
,
280 "HandleKeyPressEvent gets non-keypress event");
282 switch (aKeyboardEvent
->mKeyCode
) {
288 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress
290 aKeyboardEvent
->PreventDefault();
296 nsresult rv
= EditorBase::HandleKeyPressEvent(aKeyboardEvent
);
297 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
298 "EditorBase::HandleKeyPressEvent() failed");
302 if (!aKeyboardEvent
->IsInputtingLineBreak()) {
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");
317 if (!aKeyboardEvent
->IsInputtingText()) {
318 // we don't PreventDefault() here or keybindings like control-x won't work
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
)) {
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
);
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
));
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");
355 NS_IMETHODIMP
TextEditor::InsertLineBreak() {
356 AutoEditActionDataSetter
editActionData(*this, EditAction::eInsertLineBreak
);
357 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
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
,
379 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
381 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
382 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
383 return EditorBase::ToGenericNSResult(rv
);
386 if (IsSingleLineEditor()) {
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
,
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
,
409 if (aAllowBeforeInputEventCancelable
== AllowBeforeInputEventCancelable::No
) {
410 editActionData
.MakeBeforeInputEventNonCancelable();
412 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
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()) {
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
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
) {
502 // initialize out params
505 // special-case for empty document, to account for the padding <br> element
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.
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
;
537 bool TextEditor::IsCopyToClipboardAllowedInternal() const {
538 MOZ_ASSERT(IsEditActionDataAvailable());
539 if (!EditorBase::IsCopyToClipboardAllowedInternal()) {
543 if (!IsSingleLineEditor() || !IsPasswordEditor() ||
544 NS_WARN_IF(!mPasswordMaskData
)) {
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()) {
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) {
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())) {
575 // Get Clipboard Service
577 nsCOMPtr
<nsIClipboard
> clipboard
=
578 do_GetService("@mozilla.org/widget/clipboard;1", &rv
);
580 NS_WARNING("Failed to get nsIClipboard service");
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());
596 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
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
));
616 NS_WARNING("nsITransferable::GetAnyTransferData() failed");
620 if (!flavor
.EqualsLiteral(kTextMime
) &&
621 !flavor
.EqualsLiteral(kMozTextInternal
)) {
625 nsCOMPtr
<nsISupportsString
> text
= do_QueryInterface(genericDataObj
);
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()) {
638 aEditActionData
.SetData(stuffToPaste
);
639 if (!stuffToPaste
.IsEmpty()) {
640 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste
);
642 rv
= aEditActionData
.MaybeDispatchBeforeInputEvent();
644 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
645 "MaybeDispatchBeforeInputEvent() failed");
649 AutoPlaceholderBatch
treatAsOneTransaction(
650 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
651 rv
= InsertWithQuotationsAsSubAction(stuffToPaste
);
652 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
653 "TextEditor::InsertWithQuotationsAsSubAction() failed");
657 nsresult
TextEditor::InsertWithQuotationsAsSubAction(
658 const nsAString
& aQuotedText
) {
659 MOZ_ASSERT(IsEditActionDataAvailable());
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");
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
;
708 Text::FromNodeOrNull(anonymousDivElement
->GetFirstChild());
711 MOZ_TRY(SelectionRef().SetStartAndEndInLimiter(
712 *text
, 0, *text
, text
->TextDataLength(), eDirNext
,
713 nsISelectionListener::SELECTALL_REASON
));
718 EventTarget
* TextEditor::GetDOMEventTarget() const { return mEventTarget
; }
720 void TextEditor::ReinitializeSelection(Element
& aElement
) {
721 if (NS_WARN_IF(Destroyed())) {
725 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
726 if (NS_WARN_IF(!editActionData
.CanHandle())) {
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.
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
))) {
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(
765 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
766 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode
))) {
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
)) {
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()) {
787 nsresult rv
= FinalizeSelection();
788 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
789 "EditorBase::FinalizeSelection() failed");
793 nsresult
TextEditor::SetAttributeOrEquivalent(Element
* aElement
,
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();
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
,
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();
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()))) {
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.
860 nsIContent
* child
= aPoint
.GetContainer()->GetLastChild();
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);
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
906 if (aStartOffsetInText
>= unmaskStart
+ unmaskLength
) {
908 unmaskStart
= UINT32_MAX
;
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
;
916 // If text is copied from before start of unmasked range, just adjust
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);
936 aString
.SetCharAt(kPasswordMask
, i
);
940 // Skip the following low surrogate.
941 if (isSurrogatePair
) {
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
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
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.
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();
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.
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
,
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");
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");
1057 char16_t
TextEditor::PasswordMask() {
1058 char16_t ret
= LookAndFeel::GetPasswordCharacter();
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
)) {
1072 if (IsAllMasked()) {
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
,
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");
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()) {
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");
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");
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");
1148 // If removing range includes all unmasked range, collapse it to the
1150 DebugOnly
<nsresult
> rvIgnored
= SetUnmaskRange(aRemoveStartOffset
, 0);
1151 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1152 "TextEditor::SetUnmaskRange() failed, but ignored");
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");
1166 // If removing range starts from unmasked range, and ends after it,
1168 DebugOnly
<nsresult
> rvIgnored
=
1169 SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset
- UnmaskedStart());
1170 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1171 "TextEditor::SetUnmaskRange() failed, but ignored");
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()) {
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");
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");
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");
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");
1223 } // namespace mozilla