Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / editor / libeditor / TextEditor.cpp
blobcb9c4d6f96d2773e19a5161c7dccb956db28408e
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_editor.h"
28 #include "mozilla/TextComposition.h"
29 #include "mozilla/TextEvents.h"
30 #include "mozilla/TextServicesDocument.h"
31 #include "mozilla/dom/Event.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/Selection.h"
34 #include "mozilla/dom/StaticRange.h"
36 #include "nsAString.h"
37 #include "nsCRT.h"
38 #include "nsCaret.h"
39 #include "nsCharTraits.h"
40 #include "nsComponentManagerUtils.h"
41 #include "nsContentCID.h"
42 #include "nsContentList.h"
43 #include "nsDebug.h"
44 #include "nsDependentSubstring.h"
45 #include "nsError.h"
46 #include "nsGkAtoms.h"
47 #include "nsIClipboard.h"
48 #include "nsIContent.h"
49 #include "nsINode.h"
50 #include "nsIPrincipal.h"
51 #include "nsISelectionController.h"
52 #include "nsISupportsPrimitives.h"
53 #include "nsITransferable.h"
54 #include "nsIWeakReferenceUtils.h"
55 #include "nsNameSpaceManager.h"
56 #include "nsLiteralString.h"
57 #include "nsPresContext.h"
58 #include "nsReadableUtils.h"
59 #include "nsServiceManagerUtils.h"
60 #include "nsString.h"
61 #include "nsStringFwd.h"
62 #include "nsTextFragment.h"
63 #include "nsTextNode.h"
64 #include "nsUnicharUtils.h"
65 #include "nsXPCOM.h"
67 class nsIOutputStream;
68 class nsISupports;
70 namespace mozilla {
72 using namespace dom;
74 using LeafNodeType = HTMLEditUtils::LeafNodeType;
75 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
77 TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text) {
78 // printf("Size of TextEditor: %zu\n", sizeof(TextEditor));
79 static_assert(
80 sizeof(TextEditor) <= 512,
81 "TextEditor instance should be allocatable in the quantum class bins");
84 TextEditor::~TextEditor() {
85 // Remove event listeners. Note that if we had an HTML editor,
86 // it installed its own instead of these
87 RemoveEventListeners();
90 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
92 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase)
93 if (tmp->mPasswordMaskData) {
94 tmp->mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
95 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPasswordMaskData->mTimer)
97 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase)
100 if (tmp->mPasswordMaskData) {
101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPasswordMaskData->mTimer)
103 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
105 NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase)
106 NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase)
108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor)
109 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
110 NS_INTERFACE_MAP_ENTRY(nsINamed)
111 NS_INTERFACE_MAP_END_INHERITING(EditorBase)
113 NS_IMETHODIMP TextEditor::EndOfDocument() {
114 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
115 if (NS_WARN_IF(!editActionData.CanHandle())) {
116 return NS_ERROR_NOT_INITIALIZED;
118 nsresult rv = CollapseSelectionToEndOfTextNode();
119 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
120 "TextEditor::CollapseSelectionToEndOfTextNode() failed");
121 // This is low level API for embedders and chrome script so that we can return
122 // raw error code here.
123 return rv;
126 nsresult TextEditor::CollapseSelectionToEndOfTextNode() {
127 MOZ_ASSERT(IsEditActionDataAvailable());
129 Element* anonymousDivElement = GetRoot();
130 if (NS_WARN_IF(!anonymousDivElement)) {
131 return NS_ERROR_NULL_POINTER;
134 RefPtr<Text> textNode =
135 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
136 MOZ_ASSERT(textNode);
137 nsresult rv = CollapseSelectionToEndOf(*textNode);
138 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
139 "EditorBase::CollapseSelectionToEndOf() failed");
140 return rv;
143 nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement,
144 nsISelectionController& aSelectionController,
145 uint32_t aFlags,
146 UniquePtr<PasswordMaskData>&& aPasswordMaskData) {
147 MOZ_ASSERT(!mInitSucceeded,
148 "TextEditor::Init() called again without calling PreDestroy()?");
149 MOZ_ASSERT(!(aFlags & nsIEditor::eEditorPasswordMask) == !aPasswordMaskData);
150 mPasswordMaskData = std::move(aPasswordMaskData);
152 // Init the base editor
153 nsresult rv = InitInternal(aDocument, &aAnonymousDivElement,
154 aSelectionController, aFlags);
155 if (NS_FAILED(rv)) {
156 NS_WARNING("EditorBase::InitInternal() failed");
157 return rv;
160 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
161 if (NS_WARN_IF(!editActionData.CanHandle())) {
162 return NS_ERROR_FAILURE;
165 // We set mInitSucceeded here rather than at the end of the function,
166 // since InitEditorContentAndSelection() can perform some transactions
167 // and can warn if mInitSucceeded is still false.
168 MOZ_ASSERT(!mInitSucceeded, "TextEditor::Init() shouldn't be nested");
169 mInitSucceeded = true;
171 rv = InitEditorContentAndSelection();
172 if (NS_FAILED(rv)) {
173 NS_WARNING("TextEditor::InitEditorContentAndSelection() failed");
174 // XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
175 // is a public method?
176 mInitSucceeded = false;
177 return EditorBase::ToGenericNSResult(rv);
180 // Throw away the old transaction manager if this is not the first time that
181 // we're initializing the editor.
182 ClearUndoRedo();
183 EnableUndoRedo();
184 return NS_OK;
187 nsresult TextEditor::InitEditorContentAndSelection() {
188 MOZ_ASSERT(IsEditActionDataAvailable());
190 MOZ_TRY(EnsureEmptyTextFirstChild());
192 // If the selection hasn't been set up yet, set it up collapsed to the end of
193 // our editable content.
194 if (!SelectionRef().RangeCount()) {
195 nsresult rv = CollapseSelectionToEndOfTextNode();
196 if (NS_FAILED(rv)) {
197 NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed");
198 return rv;
202 if (!IsSingleLineEditor()) {
203 nsresult rv = EnsurePaddingBRElementInMultilineEditor();
204 if (NS_FAILED(rv)) {
205 NS_WARNING(
206 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
207 return rv;
211 return NS_OK;
214 nsresult TextEditor::PostCreate() {
215 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
216 if (NS_WARN_IF(!editActionData.CanHandle())) {
217 return NS_ERROR_NOT_INITIALIZED;
220 nsresult rv = PostCreateInternal();
222 // Restore unmasked range if there is.
223 if (IsPasswordEditor() && !IsAllMasked()) {
224 DebugOnly<nsresult> rvIgnored =
225 SetUnmaskRangeAndNotify(UnmaskedStart(), UnmaskedLength());
226 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
227 "TextEditor::SetUnmaskRangeAndNotify() failed to "
228 "restore unmasked range, but ignored");
230 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
231 "EditorBase::PostCreateInternal() failed");
232 return rv;
235 UniquePtr<PasswordMaskData> TextEditor::PreDestroy() {
236 if (mDidPreDestroy) {
237 return nullptr;
240 UniquePtr<PasswordMaskData> passwordMaskData = std::move(mPasswordMaskData);
241 if (passwordMaskData) {
242 // Disable auto-masking timer since nobody can catch the notification
243 // from the timer and canceling the unmasking.
244 passwordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes);
245 // Similary, keeping preventing echoing password temporarily across
246 // TextEditor instances is hard. So, we should forget it.
247 passwordMaskData->mEchoingPasswordPrevented = false;
250 PreDestroyInternal();
252 return passwordMaskData;
255 nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
256 // NOTE: When you change this method, you should also change:
257 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
258 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
260 // And also when you add new key handling, you need to change the subclass's
261 // HandleKeyPressEvent()'s switch statement.
263 if (NS_WARN_IF(!aKeyboardEvent)) {
264 return NS_ERROR_UNEXPECTED;
267 if (IsReadonly()) {
268 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent);
269 return NS_OK;
272 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
273 "HandleKeyPressEvent gets non-keypress event");
275 switch (aKeyboardEvent->mKeyCode) {
276 case NS_VK_META:
277 case NS_VK_WIN:
278 case NS_VK_SHIFT:
279 case NS_VK_CONTROL:
280 case NS_VK_ALT:
281 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress
282 // event.
283 aKeyboardEvent->PreventDefault();
284 return NS_OK;
286 case NS_VK_BACK:
287 case NS_VK_DELETE:
288 case NS_VK_TAB: {
289 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
290 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
291 "EditorBase::HandleKeyPressEvent() failed");
292 return rv;
294 case NS_VK_RETURN: {
295 if (!aKeyboardEvent->IsInputtingLineBreak()) {
296 return NS_OK;
298 if (!IsSingleLineEditor()) {
299 aKeyboardEvent->PreventDefault();
301 // We need to dispatch "beforeinput" event at least even if we're a
302 // single line text editor.
303 nsresult rv = InsertLineBreakAsAction();
304 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
305 "TextEditor::InsertLineBreakAsAction() failed");
306 return rv;
310 if (!aKeyboardEvent->IsInputtingText()) {
311 // we don't PreventDefault() here or keybindings like control-x won't work
312 return NS_OK;
314 // Our widget shouldn't set `\r` to `mCharCode`, but it may be synthesized
315 // keyboard event and its value may be `\r`. In such case, we should treat
316 // it as `\n` for the backward compatibility because we stopped converting
317 // `\r` and `\r\n` to `\n` at getting `HTMLInputElement.value` and
318 // `HTMLTextAreaElement.value` for the performance (i.e., we don't need to
319 // take care in `HTMLEditor`).
320 char16_t charCode =
321 static_cast<char16_t>(aKeyboardEvent->mCharCode) == nsCRT::CR
322 ? nsCRT::LF
323 : static_cast<char16_t>(aKeyboardEvent->mCharCode);
324 aKeyboardEvent->PreventDefault();
325 nsAutoString str(charCode);
326 nsresult rv = OnInputText(str);
327 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed");
328 return rv;
331 NS_IMETHODIMP TextEditor::InsertLineBreak() {
332 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak);
333 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
334 if (NS_FAILED(rv)) {
335 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
336 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
337 return EditorBase::ToGenericNSResult(rv);
340 if (NS_WARN_IF(IsSingleLineEditor())) {
341 return NS_ERROR_FAILURE;
344 AutoPlaceholderBatch treatAsOneTransaction(
345 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
346 rv = InsertLineBreakAsSubAction();
347 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
348 "TextEditor::InsertLineBreakAsSubAction() failed");
349 return EditorBase::ToGenericNSResult(rv);
352 nsresult TextEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) {
353 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak,
354 aPrincipal);
355 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
356 if (NS_FAILED(rv)) {
357 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
358 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
359 return EditorBase::ToGenericNSResult(rv);
362 if (IsSingleLineEditor()) {
363 return NS_OK;
366 // XXX This may be called by execCommand() with "insertParagraph".
367 // In such case, naming the transaction "TypingTxnName" is odd.
368 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
369 ScrollSelectionIntoView::Yes,
370 __FUNCTION__);
371 rv = InsertLineBreakAsSubAction();
372 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
373 "EditorBase::InsertLineBreakAsSubAction() failed");
374 return EditorBase::ToGenericNSResult(rv);
377 nsresult TextEditor::SetTextAsAction(
378 const nsAString& aString,
379 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
380 nsIPrincipal* aPrincipal) {
381 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound);
383 AutoEditActionDataSetter editActionData(*this, EditAction::eSetText,
384 aPrincipal);
385 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) {
386 editActionData.MakeBeforeInputEventNonCancelable();
388 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
389 if (NS_FAILED(rv)) {
390 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
391 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
392 return EditorBase::ToGenericNSResult(rv);
395 AutoPlaceholderBatch treatAsOneTransaction(
396 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
397 rv = SetTextAsSubAction(aString);
398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
399 "TextEditor::SetTextAsSubAction() failed");
400 return EditorBase::ToGenericNSResult(rv);
403 nsresult TextEditor::SetTextAsSubAction(const nsAString& aString) {
404 MOZ_ASSERT(IsEditActionDataAvailable());
405 MOZ_ASSERT(mPlaceholderBatch);
407 if (NS_WARN_IF(!mInitSucceeded)) {
408 return NS_ERROR_NOT_INITIALIZED;
411 IgnoredErrorResult ignoredError;
412 AutoEditSubActionNotifier startToHandleEditSubAction(
413 *this, EditSubAction::eSetText, nsIEditor::eNext, ignoredError);
414 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
415 return ignoredError.StealNSResult();
417 NS_WARNING_ASSERTION(
418 !ignoredError.Failed(),
419 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
421 if (!IsIMEComposing() && !IsUndoRedoEnabled() &&
422 GetEditAction() != EditAction::eReplaceText && mMaxTextLength < 0) {
423 Result<EditActionResult, nsresult> result =
424 SetTextWithoutTransaction(aString);
425 if (MOZ_UNLIKELY(result.isErr())) {
426 NS_WARNING("TextEditor::SetTextWithoutTransaction() failed");
427 return result.unwrapErr();
429 if (!result.inspect().Ignored()) {
430 return NS_OK;
435 // Note that do not notify selectionchange caused by selecting all text
436 // because it's preparation of our delete implementation so web apps
437 // shouldn't receive such selectionchange before the first mutation.
438 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
440 // XXX We should make ReplaceSelectionAsSubAction() take range. Then,
441 // we can saving the expensive cost of modifying `Selection` here.
442 if (NS_SUCCEEDED(SelectEntireDocument())) {
443 DebugOnly<nsresult> rvIgnored = ReplaceSelectionAsSubAction(aString);
444 NS_WARNING_ASSERTION(
445 NS_SUCCEEDED(rvIgnored),
446 "EditorBase::ReplaceSelectionAsSubAction() failed, but ignored");
450 // Destroying AutoUpdateViewBatch may cause destroying us.
451 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
454 already_AddRefed<Element> TextEditor::GetInputEventTargetElement() const {
455 RefPtr<Element> target = Element::FromEventTargetOrNull(mEventTarget);
456 return target.forget();
459 bool TextEditor::IsEmpty() const {
460 // Even if there is no padding <br> element for empty editor, we should be
461 // detected as empty editor if all the children are text nodes and these
462 // have no content.
463 Element* anonymousDivElement = GetRoot();
464 if (!anonymousDivElement) {
465 return true; // Don't warn it, this is possible, e.g., 997805.html
468 MOZ_ASSERT(anonymousDivElement->GetFirstChild() &&
469 anonymousDivElement->GetFirstChild()->IsText());
471 // Only when there is non-empty text node, we are not empty.
472 return !anonymousDivElement->GetFirstChild()->Length();
475 NS_IMETHODIMP TextEditor::GetTextLength(uint32_t* aCount) {
476 MOZ_ASSERT(aCount);
478 // initialize out params
479 *aCount = 0;
481 // special-case for empty document, to account for the padding <br> element
482 // for empty editor.
483 // XXX This should be overridden by `HTMLEditor` and we should return the
484 // first text node's length from `TextEditor` instead. The following
485 // code is too expensive.
486 if (IsEmpty()) {
487 return NS_OK;
490 Element* rootElement = GetRoot();
491 if (NS_WARN_IF(!rootElement)) {
492 return NS_ERROR_FAILURE;
495 uint32_t totalLength = 0;
496 PostContentIterator postOrderIter;
497 DebugOnly<nsresult> rvIgnored = postOrderIter.Init(rootElement);
498 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
499 "PostContentIterator::Init() failed, but ignored");
500 EditorType editorType = GetEditorType();
501 for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
502 nsINode* currentNode = postOrderIter.GetCurrentNode();
503 if (currentNode && currentNode->IsText() &&
504 EditorUtils::IsEditableContent(*currentNode->AsText(), editorType)) {
505 totalLength += currentNode->Length();
509 *aCount = totalLength;
510 return NS_OK;
513 bool TextEditor::IsCopyToClipboardAllowedInternal() const {
514 MOZ_ASSERT(IsEditActionDataAvailable());
515 if (!EditorBase::IsCopyToClipboardAllowedInternal()) {
516 return false;
519 if (!IsSingleLineEditor() || !IsPasswordEditor() ||
520 NS_WARN_IF(!mPasswordMaskData)) {
521 return true;
524 // If we're a password editor, we should allow selected text to be copied
525 // to the clipboard only when selection range is in unmasked range.
526 if (IsAllMasked() || IsMaskingPassword() || !UnmaskedLength()) {
527 return false;
530 // If there are 2 or more ranges, we don't allow to copy/cut for now since
531 // we need to check whether all ranges are in unmasked range or not.
532 // Anyway, such operation in password field does not make sense.
533 if (SelectionRef().RangeCount() > 1) {
534 return false;
537 uint32_t selectionStart = 0, selectionEnd = 0;
538 nsContentUtils::GetSelectionInTextControl(&SelectionRef(), mRootElement,
539 selectionStart, selectionEnd);
540 return UnmaskedStart() <= selectionStart && UnmaskedEnd() >= selectionEnd;
543 nsresult TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType,
544 bool aDispatchPasteEvent,
545 nsIPrincipal* aPrincipal) {
546 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
547 aClipboardType == nsIClipboard::kSelectionClipboard);
549 AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation,
550 aPrincipal);
551 if (NS_WARN_IF(!editActionData.CanHandle())) {
552 return NS_ERROR_NOT_INITIALIZED;
554 MOZ_ASSERT(GetDocument());
556 // Get Clipboard Service
557 nsresult rv;
558 nsCOMPtr<nsIClipboard> clipboard =
559 do_GetService("@mozilla.org/widget/clipboard;1", &rv);
560 if (NS_FAILED(rv)) {
561 NS_WARNING("Failed to get nsIClipboard service");
562 return rv;
565 // XXX Why don't we dispatch ePaste event here?
567 // Get the nsITransferable interface for getting the data from the clipboard
568 Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable =
569 EditorUtils::CreateTransferableForPlainText(*GetDocument());
570 if (maybeTransferable.isErr()) {
571 NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed");
572 return EditorBase::ToGenericNSResult(maybeTransferable.unwrapErr());
574 nsCOMPtr<nsITransferable> trans(maybeTransferable.unwrap());
575 if (!trans) {
576 NS_WARNING(
577 "EditorUtils::CreateTransferableForPlainText() returned nullptr, but "
578 "ignored");
579 return NS_OK;
582 // Get the Data from the clipboard
583 clipboard->GetData(trans, aClipboardType);
585 // Now we ask the transferable for the data
586 // it still owns the data, we just have a pointer to it.
587 // If it can't support a "text" output of the data the call will fail
588 nsCOMPtr<nsISupports> genericDataObj;
589 nsAutoCString flav;
590 rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj));
591 if (NS_FAILED(rv)) {
592 NS_WARNING("nsITransferable::GetAnyTransferData() failed");
593 return EditorBase::ToGenericNSResult(rv);
596 if (!flav.EqualsLiteral(kUnicodeMime) &&
597 !flav.EqualsLiteral(kMozTextInternal)) {
598 return NS_OK;
601 nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj);
602 if (!text) {
603 return NS_OK;
606 nsString stuffToPaste;
607 DebugOnly<nsresult> rvIgnored = text->GetData(stuffToPaste);
608 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
609 "nsISupportsString::GetData() failed, but ignored");
610 if (stuffToPaste.IsEmpty()) {
611 return NS_OK;
614 editActionData.SetData(stuffToPaste);
615 if (!stuffToPaste.IsEmpty()) {
616 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
618 // XXX Perhaps, we should dispatch "paste" event with the pasting text data.
619 editActionData.NotifyOfDispatchingClipboardEvent();
620 rv = editActionData.MaybeDispatchBeforeInputEvent();
621 if (NS_FAILED(rv)) {
622 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
623 "MaybeDispatchBeforeInputEvent() failed");
624 return EditorBase::ToGenericNSResult(rv);
627 AutoPlaceholderBatch treatAsOneTransaction(
628 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
629 rv = InsertWithQuotationsAsSubAction(stuffToPaste);
630 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
631 "TextEditor::InsertWithQuotationsAsSubAction() failed");
632 return EditorBase::ToGenericNSResult(rv);
635 nsresult TextEditor::InsertWithQuotationsAsSubAction(
636 const nsAString& aQuotedText) {
637 MOZ_ASSERT(IsEditActionDataAvailable());
639 if (IsReadonly()) {
640 return NS_OK;
643 // Let the citer quote it for us:
644 nsString quotedStuff;
645 InternetCiter::GetCiteString(aQuotedText, quotedStuff);
647 // It's best to put a blank line after the quoted text so that mails
648 // written without thinking won't be so ugly.
649 if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) {
650 quotedStuff.Append(char16_t('\n'));
653 IgnoredErrorResult ignoredError;
654 AutoEditSubActionNotifier startToHandleEditSubAction(
655 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
656 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
657 return ignoredError.StealNSResult();
659 NS_WARNING_ASSERTION(
660 !ignoredError.Failed(),
661 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
663 // XXX Do we need to support paste-as-quotation in password editor (and
664 // also in single line editor)?
665 MaybeDoAutoPasswordMasking();
667 nsresult rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
668 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
669 "EditorBase::InsertTextAsSubAction() failed");
670 return rv;
673 nsresult TextEditor::SelectEntireDocument() {
674 MOZ_ASSERT(IsEditActionDataAvailable());
676 if (NS_WARN_IF(!mInitSucceeded)) {
677 return NS_ERROR_NOT_INITIALIZED;
680 RefPtr<Element> anonymousDivElement = GetRoot();
681 if (NS_WARN_IF(!anonymousDivElement)) {
682 return NS_ERROR_NOT_INITIALIZED;
685 RefPtr<Text> text =
686 Text::FromNodeOrNull(anonymousDivElement->GetFirstChild());
687 MOZ_ASSERT(text);
689 MOZ_TRY(SelectionRef().SetStartAndEndInLimiter(
690 *text, 0, *text, text->TextDataLength(), eDirNext,
691 nsISelectionListener::SELECTALL_REASON));
693 return NS_OK;
696 EventTarget* TextEditor::GetDOMEventTarget() const { return mEventTarget; }
698 void TextEditor::ReinitializeSelection(Element& aElement) {
699 if (NS_WARN_IF(Destroyed())) {
700 return;
703 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
704 if (NS_WARN_IF(!editActionData.CanHandle())) {
705 return;
708 // We don't need to flush pending notifications here and we don't need to
709 // handle spellcheck at first focus. Therefore, we don't need to call
710 // `TextEditor::OnFocus` here.
711 EditorBase::OnFocus(aElement);
713 // If previous focused editor turn on spellcheck and this editor doesn't
714 // turn on it, spellcheck state is mismatched. So we need to re-sync it.
715 SyncRealTimeSpell();
718 nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
719 RefPtr<PresShell> presShell = GetPresShell();
720 if (NS_WARN_IF(!presShell)) {
721 return NS_ERROR_FAILURE;
723 // Let's update the layout information right now because there are some
724 // pending notifications and flushing them may cause destroying the editor.
725 presShell->FlushPendingNotifications(FlushType::Layout);
726 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
727 return NS_OK;
730 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
731 if (NS_WARN_IF(!editActionData.CanHandle())) {
732 return NS_ERROR_FAILURE;
735 // Spell check a textarea the first time that it is focused.
736 nsresult rv = FlushPendingSpellCheck();
737 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
738 NS_WARNING("EditorBase::FlushPendingSpellCheck() failed");
739 return NS_ERROR_EDITOR_DESTROYED;
741 NS_WARNING_ASSERTION(
742 NS_SUCCEEDED(rv),
743 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
744 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
745 return NS_OK;
748 return EditorBase::OnFocus(aOriginalEventTargetNode);
751 nsresult TextEditor::OnBlur(const EventTarget* aEventTarget) {
752 nsresult rv = FinalizeSelection();
753 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
754 "EditorBase::FinalizeSelection() failed");
755 return rv;
758 nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement,
759 nsAtom* aAttribute,
760 const nsAString& aValue,
761 bool aSuppressTransaction) {
762 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
763 return NS_ERROR_INVALID_ARG;
766 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
767 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
768 if (NS_FAILED(rv)) {
769 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
770 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
771 return EditorBase::ToGenericNSResult(rv);
774 rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
775 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
776 "EditorBase::SetAttributeWithTransaction() failed");
777 return EditorBase::ToGenericNSResult(rv);
780 nsresult TextEditor::RemoveAttributeOrEquivalent(Element* aElement,
781 nsAtom* aAttribute,
782 bool aSuppressTransaction) {
783 if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
784 return NS_ERROR_INVALID_ARG;
787 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
788 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
789 if (NS_FAILED(rv)) {
790 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
791 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
792 return EditorBase::ToGenericNSResult(rv);
795 rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
796 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
797 "EditorBase::RemoveAttributeWithTransaction() failed");
798 return EditorBase::ToGenericNSResult(rv);
801 // static
802 void TextEditor::MaskString(nsString& aString, const Text& aTextNode,
803 uint32_t aStartOffsetInString,
804 uint32_t aStartOffsetInText) {
805 MOZ_ASSERT(aTextNode.HasFlag(NS_MAYBE_MASKED));
806 MOZ_ASSERT(aStartOffsetInString == 0 || aStartOffsetInText == 0);
808 uint32_t unmaskStart = UINT32_MAX, unmaskLength = 0;
809 TextEditor* textEditor =
810 nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(&aTextNode);
811 if (textEditor && textEditor->UnmaskedLength() > 0) {
812 unmaskStart = textEditor->UnmaskedStart();
813 unmaskLength = textEditor->UnmaskedLength();
814 // If text is copied from after unmasked range, we can treat this case
815 // as mask all.
816 if (aStartOffsetInText >= unmaskStart + unmaskLength) {
817 unmaskLength = 0;
818 unmaskStart = UINT32_MAX;
819 } else {
820 // If text is copied from middle of unmasked range, reduce the length
821 // and adjust start offset.
822 if (aStartOffsetInText > unmaskStart) {
823 unmaskLength = unmaskStart + unmaskLength - aStartOffsetInText;
824 unmaskStart = 0;
826 // If text is copied from before start of unmasked range, just adjust
827 // the start offset.
828 else {
829 unmaskStart -= aStartOffsetInText;
831 // Make the range is in the string.
832 unmaskStart += aStartOffsetInString;
836 const char16_t kPasswordMask = TextEditor::PasswordMask();
837 for (uint32_t i = aStartOffsetInString; i < aString.Length(); ++i) {
838 bool isSurrogatePair = NS_IS_HIGH_SURROGATE(aString.CharAt(i)) &&
839 i < aString.Length() - 1 &&
840 NS_IS_LOW_SURROGATE(aString.CharAt(i + 1));
841 if (i < unmaskStart || i >= unmaskStart + unmaskLength) {
842 if (isSurrogatePair) {
843 aString.SetCharAt(kPasswordMask, i);
844 aString.SetCharAt(kPasswordMask, i + 1);
845 } else {
846 aString.SetCharAt(kPasswordMask, i);
850 // Skip the following low surrogate.
851 if (isSurrogatePair) {
852 ++i;
857 nsresult TextEditor::SetUnmaskRangeInternal(uint32_t aStart, uint32_t aLength,
858 uint32_t aTimeout, bool aNotify,
859 bool aForceStartMasking) {
860 if (mPasswordMaskData) {
861 mPasswordMaskData->mIsMaskingPassword = aForceStartMasking || aTimeout != 0;
863 // We cannot manage multiple unmasked ranges so that shrink the previous
864 // range first.
865 if (!IsAllMasked()) {
866 mPasswordMaskData->mUnmaskedLength = 0;
867 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
871 // If we're not a password editor, return error since this call does not
872 // make sense.
873 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) {
874 mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes);
875 return NS_ERROR_NOT_AVAILABLE;
878 Element* rootElement = GetRoot();
879 if (NS_WARN_IF(!rootElement)) {
880 return NS_ERROR_NOT_INITIALIZED;
882 Text* text = Text::FromNodeOrNull(rootElement->GetFirstChild());
883 if (!text || !text->Length()) {
884 // There is no anonymous text node in the editor.
885 return aStart > 0 && aStart != UINT32_MAX ? NS_ERROR_INVALID_ARG : NS_OK;
888 if (aStart < UINT32_MAX) {
889 uint32_t valueLength = text->Length();
890 if (aStart >= valueLength) {
891 return NS_ERROR_INVALID_ARG; // There is no character can be masked.
893 // If aStart is middle of a surrogate pair, expand it to include the
894 // preceding high surrogate because the caller may want to show a
895 // character before the character at `aStart + 1`.
896 const nsTextFragment* textFragment = text->GetText();
897 if (textFragment->IsLowSurrogateFollowingHighSurrogateAt(aStart)) {
898 mPasswordMaskData->mUnmaskedStart = aStart - 1;
899 // If caller collapses the range, keep it. Otherwise, expand the length.
900 if (aLength > 0) {
901 ++aLength;
903 } else {
904 mPasswordMaskData->mUnmaskedStart = aStart;
906 mPasswordMaskData->mUnmaskedLength =
907 std::min(valueLength - UnmaskedStart(), aLength);
908 // If unmasked end is middle of a surrogate pair, expand it to include
909 // the following low surrogate because the caller may want to show a
910 // character after the character at `aStart + aLength`.
911 if (UnmaskedEnd() < valueLength &&
912 textFragment->IsLowSurrogateFollowingHighSurrogateAt(UnmaskedEnd())) {
913 mPasswordMaskData->mUnmaskedLength++;
915 // If it's first time to mask the unmasking characters with timer, create
916 // the timer now. Then, we'll keep using it for saving the creation cost.
917 if (!HasAutoMaskingTimer() && aLength && aTimeout && UnmaskedLength()) {
918 mPasswordMaskData->mTimer = NS_NewTimer();
920 } else {
921 if (NS_WARN_IF(aLength != 0)) {
922 return NS_ERROR_INVALID_ARG;
924 mPasswordMaskData->MaskAll();
927 // Notify nsTextFrame of this update if the caller wants this to do it.
928 // Only in this case, script may run.
929 if (aNotify) {
930 MOZ_ASSERT(IsEditActionDataAvailable());
932 RefPtr<Document> document = GetDocument();
933 if (NS_WARN_IF(!document)) {
934 return NS_ERROR_NOT_INITIALIZED;
936 // Notify nsTextFrame of masking range change.
937 if (RefPtr<PresShell> presShell = document->GetObservingPresShell()) {
938 nsAutoScriptBlocker blockRunningScript;
939 uint32_t valueLength = text->Length();
940 CharacterDataChangeInfo changeInfo = {false, 0, valueLength, valueLength,
941 nullptr};
942 presShell->CharacterDataChanged(text, changeInfo);
945 // Scroll caret into the view since masking or unmasking character may
946 // move caret to outside of the view.
947 nsresult rv = ScrollSelectionFocusIntoView();
948 if (NS_FAILED(rv)) {
949 NS_WARNING("EditorBase::ScrollSelectionFocusIntoView() failed");
950 return rv;
954 if (!IsAllMasked() && aTimeout != 0) {
955 // Initialize the timer to mask the range automatically.
956 MOZ_ASSERT(HasAutoMaskingTimer());
957 DebugOnly<nsresult> rvIgnored = mPasswordMaskData->mTimer->InitWithCallback(
958 this, aTimeout, nsITimer::TYPE_ONE_SHOT);
959 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
960 "nsITimer::InitWithCallback() failed, but ignored");
963 return NS_OK;
966 // static
967 char16_t TextEditor::PasswordMask() {
968 char16_t ret = LookAndFeel::GetPasswordCharacter();
969 if (!ret) {
970 ret = '*';
972 return ret;
975 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TextEditor::Notify(nsITimer* aTimer) {
976 // Check whether our text editor's password flag was changed before this
977 // "hide password character" timer actually fires.
978 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) {
979 return NS_OK;
982 if (IsAllMasked()) {
983 return NS_OK;
986 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
987 if (NS_WARN_IF(!editActionData.CanHandle())) {
988 return NS_ERROR_NOT_INITIALIZED;
991 // Mask all characters.
992 nsresult rv = MaskAllCharactersAndNotify();
993 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
994 "TextEditor::MaskAllCharactersAndNotify() failed");
996 if (StaticPrefs::editor_password_testing_mask_delay()) {
997 if (RefPtr<Element> target = GetInputEventTargetElement()) {
998 RefPtr<Document> document = target->OwnerDoc();
999 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchTrustedEvent(
1000 document, target, u"MozLastInputMasked"_ns, CanBubble::eYes,
1001 Cancelable::eNo);
1002 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1003 "nsContentUtils::DispatchTrustedEvent("
1004 "MozLastInputMasked) failed, but ignored");
1008 return EditorBase::ToGenericNSResult(rv);
1011 NS_IMETHODIMP TextEditor::GetName(nsACString& aName) {
1012 aName.AssignLiteral("TextEditor");
1013 return NS_OK;
1016 void TextEditor::WillDeleteText(uint32_t aCurrentLength,
1017 uint32_t aRemoveStartOffset,
1018 uint32_t aRemoveLength) {
1019 MOZ_ASSERT(IsEditActionDataAvailable());
1021 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) {
1022 return;
1025 // Adjust unmasked range before deletion since DOM mutation may cause
1026 // layout referring the range in old text.
1028 // If we need to mask automatically, mask all now.
1029 if (IsMaskingPassword()) {
1030 DebugOnly<nsresult> rvIgnored = MaskAllCharacters();
1031 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1032 "TextEditor::MaskAllCharacters() failed, but ignored");
1033 return;
1036 if (aRemoveStartOffset < UnmaskedStart()) {
1037 // If removing range is before the unmasked range, move it.
1038 if (aRemoveStartOffset + aRemoveLength <= UnmaskedStart()) {
1039 DebugOnly<nsresult> rvIgnored =
1040 SetUnmaskRange(UnmaskedStart() - aRemoveLength, UnmaskedLength());
1041 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1042 "TextEditor::SetUnmaskRange() failed, but ignored");
1043 return;
1046 // If removing range starts before unmasked range, and ends in unmasked
1047 // range, move and shrink the range.
1048 if (aRemoveStartOffset + aRemoveLength < UnmaskedEnd()) {
1049 uint32_t unmaskedLengthInRemovingRange =
1050 aRemoveStartOffset + aRemoveLength - UnmaskedStart();
1051 DebugOnly<nsresult> rvIgnored = SetUnmaskRange(
1052 aRemoveStartOffset, UnmaskedLength() - unmaskedLengthInRemovingRange);
1053 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1054 "TextEditor::SetUnmaskRange() failed, but ignored");
1055 return;
1058 // If removing range includes all unmasked range, collapse it to the
1059 // remove offset.
1060 DebugOnly<nsresult> rvIgnored = SetUnmaskRange(aRemoveStartOffset, 0);
1061 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1062 "TextEditor::SetUnmaskRange() failed, but ignored");
1063 return;
1066 if (aRemoveStartOffset < UnmaskedEnd()) {
1067 // If removing range is in unmasked range, shrink the range.
1068 if (aRemoveStartOffset + aRemoveLength <= UnmaskedEnd()) {
1069 DebugOnly<nsresult> rvIgnored =
1070 SetUnmaskRange(UnmaskedStart(), UnmaskedLength() - aRemoveLength);
1071 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1072 "TextEditor::SetUnmaskRange() failed, but ignored");
1073 return;
1076 // If removing range starts from unmasked range, and ends after it,
1077 // shrink it.
1078 DebugOnly<nsresult> rvIgnored =
1079 SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset - UnmaskedStart());
1080 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1081 "TextEditor::SetUnmaskRange() failed, but ignored");
1082 return;
1085 // If removing range is after the unmasked range, keep it.
1088 nsresult TextEditor::DidInsertText(uint32_t aNewLength,
1089 uint32_t aInsertedOffset,
1090 uint32_t aInsertedLength) {
1091 MOZ_ASSERT(IsEditActionDataAvailable());
1093 if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) {
1094 return NS_OK;
1097 if (IsMaskingPassword()) {
1098 // If we need to mask password, mask all right now.
1099 nsresult rv = MaskAllCharactersAndNotify();
1100 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1101 "TextEditor::MaskAllCharacters() failed");
1102 return rv;
1105 if (aInsertedOffset < UnmaskedStart()) {
1106 // If insertion point is before unmasked range, expand the unmasked range
1107 // to include the new text.
1108 nsresult rv = SetUnmaskRangeAndNotify(
1109 aInsertedOffset, UnmaskedEnd() + aInsertedLength - aInsertedOffset);
1110 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1111 "TextEditor::SetUnmaskRangeAndNotify() failed");
1112 return rv;
1115 if (aInsertedOffset <= UnmaskedEnd()) {
1116 // If insertion point is in unmasked range, unmask new text.
1117 nsresult rv = SetUnmaskRangeAndNotify(UnmaskedStart(),
1118 UnmaskedLength() + aInsertedLength);
1119 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1120 "TextEditor::SetUnmaskRangeAndNotify() failed");
1121 return rv;
1124 // If insertion point is after unmasked range, extend the unmask range to
1125 // include the new text.
1126 nsresult rv = SetUnmaskRangeAndNotify(
1127 UnmaskedStart(), aInsertedOffset + aInsertedLength - UnmaskedStart());
1128 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1129 "TextEditor::SetUnmaskRangeAndNotify() failed");
1130 return rv;
1133 } // namespace mozilla