Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / EditorBase.cpp
blobb11d1ed0e642107d32dc39f3fef70225aa9bcce1
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 "EditorBase.h"
8 #include <stdio.h> // for nullptr, stdout
9 #include <string.h> // for strcmp
11 #include "AutoRangeArray.h" // for AutoRangeArray
12 #include "ChangeAttributeTransaction.h"
13 #include "CompositionTransaction.h"
14 #include "DeleteContentTransactionBase.h"
15 #include "DeleteMultipleRangesTransaction.h"
16 #include "DeleteNodeTransaction.h"
17 #include "DeleteRangeTransaction.h"
18 #include "DeleteTextTransaction.h"
19 #include "EditAction.h" // for EditSubAction
20 #include "EditorDOMPoint.h" // for EditorDOMPoint
21 #include "EditorUtils.h" // for various helper classes.
22 #include "EditTransactionBase.h" // for EditTransactionBase
23 #include "EditorEventListener.h" // for EditorEventListener
24 #include "HTMLEditor.h" // for HTMLEditor
25 #include "HTMLEditorInlines.h"
26 #include "HTMLEditUtils.h" // for HTMLEditUtils
27 #include "InsertNodeTransaction.h" // for InsertNodeTransaction
28 #include "InsertTextTransaction.h" // for InsertTextTransaction
29 #include "JoinNodesTransaction.h" // for JoinNodesTransaction
30 #include "PlaceholderTransaction.h" // for PlaceholderTransaction
31 #include "SplitNodeTransaction.h" // for SplitNodeTransaction
32 #include "TextEditor.h" // for TextEditor
34 #include "ErrorList.h"
35 #include "gfxFontUtils.h" // for gfxFontUtils
36 #include "mozilla/Assertions.h"
37 #include "mozilla/AsyncEventDispatcher.h"
38 #include "mozilla/intl/BidiEmbeddingLevel.h"
39 #include "mozilla/BasePrincipal.h" // for BasePrincipal
40 #include "mozilla/CheckedInt.h" // for CheckedInt
41 #include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
42 #include "mozilla/ContentEvents.h" // for InternalClipboardEvent
43 #include "mozilla/DebugOnly.h" // for DebugOnly
44 #include "mozilla/EditorSpellCheck.h" // for EditorSpellCheck
45 #include "mozilla/Encoding.h" // for Encoding (used in Document::GetDocumentCharacterSet)
46 #include "mozilla/EventDispatcher.h" // for EventChainPreVisitor, etc.
47 #include "mozilla/FlushType.h" // for FlushType::Frames
48 #include "mozilla/IMEContentObserver.h" // for IMEContentObserver
49 #include "mozilla/IMEStateManager.h" // for IMEStateManager
50 #include "mozilla/InputEventOptions.h" // for InputEventOptions
51 #include "mozilla/IntegerRange.h" // for IntegerRange
52 #include "mozilla/InternalMutationEvent.h" // for NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
53 #include "mozilla/mozalloc.h" // for operator new, etc.
54 #include "mozilla/mozInlineSpellChecker.h" // for mozInlineSpellChecker
55 #include "mozilla/mozSpellChecker.h" // for mozSpellChecker
56 #include "mozilla/Preferences.h" // for Preferences
57 #include "mozilla/PresShell.h" // for PresShell
58 #include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary
59 #include "mozilla/Services.h" // for GetObserverService
60 #include "mozilla/StaticPrefs_bidi.h" // for StaticPrefs::bidi_*
61 #include "mozilla/StaticPrefs_dom.h" // for StaticPrefs::dom_*
62 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
63 #include "mozilla/StaticPrefs_layout.h" // for StaticPrefs::layout_*
64 #include "mozilla/TextComposition.h" // for TextComposition
65 #include "mozilla/TextControlElement.h" // for TextControlElement
66 #include "mozilla/TextInputListener.h" // for TextInputListener
67 #include "mozilla/TextServicesDocument.h" // for TextServicesDocument
68 #include "mozilla/TextEvents.h"
69 #include "mozilla/TransactionManager.h" // for TransactionManager
70 #include "mozilla/dom/AbstractRange.h" // for AbstractRange
71 #include "mozilla/dom/Attr.h" // for Attr
72 #include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
73 #include "mozilla/dom/CharacterData.h" // for CharacterData
74 #include "mozilla/dom/DataTransfer.h" // for DataTransfer
75 #include "mozilla/dom/Document.h" // for Document
76 #include "mozilla/dom/DocumentInlines.h" // for GetObservingPresShell
77 #include "mozilla/dom/DragEvent.h" // for DragEvent
78 #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
79 #include "mozilla/dom/EventTarget.h" // for EventTarget
80 #include "mozilla/dom/HTMLBodyElement.h"
81 #include "mozilla/dom/HTMLBRElement.h"
82 #include "mozilla/dom/Selection.h" // for Selection, etc.
83 #include "mozilla/dom/StaticRange.h" // for StaticRange
84 #include "mozilla/dom/Text.h"
85 #include "mozilla/dom/Event.h"
86 #include "nsAString.h" // for nsAString::Length, etc.
87 #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker
88 #include "nsCaret.h" // for nsCaret
89 #include "nsCaseTreatment.h"
90 #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc.
91 #include "nsContentUtils.h" // for nsContentUtils
92 #include "nsCopySupport.h" // for nsCopySupport
93 #include "nsDOMString.h" // for DOMStringIsNull
94 #include "nsDebug.h" // for NS_WARNING, etc.
95 #include "nsError.h" // for NS_OK, etc.
96 #include "nsFocusManager.h" // for nsFocusManager
97 #include "nsFrameSelection.h" // for nsFrameSelection
98 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement
99 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir
100 #include "nsIClipboard.h" // for nsIClipboard
101 #include "nsIContent.h" // for nsIContent
102 #include "nsIContentInlines.h" // for nsINode::IsInDesignMode()
103 #include "nsIDocumentEncoder.h" // for nsIDocumentEncoder
104 #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener
105 #include "nsIDocShell.h" // for nsIDocShell
106 #include "nsIEditActionListener.h" // for nsIEditActionListener
107 #include "nsIFrame.h" // for nsIFrame
108 #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc.
109 #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc.
110 #include "nsINode.h" // for nsINode, etc.
111 #include "nsISelectionController.h" // for nsISelectionController, etc.
112 #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc.
113 #include "nsISupports.h" // for nsISupports
114 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF
115 #include "nsITransferable.h" // for nsITransferable
116 #include "nsIWeakReference.h" // for nsISupportsWeakReference
117 #include "nsIWidget.h" // for nsIWidget, IMEState, etc.
118 #include "nsPIDOMWindow.h" // for nsPIDOMWindow
119 #include "nsPresContext.h" // for nsPresContext
120 #include "nsRange.h" // for nsRange
121 #include "nsReadableUtils.h" // for EmptyString, ToNewCString
122 #include "nsString.h" // for nsAutoString, nsString, etc.
123 #include "nsStringFwd.h" // for nsString
124 #include "nsStyleConsts.h" // for StyleDirection::Rtl, etc.
125 #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc.
126 #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc.
127 #include "nsTextNode.h" // for nsTextNode
128 #include "nsThreadUtils.h" // for nsRunnable
129 #include "prtime.h" // for PR_Now
131 class nsIOutputStream;
132 class nsITransferable;
134 namespace mozilla {
136 using namespace dom;
137 using namespace widget;
139 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
140 using LeafNodeType = HTMLEditUtils::LeafNodeType;
141 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
142 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
144 /*****************************************************************************
145 * mozilla::EditorBase
146 *****************************************************************************/
147 template EditorDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const;
148 template EditorRawDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const;
149 template EditorDOMPoint EditorBase::GetLastIMESelectionEndPoint() const;
150 template EditorRawDOMPoint EditorBase::GetLastIMESelectionEndPoint() const;
152 template Result<CreateContentResult, nsresult>
153 EditorBase::InsertNodeWithTransaction(nsIContent& aContentToInsert,
154 const EditorDOMPoint& aPointToInsert);
155 template Result<CreateElementResult, nsresult>
156 EditorBase::InsertNodeWithTransaction(Element& aContentToInsert,
157 const EditorDOMPoint& aPointToInsert);
158 template Result<CreateTextResult, nsresult>
159 EditorBase::InsertNodeWithTransaction(Text& aContentToInsert,
160 const EditorDOMPoint& aPointToInsert);
162 template EditorDOMPoint EditorBase::GetFirstSelectionStartPoint() const;
163 template EditorRawDOMPoint EditorBase::GetFirstSelectionStartPoint() const;
164 template EditorDOMPoint EditorBase::GetFirstSelectionEndPoint() const;
165 template EditorRawDOMPoint EditorBase::GetFirstSelectionEndPoint() const;
167 template EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager(
168 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount,
169 const EditorDOMPoint& aPointAtCaret);
170 template EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager(
171 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount,
172 const EditorRawDOMPoint& aPointAtCaret);
174 EditorBase::EditorBase(EditorType aEditorType)
175 : mEditActionData(nullptr),
176 mPlaceholderName(nullptr),
177 mModCount(0),
178 mFlags(0),
179 mUpdateCount(0),
180 mPlaceholderBatch(0),
181 mNewlineHandling(StaticPrefs::editor_singleLine_pasteNewlines()),
182 mCaretStyle(StaticPrefs::layout_selection_caret_style()),
183 mDocDirtyState(-1),
184 mSpellcheckCheckboxState(eTriUnset),
185 mInitSucceeded(false),
186 mAllowsTransactionsToChangeSelection(true),
187 mDidPreDestroy(false),
188 mDidPostCreate(false),
189 mDispatchInputEvent(true),
190 mIsInEditSubAction(false),
191 mHidingCaret(false),
192 mSpellCheckerDictionaryUpdated(true),
193 mIsHTMLEditorClass(aEditorType == EditorType::HTML) {
194 #ifdef XP_WIN
195 if (!mCaretStyle && !IsTextEditor()) {
196 // Wordpad-like caret behavior.
197 mCaretStyle = 1;
199 #endif // #ifdef XP_WIN
200 if (mNewlineHandling < nsIEditor::eNewlinesPasteIntact ||
201 mNewlineHandling > nsIEditor::eNewlinesStripSurroundingWhitespace) {
202 mNewlineHandling = nsIEditor::eNewlinesPasteToFirst;
206 EditorBase::~EditorBase() {
207 MOZ_ASSERT(!IsInitialized() || mDidPreDestroy,
208 "Why PreDestroy hasn't been called?");
210 if (mComposition) {
211 mComposition->OnEditorDestroyed();
212 mComposition = nullptr;
214 // If this editor is still hiding the caret, we need to restore it.
215 HideCaret(false);
216 mTransactionManager = nullptr;
219 NS_IMPL_CYCLE_COLLECTION_CLASS(EditorBase)
221 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase)
222 // Remove event listeners first since EditorEventListener may need
223 // mDocument, mEventTarget, etc.
224 if (tmp->mEventListener) {
225 tmp->mEventListener->Disconnect();
226 tmp->mEventListener = nullptr;
229 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
230 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionController)
231 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
232 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMEContentObserver)
233 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker)
234 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextServicesDocument)
235 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextInputListener)
236 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransactionManager)
237 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners)
238 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners)
239 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget)
240 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderTransaction)
241 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)
242 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
243 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
245 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditorBase)
246 Document* currentDoc =
247 tmp->mRootElement ? tmp->mRootElement->GetUncomposedDoc() : nullptr;
248 if (currentDoc && nsCCUncollectableMarker::InGeneration(
249 cb, currentDoc->GetMarkedCCGeneration())) {
250 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
252 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement)
253 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionController)
254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
256 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker)
257 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextServicesDocument)
258 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextInputListener)
259 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransactionManager)
260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners)
261 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners)
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget)
263 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderTransaction)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder)
266 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
268 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditorBase)
269 NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
270 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
271 NS_INTERFACE_MAP_ENTRY(nsIEditor)
272 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor)
273 NS_INTERFACE_MAP_END
275 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorBase)
276 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorBase)
278 nsresult EditorBase::InitInternal(Document& aDocument, Element* aRootElement,
279 nsISelectionController& aSelectionController,
280 uint32_t aFlags) {
281 MOZ_ASSERT_IF(
282 !mEditActionData ||
283 !mEditActionData->HasEditorDestroyedDuringHandlingEditAction(),
284 GetTopLevelEditSubAction() == EditSubAction::eNone);
286 // First only set flags, but other stuff shouldn't be initialized now.
287 // Note that SetFlags() will be called by PostCreate().
288 mFlags = aFlags;
290 mDocument = &aDocument;
291 // nsISelectionController should be stored only when we're a `TextEditor`.
292 // Otherwise, in `HTMLEditor`, it's `PresShell`, and grabbing it causes
293 // a circular reference and memory leak.
294 // XXX Should we move `mSelectionController to `TextEditor`?
295 MOZ_ASSERT_IF(!IsTextEditor(), &aSelectionController == GetPresShell());
296 if (IsTextEditor()) {
297 MOZ_ASSERT(&aSelectionController != GetPresShell());
298 mSelectionController = &aSelectionController;
301 if (mEditActionData) {
302 // During edit action, selection is cached. But this selection is invalid
303 // now since selection controller is updated, so we have to update this
304 // cache.
305 Selection* selection = aSelectionController.GetSelection(
306 nsISelectionController::SELECTION_NORMAL);
307 NS_WARNING_ASSERTION(selection,
308 "SelectionController::GetSelection() failed");
309 if (selection) {
310 mEditActionData->UpdateSelectionCache(*selection);
314 // set up root element if we are passed one.
315 if (aRootElement) {
316 mRootElement = aRootElement;
319 // If this is an editor for <input> or <textarea>, the text node which
320 // has composition string is always recreated with same content. Therefore,
321 // we need to nodify mComposition of text node destruction and replacing
322 // composing string when this receives eCompositionChange event next time.
323 if (mComposition && mComposition->GetContainerTextNode() &&
324 !mComposition->GetContainerTextNode()->IsInComposedDoc()) {
325 mComposition->OnTextNodeRemoved();
328 // Show the caret.
329 DebugOnly<nsresult> rvIgnored = aSelectionController.SetCaretReadOnly(false);
330 NS_WARNING_ASSERTION(
331 NS_SUCCEEDED(rvIgnored),
332 "nsISelectionController::SetCaretReadOnly(false) failed, but ignored");
333 // Show all the selection reflected to user.
334 rvIgnored =
335 aSelectionController.SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
337 "nsISelectionController::SetSelectionFlags("
338 "nsISelectionDisplay::DISPLAY_ALL) failed, but ignored");
340 // Make sure that the editor will be destroyed properly
341 mDidPreDestroy = false;
342 // Make sure that the editor will be created properly
343 mDidPostCreate = false;
345 MOZ_ASSERT(IsBeingInitialized());
347 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
348 if (NS_WARN_IF(!editActionData.CanHandle())) {
349 return NS_ERROR_FAILURE;
352 SelectionRef().AddSelectionListener(this);
354 return NS_OK;
357 nsresult EditorBase::EnsureEmptyTextFirstChild() {
358 MOZ_ASSERT(IsTextEditor());
359 RefPtr<Element> root = GetRoot();
360 nsIContent* firstChild = root->GetFirstChild();
362 if (!firstChild || !firstChild->IsText()) {
363 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns);
364 if (!newTextNode) {
365 NS_WARNING("EditorBase::CreateTextNode() failed");
366 return NS_ERROR_UNEXPECTED;
368 IgnoredErrorResult ignoredError;
369 root->InsertChildBefore(newTextNode, root->GetFirstChild(), true,
370 ignoredError);
371 MOZ_ASSERT(!ignoredError.Failed());
374 return NS_OK;
377 nsresult EditorBase::PostCreateInternal() {
378 MOZ_ASSERT(IsEditActionDataAvailable());
380 // Synchronize some stuff for the flags. SetFlags() will initialize
381 // something by the flag difference. This is first time of that, so, all
382 // initializations must be run. For such reason, we need to invert mFlags
383 // value first.
384 mFlags = ~mFlags;
385 nsresult rv = SetFlags(~mFlags);
386 if (NS_FAILED(rv)) {
387 NS_WARNING("EditorBase::SetFlags() failed");
388 return EditorBase::ToGenericNSResult(rv);
391 // These operations only need to happen on the first PostCreate call
392 if (!mDidPostCreate) {
393 mDidPostCreate = true;
395 // Set up listeners
396 CreateEventListeners();
397 nsresult rv = InstallEventListeners();
398 if (NS_FAILED(rv)) {
399 NS_WARNING("EditorBase::InstallEventListeners() failed");
400 return EditorBase::ToGenericNSResult(rv);
403 // nuke the modification count, so the doc appears unmodified
404 // do this before we notify listeners
405 DebugOnly<nsresult> rvIgnored = ResetModificationCount();
406 NS_WARNING_ASSERTION(
407 NS_SUCCEEDED(rvIgnored),
408 "EditorBase::ResetModificationCount() failed, but ignored");
410 // update the UI with our state
411 rvIgnored = NotifyDocumentListeners(eDocumentCreated);
412 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
413 "EditorBase::NotifyDocumentListeners(eDocumentCreated)"
414 " failed, but ignored");
415 rvIgnored = NotifyDocumentListeners(eDocumentStateChanged);
416 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
417 "EditorBase::NotifyDocumentListeners("
418 "eDocumentStateChanged) failed, but ignored");
421 // update nsTextStateManager and caret if we have focus
422 if (RefPtr<Element> focusedElement = GetFocusedElement()) {
423 DebugOnly<nsresult> rvIgnored = InitializeSelection(*focusedElement);
424 NS_WARNING_ASSERTION(
425 NS_SUCCEEDED(rvIgnored),
426 "EditorBase::InitializeSelection() failed, but ignored");
428 // If the text control gets reframed during focus, Focus() would not be
429 // called, so take a chance here to see if we need to spell check the text
430 // control.
431 nsresult rv = FlushPendingSpellCheck();
432 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
433 NS_WARNING(
434 "EditorBase::FlushPendingSpellCheck() caused destroying the editor");
435 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
437 NS_WARNING_ASSERTION(
438 NS_SUCCEEDED(rv),
439 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
441 IMEState newState;
442 rv = GetPreferredIMEState(&newState);
443 if (NS_FAILED(rv)) {
444 NS_WARNING("EditorBase::GetPreferredIMEState() failed");
445 return NS_OK;
447 IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
450 // FYI: This call might cause destroying this editor.
451 IMEStateManager::OnEditorInitialized(*this);
453 return NS_OK;
456 void EditorBase::SetTextInputListener(TextInputListener* aTextInputListener) {
457 MOZ_ASSERT(!mTextInputListener || !aTextInputListener ||
458 mTextInputListener == aTextInputListener);
459 mTextInputListener = aTextInputListener;
462 void EditorBase::SetIMEContentObserver(
463 IMEContentObserver* aIMEContentObserver) {
464 MOZ_ASSERT(!mIMEContentObserver || !aIMEContentObserver ||
465 mIMEContentObserver == aIMEContentObserver);
466 mIMEContentObserver = aIMEContentObserver;
469 void EditorBase::CreateEventListeners() {
470 // Don't create the handler twice
471 if (!mEventListener) {
472 mEventListener = new EditorEventListener();
476 nsresult EditorBase::InstallEventListeners() {
477 // FIXME InstallEventListeners() should not be called if we failed to set
478 // document or create an event listener. So, these checks should be
479 // MOZ_DIAGNOSTIC_ASSERT instead.
480 MOZ_ASSERT(GetDocument());
481 if (MOZ_UNLIKELY(!GetDocument()) || NS_WARN_IF(!mEventListener)) {
482 return NS_ERROR_NOT_INITIALIZED;
485 // Initialize the event target.
486 mEventTarget = GetExposedRoot();
487 if (NS_WARN_IF(!mEventTarget)) {
488 return NS_ERROR_NOT_AVAILABLE;
491 nsresult rv = mEventListener->Connect(this);
492 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
493 "EditorEventListener::Connect() failed");
494 if (mComposition) {
495 // If mComposition has already been destroyed, we should forget it.
496 // This may happen if it ended while we don't listen to composition
497 // events.
498 if (mComposition->Destroyed()) {
499 // XXX We may need to fix existing composition transaction here.
500 // However, this may be called when it's not safe.
501 // Perhaps, we should stop handling composition with events.
502 mComposition = nullptr;
504 // Otherwise, Restart to handle composition with new editor contents.
505 else {
506 mComposition->StartHandlingComposition(this);
509 return rv;
512 void EditorBase::RemoveEventListeners() {
513 if (!mEventListener) {
514 return;
516 mEventListener->Disconnect();
517 if (mComposition) {
518 // Even if this is called, don't release mComposition because this is
519 // may be reused after reframing.
520 mComposition->EndHandlingComposition(this);
522 mEventTarget = nullptr;
525 bool EditorBase::IsListeningToEvents() const {
526 return mEventListener && !mEventListener->DetachedFromEditor();
529 bool EditorBase::GetDesiredSpellCheckState() {
530 // Check user override on this element
531 if (mSpellcheckCheckboxState != eTriUnset) {
532 return (mSpellcheckCheckboxState == eTriTrue);
535 // Check user preferences
536 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
538 if (!spellcheckLevel) {
539 return false; // Spellchecking forced off globally
542 if (!CanEnableSpellCheck()) {
543 return false;
546 PresShell* presShell = GetPresShell();
547 if (presShell) {
548 nsPresContext* context = presShell->GetPresContext();
549 if (context && !context->IsDynamic()) {
550 return false;
554 // Check DOM state
555 nsCOMPtr<nsIContent> content = GetExposedRoot();
556 if (!content) {
557 return false;
560 auto element = nsGenericHTMLElement::FromNode(content);
561 if (!element) {
562 return false;
565 // XXX I'm not sure whether we don't use this path when we're a plaintext mail
566 // composer.
567 if (IsHTMLEditor() && !AsHTMLEditor()->IsPlaintextMailComposer()) {
568 // Some of the page content might be editable and some not, if spellcheck=
569 // is explicitly set anywhere, so if there's anything editable on the page,
570 // return true and let the spellchecker figure it out.
571 Document* doc = content->GetComposedDoc();
572 return doc && doc->IsEditingOn();
575 return element->Spellcheck();
578 void EditorBase::PreDestroyInternal() {
579 MOZ_ASSERT(!mDidPreDestroy);
581 mInitSucceeded = false;
583 Selection* selection = GetSelection();
584 if (selection) {
585 selection->RemoveSelectionListener(this);
588 IMEStateManager::OnEditorDestroying(*this);
590 // Let spellchecker clean up its observers etc. It is important not to
591 // actually free the spellchecker here, since the spellchecker could have
592 // caused flush notifications, which could have gotten here if a textbox
593 // is being removed. Setting the spellchecker to nullptr could free the
594 // object that is still in use! It will be freed when the editor is
595 // destroyed.
596 if (mInlineSpellChecker) {
597 DebugOnly<nsresult> rvIgnored =
598 mInlineSpellChecker->Cleanup(IsTextEditor());
599 NS_WARNING_ASSERTION(
600 NS_SUCCEEDED(rvIgnored),
601 "mozInlineSpellChecker::Cleanup() failed, but ignored");
604 // tell our listeners that the doc is going away
605 DebugOnly<nsresult> rvIgnored =
606 NotifyDocumentListeners(eDocumentToBeDestroyed);
607 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
608 "EditorBase::NotifyDocumentListeners("
609 "eDocumentToBeDestroyed) failed, but ignored");
611 // Unregister event listeners
612 RemoveEventListeners();
613 // If this editor is still hiding the caret, we need to restore it.
614 HideCaret(false);
615 mActionListeners.Clear();
616 mDocStateListeners.Clear();
617 mInlineSpellChecker = nullptr;
618 mTextServicesDocument = nullptr;
619 mTextInputListener = nullptr;
620 mSpellcheckCheckboxState = eTriUnset;
621 mRootElement = nullptr;
623 // Transaction may grab this instance. Therefore, they should be released
624 // here for stopping the circular reference with this instance.
625 if (mTransactionManager) {
626 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo();
627 NS_WARNING_ASSERTION(disabledUndoRedo,
628 "EditorBase::DisableUndoRedo() failed, but ignored");
629 mTransactionManager = nullptr;
632 if (mEditActionData) {
633 mEditActionData->OnEditorDestroy();
636 mDidPreDestroy = true;
639 NS_IMETHODIMP EditorBase::GetFlags(uint32_t* aFlags) {
640 // NOTE: If you need to override this method, you need to make Flags()
641 // virtual.
642 *aFlags = Flags();
643 return NS_OK;
646 NS_IMETHODIMP EditorBase::SetFlags(uint32_t aFlags) {
647 if (mFlags == aFlags) {
648 return NS_OK;
651 // If we're a `TextEditor` instance, it's always a plaintext editor.
652 // Therefore, `eEditorPlaintextMask` is not necessary and should not be set
653 // for the performance reason.
654 MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorPlaintextMask));
655 // If we're an `HTMLEditor` instance, we cannot treat it as a single line
656 // editor. So, eEditorSingleLineMask is available only when we're a
657 // `TextEditor` instance.
658 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorSingleLineMask));
659 // If we're an `HTMLEditor` instance, we cannot treat it as a password editor.
660 // So, eEditorPasswordMask is available only when we're a `TextEditor`
661 // instance.
662 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorPasswordMask));
663 // eEditorAllowInteraction changes the behavior of `HTMLEditor`. So, it's
664 // not available with `TextEditor` instance.
665 MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorAllowInteraction));
667 const bool isCalledByPostCreate = (mFlags == ~aFlags);
668 // We don't support dynamic password flag change.
669 MOZ_ASSERT_IF(!isCalledByPostCreate,
670 !((mFlags ^ aFlags) & nsIEditor::eEditorPasswordMask));
671 bool spellcheckerWasEnabled = !isCalledByPostCreate && CanEnableSpellCheck();
672 mFlags = aFlags;
674 if (!IsInitialized()) {
675 // If we're initializing, we shouldn't do anything now.
676 // SetFlags() will be called by PostCreate(),
677 // we should synchronize some stuff for the flags at that time.
678 return NS_OK;
681 // The flag change may cause the spellchecker state change
682 if (CanEnableSpellCheck() != spellcheckerWasEnabled) {
683 SyncRealTimeSpell();
686 // If this is called from PostCreate(), it will update the IME state if it's
687 // necessary.
688 if (!mDidPostCreate) {
689 return NS_OK;
692 // Might be changing editable state, so, we need to reset current IME state
693 // if we're focused and the flag change causes IME state change.
694 if (RefPtr<Element> focusedElement = GetFocusedElement()) {
695 IMEState newState;
696 nsresult rv = GetPreferredIMEState(&newState);
697 NS_WARNING_ASSERTION(
698 NS_SUCCEEDED(rv),
699 "EditorBase::GetPreferredIMEState() failed, but ignored");
700 if (NS_SUCCEEDED(rv)) {
701 // NOTE: When the enabled state isn't going to be modified, this method
702 // is going to do nothing.
703 IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
707 return NS_OK;
710 NS_IMETHODIMP EditorBase::GetIsSelectionEditable(bool* aIsSelectionEditable) {
711 if (NS_WARN_IF(!aIsSelectionEditable)) {
712 return NS_ERROR_INVALID_ARG;
714 *aIsSelectionEditable = IsSelectionEditable();
715 return NS_OK;
718 bool EditorBase::IsSelectionEditable() {
719 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
720 if (NS_WARN_IF(!editActionData.CanHandle())) {
721 return false;
724 if (IsTextEditor()) {
725 // XXX we just check that the anchor node is editable at the moment
726 // we should check that all nodes in the selection are editable
727 const nsINode* anchorNode = SelectionRef().GetAnchorNode();
728 return anchorNode && anchorNode->IsContent() && anchorNode->IsEditable();
731 const nsINode* anchorNode = SelectionRef().GetAnchorNode();
732 const nsINode* focusNode = SelectionRef().GetFocusNode();
733 if (!anchorNode || !focusNode) {
734 return false;
737 // if anchorNode or focusNode is in a native anonymous subtree, HTMLEditor
738 // shouldn't edit content in it.
739 // XXX This must be a bug of Selection API.
740 if (MOZ_UNLIKELY(anchorNode->IsInNativeAnonymousSubtree() ||
741 focusNode->IsInNativeAnonymousSubtree())) {
742 return false;
745 // Per the editing spec as of June 2012: we have to have a selection whose
746 // start and end nodes are editable, and which share an ancestor editing
747 // host. (Bug 766387.)
748 bool isSelectionEditable = SelectionRef().RangeCount() &&
749 anchorNode->IsEditable() &&
750 focusNode->IsEditable();
751 if (!isSelectionEditable) {
752 return false;
755 const nsINode* commonAncestor =
756 SelectionRef().GetAnchorFocusRange()->GetClosestCommonInclusiveAncestor();
757 while (commonAncestor && !commonAncestor->IsEditable()) {
758 commonAncestor = commonAncestor->GetParentNode();
760 // If there is no editable common ancestor, return false.
761 return !!commonAncestor;
764 NS_IMETHODIMP EditorBase::GetIsDocumentEditable(bool* aIsDocumentEditable) {
765 if (NS_WARN_IF(!aIsDocumentEditable)) {
766 return NS_ERROR_INVALID_ARG;
768 RefPtr<Document> document = GetDocument();
769 *aIsDocumentEditable = document && IsModifiable();
770 return NS_OK;
773 NS_IMETHODIMP EditorBase::GetDocument(Document** aDocument) {
774 if (NS_WARN_IF(!aDocument)) {
775 return NS_ERROR_INVALID_ARG;
777 *aDocument = do_AddRef(mDocument).take();
778 return NS_WARN_IF(!*aDocument) ? NS_ERROR_NOT_INITIALIZED : NS_OK;
781 already_AddRefed<nsIWidget> EditorBase::GetWidget() const {
782 nsPresContext* presContext = GetPresContext();
783 if (NS_WARN_IF(!presContext)) {
784 return nullptr;
786 nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
787 return NS_WARN_IF(!widget) ? nullptr : widget.forget();
790 NS_IMETHODIMP EditorBase::GetContentsMIMEType(nsAString& aContentsMIMEType) {
791 aContentsMIMEType = mContentMIMEType;
792 return NS_OK;
795 NS_IMETHODIMP EditorBase::SetContentsMIMEType(
796 const nsAString& aContentsMIMEType) {
797 mContentMIMEType.Assign(aContentsMIMEType);
798 return NS_OK;
801 NS_IMETHODIMP EditorBase::GetSelectionController(
802 nsISelectionController** aSelectionController) {
803 if (NS_WARN_IF(!aSelectionController)) {
804 return NS_ERROR_INVALID_ARG;
806 *aSelectionController = do_AddRef(GetSelectionController()).take();
807 return NS_WARN_IF(!*aSelectionController) ? NS_ERROR_FAILURE : NS_OK;
810 NS_IMETHODIMP EditorBase::DeleteSelection(EDirection aAction,
811 EStripWrappers aStripWrappers) {
812 nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers);
813 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
814 "EditorBase::DeleteSelectionAsAction() failed");
815 return rv;
818 NS_IMETHODIMP EditorBase::GetSelection(Selection** aSelection) {
819 nsresult rv = GetSelection(SelectionType::eNormal, aSelection);
820 NS_WARNING_ASSERTION(
821 NS_SUCCEEDED(rv),
822 "EditorBase::GetSelection(SelectionType::eNormal) failed");
823 return rv;
826 nsresult EditorBase::GetSelection(SelectionType aSelectionType,
827 Selection** aSelection) const {
828 if (NS_WARN_IF(!aSelection)) {
829 return NS_ERROR_INVALID_ARG;
831 if (IsEditActionDataAvailable()) {
832 *aSelection = do_AddRef(&SelectionRef()).take();
833 return NS_OK;
835 nsISelectionController* selectionController = GetSelectionController();
836 if (NS_WARN_IF(!selectionController)) {
837 *aSelection = nullptr;
838 return NS_ERROR_NOT_INITIALIZED;
840 *aSelection = do_AddRef(selectionController->GetSelection(
841 ToRawSelectionType(aSelectionType)))
842 .take();
843 return NS_WARN_IF(!*aSelection) ? NS_ERROR_FAILURE : NS_OK;
846 nsresult EditorBase::DoTransactionInternal(nsITransaction* aTransaction) {
847 MOZ_ASSERT(IsEditActionDataAvailable());
848 MOZ_ASSERT(!ShouldAlreadyHaveHandledBeforeInputEventDispatching(),
849 "beforeinput event hasn't been dispatched yet");
851 if (mPlaceholderBatch && !mPlaceholderTransaction) {
852 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderName);
853 mPlaceholderTransaction = PlaceholderTransaction::Create(
854 *this, *mPlaceholderName, std::move(mSelState));
855 MOZ_ASSERT(mSelState.isNothing());
857 // We will recurse, but will not hit this case in the nested call
858 RefPtr<PlaceholderTransaction> placeholderTransaction =
859 mPlaceholderTransaction;
860 DebugOnly<nsresult> rvIgnored =
861 DoTransactionInternal(placeholderTransaction);
862 NS_WARNING_ASSERTION(
863 NS_SUCCEEDED(rvIgnored),
864 "EditorBase::DoTransactionInternal() failed, but ignored");
866 if (mTransactionManager) {
867 if (nsCOMPtr<nsITransaction> topTransaction =
868 mTransactionManager->PeekUndoStack()) {
869 if (RefPtr<EditTransactionBase> topTransactionBase =
870 topTransaction->GetAsEditTransactionBase()) {
871 if (PlaceholderTransaction* topPlaceholderTransaction =
872 topTransactionBase->GetAsPlaceholderTransaction()) {
873 // there is a placeholder transaction on top of the undo stack. It
874 // is either the one we just created, or an earlier one that we are
875 // now merging into. From here on out remember this placeholder
876 // instead of the one we just created.
877 mPlaceholderTransaction = topPlaceholderTransaction;
884 if (aTransaction) {
885 // XXX: Why are we doing selection specific batching stuff here?
886 // XXX: Most entry points into the editor have auto variables that
887 // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make
888 // XXX: these selection batch calls no-ops.
889 // XXX:
890 // XXX: I suspect that this was placed here to avoid multiple
891 // XXX: selection changed notifications from happening until after
892 // XXX: the transaction was done. I suppose that can still happen
893 // XXX: if an embedding application called DoTransaction() directly
894 // XXX: to pump its own transactions through the system, but in that
895 // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or
896 // XXX: its auto equivalent AutoUpdateViewBatch to ensure that
897 // XXX: selection listeners have access to accurate frame data?
898 // XXX:
899 // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls
900 // XXX: we will need to make sure that they are disabled during
901 // XXX: the init of the editor for text widgets to avoid layout
902 // XXX: re-entry during initial reflow. - kin
904 // get the selection and start a batch change
905 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
907 if (mTransactionManager) {
908 RefPtr<TransactionManager> transactionManager(mTransactionManager);
909 nsresult rv = transactionManager->DoTransaction(aTransaction);
910 if (NS_FAILED(rv)) {
911 NS_WARNING("TransactionManager::DoTransaction() failed");
912 return rv;
914 } else {
915 nsresult rv = aTransaction->DoTransaction();
916 if (NS_FAILED(rv)) {
917 NS_WARNING("nsITransaction::DoTransaction() failed");
918 return rv;
922 DoAfterDoTransaction(aTransaction);
925 return NS_OK;
928 NS_IMETHODIMP EditorBase::EnableUndo(bool aEnable) {
929 // XXX Should we return NS_ERROR_FAILURE if EdnableUndoRedo() or
930 // DisableUndoRedo() returns false?
931 if (aEnable) {
932 DebugOnly<bool> enabledUndoRedo = EnableUndoRedo();
933 NS_WARNING_ASSERTION(enabledUndoRedo,
934 "EditorBase::EnableUndoRedo() failed, but ignored");
935 return NS_OK;
937 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo();
938 NS_WARNING_ASSERTION(disabledUndoRedo,
939 "EditorBase::DisableUndoRedo() failed, but ignored");
940 return NS_OK;
943 NS_IMETHODIMP EditorBase::ClearUndoRedoXPCOM() {
944 if (MOZ_UNLIKELY(!ClearUndoRedo())) {
945 return NS_ERROR_FAILURE; // We're handling a transaction
947 return NS_OK;
950 NS_IMETHODIMP EditorBase::Undo() {
951 nsresult rv = UndoAsAction(1u);
952 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed");
953 return rv;
956 NS_IMETHODIMP EditorBase::UndoAll() {
957 if (!mTransactionManager) {
958 return NS_OK;
960 size_t numberOfUndoItems = mTransactionManager->NumberOfUndoItems();
961 if (!numberOfUndoItems) {
962 return NS_OK; // no transactions
964 nsresult rv = UndoAsAction(numberOfUndoItems);
965 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed");
966 return rv;
969 NS_IMETHODIMP EditorBase::GetUndoRedoEnabled(bool* aIsEnabled) {
970 MOZ_ASSERT(aIsEnabled);
971 *aIsEnabled = IsUndoRedoEnabled();
972 return NS_OK;
975 NS_IMETHODIMP EditorBase::GetCanUndo(bool* aCanUndo) {
976 MOZ_ASSERT(aCanUndo);
977 *aCanUndo = CanUndo();
978 return NS_OK;
981 NS_IMETHODIMP EditorBase::Redo() {
982 nsresult rv = RedoAsAction(1u);
983 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::RedoAsAction() failed");
984 return rv;
987 NS_IMETHODIMP EditorBase::GetCanRedo(bool* aCanRedo) {
988 MOZ_ASSERT(aCanRedo);
989 *aCanRedo = CanRedo();
990 return NS_OK;
993 nsresult EditorBase::UndoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) {
994 if (aCount == 0 || IsReadonly()) {
995 return NS_OK;
998 // If we don't have transaction in the undo stack, we shouldn't notify
999 // anybody of trying to undo since it's not useful notification but we
1000 // need to pay some runtime cost.
1001 if (!CanUndo()) {
1002 return NS_OK;
1005 // If there is composition, we shouldn't allow to undo with committing
1006 // composition since Chrome doesn't allow it and it doesn't make sense
1007 // because committing composition causes one transaction and Undo(1)
1008 // undoes the committing composition.
1009 if (GetComposition()) {
1010 return NS_OK;
1013 AutoEditActionDataSetter editActionData(*this, EditAction::eUndo, aPrincipal);
1014 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1015 if (NS_FAILED(rv)) {
1016 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1017 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1018 return EditorBase::ToGenericNSResult(rv);
1021 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
1023 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1024 if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) {
1025 return NS_ERROR_FAILURE;
1028 rv = NS_OK;
1030 IgnoredErrorResult ignoredError;
1031 AutoEditSubActionNotifier startToHandleEditSubAction(
1032 *this, EditSubAction::eUndo, nsIEditor::eNone, ignoredError);
1033 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1034 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
1036 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1037 "TextEditor::OnStartToHandleTopLevelEditSubAction() "
1038 "failed, but ignored");
1040 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1041 for (uint32_t i = 0; i < aCount; ++i) {
1042 if (NS_FAILED(transactionManager->Undo())) {
1043 NS_WARNING("TransactionManager::Undo() failed");
1044 break;
1046 DoAfterUndoTransaction();
1049 if (IsHTMLEditor()) {
1050 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor();
1054 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1055 return EditorBase::ToGenericNSResult(rv);
1058 nsresult EditorBase::RedoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) {
1059 if (aCount == 0 || IsReadonly()) {
1060 return NS_OK;
1063 // If we don't have transaction in the redo stack, we shouldn't notify
1064 // anybody of trying to redo since it's not useful notification but we
1065 // need to pay some runtime cost.
1066 if (!CanRedo()) {
1067 return NS_OK;
1070 // If there is composition, we shouldn't allow to redo with committing
1071 // composition since Chrome doesn't allow it and it doesn't make sense
1072 // because committing composition causes removing all transactions from
1073 // the redo queue. So, it becomes impossible to redo anything.
1074 if (GetComposition()) {
1075 return NS_OK;
1078 AutoEditActionDataSetter editActionData(*this, EditAction::eRedo, aPrincipal);
1079 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1080 if (NS_FAILED(rv)) {
1081 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1082 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1083 return EditorBase::ToGenericNSResult(rv);
1086 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
1088 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1089 if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) {
1090 return NS_ERROR_FAILURE;
1093 rv = NS_OK;
1095 IgnoredErrorResult ignoredError;
1096 AutoEditSubActionNotifier startToHandleEditSubAction(
1097 *this, EditSubAction::eRedo, nsIEditor::eNone, ignoredError);
1098 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1099 return ignoredError.StealNSResult();
1101 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1102 "TextEditor::OnStartToHandleTopLevelEditSubAction() "
1103 "failed, but ignored");
1105 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1106 for (uint32_t i = 0; i < aCount; ++i) {
1107 if (NS_FAILED(transactionManager->Redo())) {
1108 NS_WARNING("TransactionManager::Redo() failed");
1109 break;
1111 DoAfterRedoTransaction();
1114 if (IsHTMLEditor()) {
1115 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor();
1119 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1120 return EditorBase::ToGenericNSResult(rv);
1123 NS_IMETHODIMP EditorBase::BeginTransaction() {
1124 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown);
1125 if (NS_WARN_IF(!editActionData.CanHandle())) {
1126 return NS_ERROR_FAILURE;
1129 BeginTransactionInternal(__FUNCTION__);
1130 return NS_OK;
1133 void EditorBase::BeginTransactionInternal(const char* aRequesterFuncName) {
1134 BeginUpdateViewBatch(aRequesterFuncName);
1136 if (NS_WARN_IF(!mTransactionManager)) {
1137 return;
1140 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1141 DebugOnly<nsresult> rvIgnored = transactionManager->BeginBatch(nullptr);
1142 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1143 "TransactionManager::BeginBatch() failed, but ignored");
1146 NS_IMETHODIMP EditorBase::EndTransaction() {
1147 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown);
1148 if (NS_WARN_IF(!editActionData.CanHandle())) {
1149 return NS_ERROR_FAILURE;
1152 EndTransactionInternal(__FUNCTION__);
1153 return NS_OK;
1156 void EditorBase::EndTransactionInternal(const char* aRequesterFuncName) {
1157 if (mTransactionManager) {
1158 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1159 DebugOnly<nsresult> rvIgnored = transactionManager->EndBatch(false);
1160 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1161 "TransactionManager::EndBatch() failed, but ignored");
1164 EndUpdateViewBatch(aRequesterFuncName);
1167 void EditorBase::BeginPlaceholderTransaction(nsStaticAtom& aTransactionName,
1168 const char* aRequesterFuncName) {
1169 MOZ_ASSERT(IsEditActionDataAvailable());
1170 MOZ_ASSERT(mPlaceholderBatch >= 0, "negative placeholder batch count!");
1172 if (!mPlaceholderBatch) {
1173 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1174 // time to turn on the batch
1175 BeginUpdateViewBatch(aRequesterFuncName);
1176 mPlaceholderTransaction = nullptr;
1177 mPlaceholderName = &aTransactionName;
1178 mSelState.emplace();
1179 mSelState->SaveSelection(SelectionRef());
1180 // Composition transaction can modify multiple nodes and it merges text
1181 // node for ime into single text node.
1182 // So if current selection is into IME text node, it might be failed
1183 // to restore selection by UndoTransaction.
1184 // So we need update selection by range updater.
1185 if (mPlaceholderName == nsGkAtoms::IMETxnName) {
1186 RangeUpdaterRef().RegisterSelectionState(*mSelState);
1189 mPlaceholderBatch++;
1192 void EditorBase::EndPlaceholderTransaction(
1193 ScrollSelectionIntoView aScrollSelectionIntoView,
1194 const char* aRequesterFuncName) {
1195 MOZ_ASSERT(IsEditActionDataAvailable());
1196 MOZ_ASSERT(mPlaceholderBatch > 0,
1197 "zero or negative placeholder batch count when ending batch!");
1199 if (!(--mPlaceholderBatch)) {
1200 // By making the assumption that no reflow happens during the calls
1201 // to EndUpdateViewBatch and ScrollSelectionFocusIntoView, we are able to
1202 // allow the selection to cache a frame offset which is used by the
1203 // caret drawing code. We only enable this cache here; at other times,
1204 // we have no way to know whether reflow invalidates it
1205 // See bugs 35296 and 199412.
1206 SelectionRef().SetCanCacheFrameOffset(true);
1208 // time to turn off the batch
1209 EndUpdateViewBatch(aRequesterFuncName);
1210 // make sure selection is in view
1212 // After ScrollSelectionFocusIntoView(), the pending notifications might be
1213 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1214 // XXX Even if we're destroyed, we need to keep handling below because
1215 // this method changes a lot of status. We should rewrite this safer.
1216 if (aScrollSelectionIntoView == ScrollSelectionIntoView::Yes) {
1217 DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView();
1218 NS_WARNING_ASSERTION(
1219 NS_SUCCEEDED(rvIgnored),
1220 "EditorBase::ScrollSelectionFocusIntoView() failed, but Ignored");
1223 // cached for frame offset are Not available now
1224 SelectionRef().SetCanCacheFrameOffset(false);
1226 if (mSelState) {
1227 // we saved the selection state, but never got to hand it to placeholder
1228 // (else we ould have nulled out this pointer), so destroy it to prevent
1229 // leaks.
1230 if (mPlaceholderName == nsGkAtoms::IMETxnName) {
1231 RangeUpdaterRef().DropSelectionState(*mSelState);
1233 mSelState.reset();
1235 // We might have never made a placeholder if no action took place.
1236 if (mPlaceholderTransaction) {
1237 // FYI: Disconnect placeholder transaction before dispatching "input"
1238 // event because an input event listener may start other things.
1239 // TODO: We should forget EditActionDataSetter too.
1240 RefPtr<PlaceholderTransaction> placeholderTransaction =
1241 std::move(mPlaceholderTransaction);
1242 DebugOnly<nsresult> rvIgnored =
1243 placeholderTransaction->EndPlaceHolderBatch();
1244 NS_WARNING_ASSERTION(
1245 NS_SUCCEEDED(rvIgnored),
1246 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored");
1247 // notify editor observers of action but if composing, it's done by
1248 // compositionchange event handler.
1249 if (!mComposition) {
1250 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1252 } else {
1253 NotifyEditorObservers(eNotifyEditorObserversOfCancel);
1258 NS_IMETHODIMP EditorBase::GetDocumentIsEmpty(bool* aDocumentIsEmpty) {
1259 MOZ_ASSERT(aDocumentIsEmpty);
1260 *aDocumentIsEmpty = IsEmpty();
1261 return NS_OK;
1264 // XXX: The rule system should tell us which node to select all on (ie, the
1265 // root, or the body)
1266 NS_IMETHODIMP EditorBase::SelectAll() {
1267 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1268 if (NS_WARN_IF(!editActionData.CanHandle())) {
1269 return NS_ERROR_NOT_INITIALIZED;
1272 nsresult rv = SelectAllInternal();
1273 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SelectAllInternal() failed");
1274 // This is low level API for XUL applcation. So, we should return raw
1275 // error code here.
1276 return rv;
1279 nsresult EditorBase::SelectAllInternal() {
1280 MOZ_ASSERT(IsInitialized());
1282 DebugOnly<nsresult> rvIgnored = CommitComposition();
1283 if (NS_WARN_IF(Destroyed())) {
1284 return NS_ERROR_EDITOR_DESTROYED;
1286 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1287 "EditorBase::CommitComposition() failed, but ignored");
1289 // XXX Do we need to keep handling after committing composition causes moving
1290 // focus to different element? Although TextEditor has independent
1291 // selection, so, we may not see any odd behavior even in such case.
1293 nsresult rv = SelectEntireDocument();
1294 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1295 "EditorBase::SelectEntireDocument() failed");
1296 return rv;
1299 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP EditorBase::BeginningOfDocument() {
1300 MOZ_ASSERT(IsTextEditor());
1302 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1303 if (NS_WARN_IF(!editActionData.CanHandle())) {
1304 return NS_ERROR_NOT_INITIALIZED;
1307 // get the root element
1308 RefPtr<Element> rootElement = GetRoot();
1309 if (NS_WARN_IF(!rootElement)) {
1310 return NS_ERROR_NULL_POINTER;
1313 // find first editable thingy
1314 nsCOMPtr<nsIContent> firstEditableLeaf;
1315 // If we're `TextEditor`, the first editable leaf node is a text node or
1316 // padding `<br>` element. In the first case, we need to collapse selection
1317 // into it.
1318 if (rootElement->GetFirstChild() && rootElement->GetFirstChild()->IsText()) {
1319 firstEditableLeaf = rootElement->GetFirstChild();
1321 if (!firstEditableLeaf) {
1322 // just the root node, set selection to inside the root
1323 nsresult rv = CollapseSelectionToStartOf(*rootElement);
1324 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1325 "EditorBase::CollapseSelectionToStartOf() failed");
1326 return rv;
1329 if (firstEditableLeaf->IsText()) {
1330 // If firstEditableLeaf is text, set selection to beginning of the text
1331 // node.
1332 nsresult rv = CollapseSelectionToStartOf(*firstEditableLeaf);
1333 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1334 "EditorBase::CollapseSelectionToStartOf() failed");
1335 return rv;
1338 // Otherwise, it's a leaf node and we set the selection just in front of it.
1339 nsCOMPtr<nsIContent> parent = firstEditableLeaf->GetParent();
1340 if (NS_WARN_IF(!parent)) {
1341 return NS_ERROR_NULL_POINTER;
1344 MOZ_ASSERT(
1345 parent->ComputeIndexOf(firstEditableLeaf).valueOr(UINT32_MAX) == 0,
1346 "How come the first node isn't the left most child in its parent?");
1347 nsresult rv = CollapseSelectionToStartOf(*parent);
1348 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1349 "EditorBase::CollapseSelectionToStartOf() failed");
1350 return rv;
1353 NS_IMETHODIMP EditorBase::EndOfDocument() { return NS_ERROR_NOT_IMPLEMENTED; }
1355 NS_IMETHODIMP EditorBase::GetDocumentModified(bool* aOutDocModified) {
1356 if (NS_WARN_IF(!aOutDocModified)) {
1357 return NS_ERROR_INVALID_ARG;
1360 int32_t modCount = 0;
1361 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount);
1362 NS_WARNING_ASSERTION(
1363 NS_SUCCEEDED(rvIgnored),
1364 "EditorBase::GetModificationCount() failed, but ignored");
1366 *aOutDocModified = (modCount != 0);
1367 return NS_OK;
1370 NS_IMETHODIMP EditorBase::GetDocumentCharacterSet(nsACString& aCharacterSet) {
1371 return NS_ERROR_NOT_AVAILABLE;
1374 nsresult EditorBase::GetDocumentCharsetInternal(nsACString& aCharset) const {
1375 Document* document = GetDocument();
1376 if (NS_WARN_IF(!document)) {
1377 return NS_ERROR_NOT_INITIALIZED;
1379 document->GetDocumentCharacterSet()->Name(aCharset);
1380 return NS_OK;
1383 NS_IMETHODIMP EditorBase::SetDocumentCharacterSet(
1384 const nsACString& aCharacterSet) {
1385 return NS_ERROR_NOT_AVAILABLE;
1388 NS_IMETHODIMP EditorBase::OutputToString(const nsAString& aFormatType,
1389 uint32_t aDocumentEncoderFlags,
1390 nsAString& aOutputString) {
1391 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1392 if (NS_WARN_IF(!editActionData.CanHandle())) {
1393 return NS_ERROR_NOT_INITIALIZED;
1396 nsresult rv =
1397 ComputeValueInternal(aFormatType, aDocumentEncoderFlags, aOutputString);
1398 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1399 "EditorBase::ComputeValueInternal() failed");
1400 // This is low level API for XUL application. So, we should return raw
1401 // error code here.
1402 return rv;
1405 nsresult EditorBase::ComputeValueInternal(const nsAString& aFormatType,
1406 uint32_t aDocumentEncoderFlags,
1407 nsAString& aOutputString) const {
1408 MOZ_ASSERT(IsEditActionDataAvailable());
1410 // First, let's try to get the value simply only from text node if the
1411 // caller wants plaintext value.
1412 if (aFormatType.LowerCaseEqualsLiteral("text/plain") &&
1413 !(aDocumentEncoderFlags & (nsIDocumentEncoder::OutputSelectionOnly |
1414 nsIDocumentEncoder::OutputWrap))) {
1415 // Shortcut for empty editor case.
1416 if (IsEmpty()) {
1417 aOutputString.Truncate();
1418 return NS_OK;
1420 // NOTE: If it's neither <input type="text"> nor <textarea>, e.g., an HTML
1421 // editor which is in plaintext mode (e.g., plaintext email composer on
1422 // Thunderbird), it should be handled by the expensive path.
1423 if (IsTextEditor()) {
1424 // If it's necessary to check selection range or the editor wraps hard,
1425 // we need some complicated handling. In such case, we need to use the
1426 // expensive path.
1427 // XXX Anything else what we cannot return the text node data simply?
1428 Result<EditActionResult, nsresult> result =
1429 AsTextEditor()->ComputeValueFromTextNodeAndBRElement(aOutputString);
1430 if (MOZ_UNLIKELY(result.isErr())) {
1431 NS_WARNING("TextEditor::ComputeValueFromTextNodeAndBRElement() failed");
1432 return result.unwrapErr();
1434 if (!result.inspect().Ignored()) {
1435 return NS_OK;
1440 nsAutoCString charset;
1441 nsresult rv = GetDocumentCharsetInternal(charset);
1442 if (NS_FAILED(rv) || charset.IsEmpty()) {
1443 charset.AssignLiteral("windows-1252"); // XXX Why don't we use "UTF-8"?
1446 nsCOMPtr<nsIDocumentEncoder> encoder =
1447 GetAndInitDocEncoder(aFormatType, aDocumentEncoderFlags, charset);
1448 if (!encoder) {
1449 NS_WARNING("EditorBase::GetAndInitDocEncoder() failed");
1450 return NS_ERROR_FAILURE;
1453 rv = encoder->EncodeToString(aOutputString);
1454 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1455 "nsIDocumentEncoder::EncodeToString() failed");
1456 return rv;
1459 already_AddRefed<nsIDocumentEncoder> EditorBase::GetAndInitDocEncoder(
1460 const nsAString& aFormatType, uint32_t aDocumentEncoderFlags,
1461 const nsACString& aCharset) const {
1462 MOZ_ASSERT(IsEditActionDataAvailable());
1464 nsCOMPtr<nsIDocumentEncoder> docEncoder;
1465 if (!mCachedDocumentEncoder ||
1466 !mCachedDocumentEncoderType.Equals(aFormatType)) {
1467 nsAutoCString formatType;
1468 LossyAppendUTF16toASCII(aFormatType, formatType);
1469 docEncoder = do_createDocumentEncoder(PromiseFlatCString(formatType).get());
1470 if (NS_WARN_IF(!docEncoder)) {
1471 return nullptr;
1473 mCachedDocumentEncoder = docEncoder;
1474 mCachedDocumentEncoderType = aFormatType;
1475 } else {
1476 docEncoder = mCachedDocumentEncoder;
1479 RefPtr<Document> doc = GetDocument();
1480 NS_ASSERTION(doc, "Need a document");
1482 nsresult rv = docEncoder->NativeInit(
1483 doc, aFormatType,
1484 aDocumentEncoderFlags | nsIDocumentEncoder::RequiresReinitAfterOutput);
1485 if (NS_FAILED(rv)) {
1486 NS_WARNING("nsIDocumentEncoder::NativeInit() failed");
1487 return nullptr;
1490 if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) {
1491 DebugOnly<nsresult> rvIgnored = docEncoder->SetCharset(aCharset);
1492 NS_WARNING_ASSERTION(
1493 NS_SUCCEEDED(rvIgnored),
1494 "nsIDocumentEncoder::SetCharset() failed, but ignored");
1497 const int32_t wrapWidth = std::max(WrapWidth(), 0);
1498 DebugOnly<nsresult> rvIgnored = docEncoder->SetWrapColumn(wrapWidth);
1499 NS_WARNING_ASSERTION(
1500 NS_SUCCEEDED(rvIgnored),
1501 "nsIDocumentEncoder::SetWrapColumn() failed, but ignored");
1503 // Set the selection, if appropriate.
1504 // We do this either if the OutputSelectionOnly flag is set,
1505 // in which case we use our existing selection ...
1506 if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) {
1507 if (NS_FAILED(docEncoder->SetSelection(&SelectionRef()))) {
1508 NS_WARNING("nsIDocumentEncoder::SetSelection() failed");
1509 return nullptr;
1512 // ... or if the root element is not a body,
1513 // in which case we set the selection to encompass the root.
1514 else {
1515 Element* rootElement = GetRoot();
1516 if (NS_WARN_IF(!rootElement)) {
1517 return nullptr;
1519 if (!rootElement->IsHTMLElement(nsGkAtoms::body)) {
1520 if (NS_FAILED(docEncoder->SetContainerNode(rootElement))) {
1521 NS_WARNING("nsIDocumentEncoder::SetContainerNode() failed");
1522 return nullptr;
1527 return docEncoder.forget();
1530 bool EditorBase::AreClipboardCommandsUnconditionallyEnabled() const {
1531 Document* document = GetDocument();
1532 return document && document->AreClipboardCommandsUnconditionallyEnabled();
1535 bool EditorBase::CheckForClipboardCommandListener(
1536 nsAtom* aCommand, EventMessage aEventMessage) const {
1537 RefPtr<Document> document = GetDocument();
1538 if (!document) {
1539 return false;
1542 // We exclude XUL and chrome docs here to maintain current behavior where
1543 // in these cases the editor element alone is expected to handle clipboard
1544 // command availability.
1545 if (!document->AreClipboardCommandsUnconditionallyEnabled()) {
1546 return false;
1549 // So in web content documents, "unconditionally" enabled Cut/Copy are not
1550 // really unconditional; they're enabled if there is a listener that wants
1551 // to handle them. What they're not conditional on here is whether there is
1552 // currently a selection in the editor.
1553 RefPtr<PresShell> presShell = document->GetObservingPresShell();
1554 if (!presShell) {
1555 return false;
1557 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
1558 if (!presContext) {
1559 return false;
1562 RefPtr<EventTarget> et = IsHTMLEditor()
1563 ? AsHTMLEditor()->ComputeEditingHost(
1564 HTMLEditor::LimitInBodyElement::No)
1565 : GetDOMEventTarget();
1567 while (et) {
1568 EventListenerManager* elm = et->GetExistingListenerManager();
1569 if (elm && elm->HasListenersFor(aCommand)) {
1570 return true;
1572 InternalClipboardEvent event(true, aEventMessage);
1573 EventChainPreVisitor visitor(presContext, &event, nullptr,
1574 nsEventStatus_eIgnore, false, et);
1575 et->GetEventTargetParent(visitor);
1576 et = visitor.GetParentTarget();
1579 return false;
1582 Result<EditorBase::ClipboardEventResult, nsresult>
1583 EditorBase::DispatchClipboardEventAndUpdateClipboard(EventMessage aEventMessage,
1584 int32_t aClipboardType) {
1585 MOZ_ASSERT(IsEditActionDataAvailable());
1587 const bool isPasting =
1588 aEventMessage == ePaste || aEventMessage == ePasteNoFormatting;
1589 if (isPasting) {
1590 CommitComposition();
1591 if (NS_WARN_IF(Destroyed())) {
1592 return Err(NS_ERROR_EDITOR_DESTROYED);
1596 RefPtr<PresShell> presShell = GetPresShell();
1597 if (NS_WARN_IF(!presShell)) {
1598 return Err(NS_ERROR_NOT_AVAILABLE);
1601 const RefPtr<Selection> sel = [&]() {
1602 if (IsHTMLEditor() && aEventMessage == eCopy &&
1603 SelectionRef().IsCollapsed()) {
1604 // If we don't have a usable selection for copy and we're an HTML
1605 // editor (which is global for the document) try to use the last
1606 // focused selection instead.
1607 return nsCopySupport::GetSelectionForCopy(GetDocument());
1609 return do_AddRef(&SelectionRef());
1610 }();
1612 bool actionTaken = false;
1613 const bool doDefault = nsCopySupport::FireClipboardEvent(
1614 aEventMessage, aClipboardType, presShell, sel, &actionTaken);
1615 NotifyOfDispatchingClipboardEvent();
1617 if (NS_WARN_IF(Destroyed())) {
1618 return Err(NS_ERROR_EDITOR_DESTROYED);
1621 if (doDefault) {
1622 MOZ_ASSERT(actionTaken);
1623 return ClipboardEventResult::DoDefault;
1625 // If we handle a "paste" and nsCopySupport::FireClipboardEvent sets
1626 // actionTaken to "false" means that it's an error. Otherwise, the "paste"
1627 // event is just canceled.
1628 if (isPasting) {
1629 return actionTaken ? ClipboardEventResult::DefaultPreventedOfPaste
1630 : ClipboardEventResult::IgnoredOrError;
1632 // If we handle a "copy", actionTaken is set to true only when
1633 // nsCopySupport::FireClipboardEvent does not meet an error.
1634 // If we handle a "cut", actionTaken is set to true only when
1635 // nsCopySupport::FireClipboardEvent does not meet an error and
1636 // - the selection is collapsed in editable elements when the event is not
1637 // canceled.
1638 // - the event is canceled but update the clipboard with the dataTransfer
1639 // of the event.
1640 return actionTaken ? ClipboardEventResult::CopyOrCutHandled
1641 : ClipboardEventResult::IgnoredOrError;
1644 NS_IMETHODIMP EditorBase::Cut() {
1645 nsresult rv = CutAsAction();
1646 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed");
1647 return rv;
1650 nsresult EditorBase::CutAsAction(nsIPrincipal* aPrincipal) {
1651 AutoEditActionDataSetter editActionData(*this, EditAction::eCut, aPrincipal);
1652 if (NS_WARN_IF(!editActionData.CanHandle())) {
1653 return NS_ERROR_NOT_INITIALIZED;
1657 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
1658 if (NS_WARN_IF(!focusManager)) {
1659 return NS_ERROR_UNEXPECTED;
1661 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();
1663 Result<ClipboardEventResult, nsresult> ret =
1664 DispatchClipboardEventAndUpdateClipboard(
1665 eCut, nsIClipboard::kGlobalClipboard);
1666 if (MOZ_UNLIKELY(ret.isErr())) {
1667 NS_WARNING(
1668 "EditorBase::DispatchClipboardEventAndUpdateClipboard(eCut, "
1669 "nsIClipboard::kGlobalClipboard) failed");
1670 return EditorBase::ToGenericNSResult(ret.unwrapErr());
1672 switch (ret.unwrap()) {
1673 case ClipboardEventResult::DoDefault:
1674 break;
1675 case ClipboardEventResult::CopyOrCutHandled:
1676 return NS_OK;
1677 case ClipboardEventResult::IgnoredOrError:
1678 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1679 case ClipboardEventResult::DefaultPreventedOfPaste:
1680 MOZ_ASSERT_UNREACHABLE("Invalid result for eCut");
1683 // If focus is changed by a "cut" event listener, we should stop handling
1684 // the cut.
1685 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
1686 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
1687 if (focusManager->GetFocusedWindow() != GetWindow()) {
1688 return NS_OK;
1690 RefPtr<EditorBase> editorBase =
1691 nsContentUtils::GetActiveEditor(GetPresContext());
1692 if (!editorBase || (editorBase->IsHTMLEditor() &&
1693 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
1694 return NS_OK;
1696 if (editorBase != this) {
1697 return NS_OK;
1702 // Dispatch "beforeinput" event after dispatching "cut" event.
1703 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
1704 if (NS_FAILED(rv)) {
1705 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1706 "MaybeDispatchBeforeInputEvent() failed");
1707 return EditorBase::ToGenericNSResult(rv);
1709 // XXX This transaction name is referred by PlaceholderTransaction::Merge()
1710 // so that we need to keep using it here.
1711 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName,
1712 ScrollSelectionIntoView::Yes,
1713 __FUNCTION__);
1714 rv = DeleteSelectionAsSubAction(
1715 eNone, IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
1716 NS_WARNING_ASSERTION(
1717 NS_SUCCEEDED(rv),
1718 "EditorBase::DeleteSelectionAsSubAction(eNone) failed, but ignored");
1719 return EditorBase::ToGenericNSResult(rv);
1722 NS_IMETHODIMP EditorBase::CanCut(bool* aCanCut) {
1723 if (NS_WARN_IF(!aCanCut)) {
1724 return NS_ERROR_INVALID_ARG;
1726 *aCanCut = IsCutCommandEnabled();
1727 return NS_OK;
1730 bool EditorBase::IsCutCommandEnabled() const {
1731 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1732 if (NS_WARN_IF(!editActionData.CanHandle())) {
1733 return false;
1736 if (IsModifiable() && IsCopyToClipboardAllowedInternal()) {
1737 return true;
1740 // If there's an event listener for "cut", we always enable the command
1741 // as we don't really know what the listener may want to do in response.
1742 // We look up the event target chain for a possible listener on a parent
1743 // in addition to checking the immediate target.
1744 return CheckForClipboardCommandListener(nsGkAtoms::oncut, eCut);
1747 NS_IMETHODIMP EditorBase::Copy() {
1748 AutoEditActionDataSetter editActionData(*this, EditAction::eCopy);
1749 if (NS_WARN_IF(!editActionData.CanHandle())) {
1750 return NS_ERROR_NOT_INITIALIZED;
1753 Result<ClipboardEventResult, nsresult> ret =
1754 DispatchClipboardEventAndUpdateClipboard(eCopy,
1755 nsIClipboard::kGlobalClipboard);
1756 if (MOZ_UNLIKELY(ret.isErr())) {
1757 NS_WARNING(
1758 "EditorBase::DispatchClipboardEventAndUpdateClipboard(eCopy, "
1759 "nsIClipboard::kGlobalClipboard) failed");
1760 return EditorBase::ToGenericNSResult(ret.unwrapErr());
1762 switch (ret.unwrap()) {
1763 case ClipboardEventResult::DoDefault:
1764 case ClipboardEventResult::CopyOrCutHandled:
1765 return NS_OK;
1766 case ClipboardEventResult::IgnoredOrError:
1767 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1768 case ClipboardEventResult::DefaultPreventedOfPaste:
1769 MOZ_ASSERT_UNREACHABLE("Invalid result for eCopy");
1771 return NS_ERROR_UNEXPECTED;
1774 NS_IMETHODIMP EditorBase::CanCopy(bool* aCanCopy) {
1775 if (NS_WARN_IF(!aCanCopy)) {
1776 return NS_ERROR_INVALID_ARG;
1778 *aCanCopy = IsCopyCommandEnabled();
1779 return NS_OK;
1782 bool EditorBase::IsCopyCommandEnabled() const {
1783 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1784 if (NS_WARN_IF(!editActionData.CanHandle())) {
1785 return false;
1788 if (IsCopyToClipboardAllowedInternal()) {
1789 return true;
1792 // Like "cut", always enable "copy" if there's a listener.
1793 return CheckForClipboardCommandListener(nsGkAtoms::oncopy, eCopy);
1796 NS_IMETHODIMP EditorBase::Paste(int32_t aClipboardType) {
1797 const nsresult rv = PasteAsAction(aClipboardType, DispatchPasteEvent::Yes);
1798 NS_WARNING_ASSERTION(
1799 NS_SUCCEEDED(rv),
1800 "EditorBase::PasteAsAction(DispatchPasteEvent::Yes) failed");
1801 return rv;
1804 nsresult EditorBase::PasteAsAction(int32_t aClipboardType,
1805 DispatchPasteEvent aDispatchPasteEvent,
1806 nsIPrincipal* aPrincipal /* = nullptr */) {
1807 if (IsHTMLEditor() && IsReadonly()) {
1808 return NS_OK;
1811 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
1812 aPrincipal);
1813 if (NS_WARN_IF(!editActionData.CanHandle())) {
1814 return NS_ERROR_NOT_INITIALIZED;
1817 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
1818 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
1819 if (NS_WARN_IF(!focusManager)) {
1820 return NS_ERROR_UNEXPECTED;
1822 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();
1824 Result<ClipboardEventResult, nsresult> ret =
1825 DispatchClipboardEventAndUpdateClipboard(ePaste, aClipboardType);
1826 if (MOZ_UNLIKELY(ret.isErr())) {
1827 NS_WARNING(
1828 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
1829 "failed");
1830 return EditorBase::ToGenericNSResult(ret.unwrapErr());
1832 switch (ret.inspect()) {
1833 case ClipboardEventResult::DoDefault:
1834 break;
1835 case ClipboardEventResult::DefaultPreventedOfPaste:
1836 case ClipboardEventResult::IgnoredOrError:
1837 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1838 case ClipboardEventResult::CopyOrCutHandled:
1839 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
1842 // If focus is changed by a "paste" event listener, we should keep handling
1843 // the "pasting" in new focused editor because Chrome works as so.
1844 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
1845 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
1846 // For the privacy reason, let's top handling it if new focused element is
1847 // in different document.
1848 if (focusManager->GetFocusedWindow() != GetWindow()) {
1849 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1851 RefPtr<EditorBase> editorBase =
1852 nsContentUtils::GetActiveEditor(GetPresContext());
1853 if (!editorBase || (editorBase->IsHTMLEditor() &&
1854 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
1855 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1857 if (editorBase != this) {
1858 nsresult rv = editorBase->PasteAsAction(
1859 aClipboardType, DispatchPasteEvent::No, aPrincipal);
1860 NS_WARNING_ASSERTION(
1861 NS_SUCCEEDED(rv),
1862 "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed");
1863 return EditorBase::ToGenericNSResult(rv);
1866 } else {
1867 // The caller must already have dispatched a "paste" event.
1868 editActionData.NotifyOfDispatchingClipboardEvent();
1871 nsresult rv = HandlePaste(editActionData, aClipboardType);
1872 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::HandlePaste() failed");
1873 return EditorBase::ToGenericNSResult(rv);
1876 nsresult EditorBase::PasteAsQuotationAsAction(
1877 int32_t aClipboardType, DispatchPasteEvent aDispatchPasteEvent,
1878 nsIPrincipal* aPrincipal /* = nullptr */) {
1879 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
1880 aClipboardType == nsIClipboard::kSelectionClipboard);
1882 if (IsHTMLEditor() && IsReadonly()) {
1883 return NS_OK;
1886 AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation,
1887 aPrincipal);
1888 if (NS_WARN_IF(!editActionData.CanHandle())) {
1889 return NS_ERROR_NOT_INITIALIZED;
1892 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
1893 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
1894 if (NS_WARN_IF(!focusManager)) {
1895 return NS_ERROR_UNEXPECTED;
1897 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();
1899 Result<ClipboardEventResult, nsresult> ret =
1900 DispatchClipboardEventAndUpdateClipboard(ePaste, aClipboardType);
1901 if (MOZ_UNLIKELY(ret.isErr())) {
1902 NS_WARNING(
1903 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
1904 "failed");
1905 return EditorBase::ToGenericNSResult(ret.unwrapErr());
1907 switch (ret.inspect()) {
1908 case ClipboardEventResult::DoDefault:
1909 break;
1910 case ClipboardEventResult::DefaultPreventedOfPaste:
1911 case ClipboardEventResult::IgnoredOrError:
1912 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1913 case ClipboardEventResult::CopyOrCutHandled:
1914 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
1917 // If focus is changed by a "paste" event listener, we should keep handling
1918 // the "pasting" in new focused editor because Chrome works as so.
1919 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
1920 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
1921 // For the privacy reason, let's top handling it if new focused element is
1922 // in different document.
1923 if (focusManager->GetFocusedWindow() != GetWindow()) {
1924 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1926 RefPtr<EditorBase> editorBase =
1927 nsContentUtils::GetActiveEditor(GetPresContext());
1928 if (!editorBase || (editorBase->IsHTMLEditor() &&
1929 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
1930 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1932 if (editorBase != this) {
1933 nsresult rv = editorBase->PasteAsQuotationAsAction(
1934 aClipboardType, DispatchPasteEvent::No, aPrincipal);
1935 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1936 "EditorBase::PasteAsQuotationAsAction("
1937 "DispatchPasteEvent::No) failed");
1938 return EditorBase::ToGenericNSResult(rv);
1941 } else {
1942 // The caller must already have dispatched a "paste" event.
1943 editActionData.NotifyOfDispatchingClipboardEvent();
1946 nsresult rv = HandlePasteAsQuotation(editActionData, aClipboardType);
1947 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1948 "EditorBase::HandlePasteAsQuotation() failed");
1949 return EditorBase::ToGenericNSResult(rv);
1952 nsresult EditorBase::PasteTransferableAsAction(
1953 nsITransferable* aTransferable, DispatchPasteEvent aDispatchPasteEvent,
1954 nsIPrincipal* aPrincipal /* = nullptr */) {
1955 // FIXME: This may be called as a call of nsIEditor::PasteTransferable.
1956 // In this case, we should keep handling the paste even in the readonly mode.
1957 if (IsHTMLEditor() && IsReadonly()) {
1958 return NS_OK;
1961 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste,
1962 aPrincipal);
1963 if (NS_WARN_IF(!editActionData.CanHandle())) {
1964 return NS_ERROR_NOT_INITIALIZED;
1967 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) {
1968 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
1969 if (NS_WARN_IF(!focusManager)) {
1970 return NS_ERROR_UNEXPECTED;
1972 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement();
1974 // Use an invalid value for the clipboard type as data comes from
1975 // aTransferable and we don't currently implement a way to put that in the
1976 // data transfer in TextEditor yet.
1977 Result<ClipboardEventResult, nsresult> ret =
1978 DispatchClipboardEventAndUpdateClipboard(
1979 ePaste, IsTextEditor() ? -1 : nsIClipboard::kGlobalClipboard);
1980 if (MOZ_UNLIKELY(ret.isErr())) {
1981 NS_WARNING(
1982 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) "
1983 "failed");
1984 return EditorBase::ToGenericNSResult(ret.unwrapErr());
1986 switch (ret.inspect()) {
1987 case ClipboardEventResult::DoDefault:
1988 break;
1989 case ClipboardEventResult::DefaultPreventedOfPaste:
1990 case ClipboardEventResult::IgnoredOrError:
1991 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
1992 case ClipboardEventResult::CopyOrCutHandled:
1993 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste");
1996 // If focus is changed by a "paste" event listener, we should keep handling
1997 // the "pasting" in new focused editor because Chrome works as so.
1998 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement();
1999 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) {
2000 // For the privacy reason, let's top handling it if new focused element is
2001 // in different document.
2002 if (focusManager->GetFocusedWindow() != GetWindow()) {
2003 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
2005 RefPtr<EditorBase> editorBase =
2006 nsContentUtils::GetActiveEditor(GetPresContext());
2007 if (!editorBase || (editorBase->IsHTMLEditor() &&
2008 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) {
2009 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED);
2011 if (editorBase != this) {
2012 nsresult rv = editorBase->PasteTransferableAsAction(
2013 aTransferable, DispatchPasteEvent::No, aPrincipal);
2014 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2015 "EditorBase::PasteTransferableAsAction("
2016 "DispatchPasteEvent::No) failed");
2017 return EditorBase::ToGenericNSResult(rv);
2020 } else {
2021 // The caller must already have dispatched a "paste" event.
2022 editActionData.NotifyOfDispatchingClipboardEvent();
2025 if (NS_WARN_IF(!aTransferable)) {
2026 return NS_ERROR_INVALID_ARG;
2029 nsresult rv = HandlePasteTransferable(editActionData, *aTransferable);
2030 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2031 "EditorBase::HandlePasteTransferable() failed");
2032 return EditorBase::ToGenericNSResult(rv);
2035 nsresult EditorBase::PrepareToInsertContent(
2036 const EditorDOMPoint& aPointToInsert,
2037 DeleteSelectedContent aDeleteSelectedContent) {
2038 // TODO: Move this method to `EditorBase`.
2039 MOZ_ASSERT(IsEditActionDataAvailable());
2041 MOZ_ASSERT(aPointToInsert.IsSet());
2043 EditorDOMPoint pointToInsert(aPointToInsert);
2044 if (aDeleteSelectedContent == DeleteSelectedContent::Yes) {
2045 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
2046 nsresult rv = DeleteSelectionAsSubAction(
2047 nsIEditor::eNone,
2048 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
2049 if (NS_FAILED(rv)) {
2050 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed");
2051 return rv;
2055 nsresult rv = CollapseSelectionTo(pointToInsert);
2056 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2057 "EditorBase::CollapseSelectionTo() failed");
2058 return rv;
2061 nsresult EditorBase::InsertTextAt(
2062 const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert,
2063 DeleteSelectedContent aDeleteSelectedContent) {
2064 MOZ_ASSERT(IsEditActionDataAvailable());
2065 MOZ_ASSERT(aPointToInsert.IsSet());
2067 nsresult rv = PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent);
2068 if (NS_FAILED(rv)) {
2069 NS_WARNING("EditorBase::PrepareToInsertContent() failed");
2070 return rv;
2073 rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
2074 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2075 "EditorBase::InsertTextAsSubAction() failed");
2076 return rv;
2079 EditorBase::SafeToInsertData EditorBase::IsSafeToInsertData(
2080 nsIPrincipal* aSourcePrincipal) const {
2081 // Try to determine whether we should use a sanitizing fragment sink
2082 RefPtr<Document> destdoc = GetDocument();
2083 NS_ASSERTION(destdoc, "Where is our destination doc?");
2085 nsIDocShell* docShell = nullptr;
2086 if (RefPtr<BrowsingContext> bc = destdoc->GetBrowsingContext()) {
2087 RefPtr<BrowsingContext> root = bc->Top();
2088 MOZ_ASSERT(root, "root should not be null");
2090 docShell = root->GetDocShell();
2093 bool isSafe =
2094 docShell && docShell->GetAppType() == nsIDocShell::APP_TYPE_EDITOR;
2096 if (!isSafe && aSourcePrincipal) {
2097 nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
2098 NS_ASSERTION(destPrincipal, "How come we don't have a principal?");
2099 DebugOnly<nsresult> rvIgnored =
2100 aSourcePrincipal->Subsumes(destPrincipal, &isSafe);
2101 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2102 "nsIPrincipal::Subsumes() failed, but ignored");
2105 return isSafe ? SafeToInsertData::Yes : SafeToInsertData::No;
2108 NS_IMETHODIMP EditorBase::PasteTransferable(nsITransferable* aTransferable) {
2109 nsresult rv =
2110 PasteTransferableAsAction(aTransferable, DispatchPasteEvent::Yes);
2111 NS_WARNING_ASSERTION(
2112 NS_SUCCEEDED(rv),
2113 "EditorBase::PasteTransferableAsAction(DispatchPasteEvent::Yes) failed");
2114 return rv;
2117 NS_IMETHODIMP EditorBase::CanPaste(int32_t aClipboardType, bool* aCanPaste) {
2118 if (NS_WARN_IF(!aCanPaste)) {
2119 return NS_ERROR_INVALID_ARG;
2121 *aCanPaste = CanPaste(aClipboardType);
2122 return NS_OK;
2125 NS_IMETHODIMP EditorBase::SetAttribute(Element* aElement,
2126 const nsAString& aAttribute,
2127 const nsAString& aValue) {
2128 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) {
2129 return NS_ERROR_INVALID_ARG;
2132 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
2133 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2134 if (NS_FAILED(rv)) {
2135 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2136 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2137 return EditorBase::ToGenericNSResult(rv);
2140 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
2141 rv = SetAttributeWithTransaction(*aElement, *attribute, aValue);
2142 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2143 "EditorBase::SetAttributeWithTransaction() failed");
2144 return EditorBase::ToGenericNSResult(rv);
2147 nsresult EditorBase::SetAttributeWithTransaction(Element& aElement,
2148 nsAtom& aAttribute,
2149 const nsAString& aValue) {
2150 RefPtr<ChangeAttributeTransaction> transaction =
2151 ChangeAttributeTransaction::Create(aElement, aAttribute, aValue);
2152 nsresult rv = DoTransactionInternal(transaction);
2153 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2154 "EditorBase::DoTransactionInternal() failed");
2155 return rv;
2158 NS_IMETHODIMP EditorBase::RemoveAttribute(Element* aElement,
2159 const nsAString& aAttribute) {
2160 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) {
2161 return NS_ERROR_INVALID_ARG;
2164 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
2165 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2166 if (NS_FAILED(rv)) {
2167 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2168 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2169 return EditorBase::ToGenericNSResult(rv);
2172 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
2173 rv = RemoveAttributeWithTransaction(*aElement, *attribute);
2174 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2175 "EditorBase::RemoveAttributeWithTransaction() failed");
2176 return EditorBase::ToGenericNSResult(rv);
2179 nsresult EditorBase::RemoveAttributeWithTransaction(Element& aElement,
2180 nsAtom& aAttribute) {
2181 if (!aElement.HasAttr(&aAttribute)) {
2182 return NS_OK;
2184 RefPtr<ChangeAttributeTransaction> transaction =
2185 ChangeAttributeTransaction::CreateToRemove(aElement, aAttribute);
2186 nsresult rv = DoTransactionInternal(transaction);
2187 if (NS_WARN_IF(Destroyed())) {
2188 return NS_ERROR_EDITOR_DESTROYED;
2190 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2191 "EditorBase::DoTransactionInternal() failed");
2192 return rv;
2195 nsresult EditorBase::MarkElementDirty(Element& aElement) const {
2196 // Mark the node dirty, but not for webpages (bug 599983)
2197 if (!OutputsMozDirty()) {
2198 return NS_OK;
2200 DebugOnly<nsresult> rvIgnored =
2201 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::mozdirty, u""_ns, false);
2202 NS_WARNING_ASSERTION(
2203 NS_SUCCEEDED(rvIgnored),
2204 "Element::SetAttr(nsGkAtoms::mozdirty) failed, but ignored");
2205 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
2208 NS_IMETHODIMP EditorBase::GetInlineSpellChecker(
2209 bool aAutoCreate, nsIInlineSpellChecker** aInlineSpellChecker) {
2210 if (NS_WARN_IF(!aInlineSpellChecker)) {
2211 return NS_ERROR_INVALID_ARG;
2214 if (mDidPreDestroy) {
2215 // Don't allow people to get or create the spell checker once the editor
2216 // is going away.
2217 *aInlineSpellChecker = nullptr;
2218 return aAutoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK;
2221 // We don't want to show the spell checking UI if there are no spell check
2222 // dictionaries available.
2223 if (!mozInlineSpellChecker::CanEnableInlineSpellChecking()) {
2224 *aInlineSpellChecker = nullptr;
2225 return NS_ERROR_FAILURE;
2228 if (!mInlineSpellChecker && aAutoCreate) {
2229 mInlineSpellChecker = new mozInlineSpellChecker();
2232 if (mInlineSpellChecker) {
2233 nsresult rv = mInlineSpellChecker->Init(this);
2234 if (NS_FAILED(rv)) {
2235 NS_WARNING("mozInlineSpellChecker::Init() failed");
2236 mInlineSpellChecker = nullptr;
2237 return rv;
2241 *aInlineSpellChecker = do_AddRef(mInlineSpellChecker).take();
2242 return NS_OK;
2245 void EditorBase::SyncRealTimeSpell() {
2246 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2247 if (NS_WARN_IF(!editActionData.CanHandle())) {
2248 return;
2251 bool enable = GetDesiredSpellCheckState();
2253 // Initializes mInlineSpellChecker
2254 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
2255 DebugOnly<nsresult> rvIgnored =
2256 GetInlineSpellChecker(enable, getter_AddRefs(spellChecker));
2257 NS_WARNING_ASSERTION(
2258 NS_SUCCEEDED(rvIgnored),
2259 "EditorBase::GetInlineSpellChecker() failed, but ignored");
2261 if (mInlineSpellChecker) {
2262 if (!mSpellCheckerDictionaryUpdated && enable) {
2263 DebugOnly<nsresult> rvIgnored =
2264 mInlineSpellChecker->UpdateCurrentDictionary();
2265 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2266 "mozInlineSpellChecker::UpdateCurrentDictionary() "
2267 "failed, but ignored");
2268 mSpellCheckerDictionaryUpdated = true;
2271 // We might have a mInlineSpellChecker even if there are no dictionaries
2272 // available since we don't destroy the mInlineSpellChecker when the last
2273 // dictionariy is removed, but in that case spellChecker is null
2274 DebugOnly<nsresult> rvIgnored =
2275 mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker);
2276 NS_WARNING_ASSERTION(
2277 NS_SUCCEEDED(rvIgnored),
2278 "mozInlineSpellChecker::SetEnableRealTimeSpell() failed, but ignored");
2282 NS_IMETHODIMP EditorBase::SetSpellcheckUserOverride(bool enable) {
2283 mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse;
2284 SyncRealTimeSpell();
2285 return NS_OK;
2288 NS_IMETHODIMP EditorBase::InsertNode(nsINode* aNodeToInsert,
2289 nsINode* aContainer, uint32_t aOffset,
2290 bool aPreserveSelection,
2291 uint8_t aOptionalArgCount) {
2292 MOZ_DIAGNOSTIC_ASSERT(IsHTMLEditor());
2294 nsCOMPtr<nsIContent> contentToInsert =
2295 nsIContent::FromNodeOrNull(aNodeToInsert);
2296 if (NS_WARN_IF(!contentToInsert) || NS_WARN_IF(!aContainer)) {
2297 return NS_ERROR_NULL_POINTER;
2300 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertNode);
2301 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2302 if (NS_FAILED(rv)) {
2303 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2304 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2305 return EditorBase::ToGenericNSResult(rv);
2308 // Make dispatch `input` event after stopping preserving selection.
2309 AutoPlaceholderBatch treatAsOneTransaction(
2310 *this,
2311 ScrollSelectionIntoView::No, // not a user interaction
2312 __FUNCTION__);
2314 Maybe<AutoTransactionsConserveSelection> preseveSelection;
2315 if (aOptionalArgCount && aPreserveSelection) {
2316 preseveSelection.emplace(*this);
2319 const uint32_t offset = std::min(aOffset, aContainer->Length());
2320 Result<CreateContentResult, nsresult> insertContentResult =
2321 InsertNodeWithTransaction(*contentToInsert,
2322 EditorDOMPoint(aContainer, offset));
2323 if (MOZ_UNLIKELY(insertContentResult.isErr())) {
2324 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
2325 return EditorBase::ToGenericNSResult(insertContentResult.unwrapErr());
2327 rv = insertContentResult.inspect().SuggestCaretPointTo(
2328 *this, {SuggestCaret::OnlyIfHasSuggestion,
2329 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2330 SuggestCaret::AndIgnoreTrivialError});
2331 if (NS_FAILED(rv)) {
2332 NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
2333 return EditorBase::ToGenericNSResult(rv);
2335 NS_WARNING_ASSERTION(
2336 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2337 "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
2338 return NS_OK;
2341 template <typename ContentNodeType>
2342 Result<CreateNodeResultBase<ContentNodeType>, nsresult>
2343 EditorBase::InsertNodeWithTransaction(ContentNodeType& aContentToInsert,
2344 const EditorDOMPoint& aPointToInsert) {
2345 MOZ_ASSERT(IsEditActionDataAvailable());
2346 MOZ_ASSERT_IF(IsTextEditor(), !aContentToInsert.IsText());
2348 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
2349 return Err(NS_ERROR_INVALID_ARG);
2351 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
2353 IgnoredErrorResult ignoredError;
2354 AutoEditSubActionNotifier startToHandleEditSubAction(
2355 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
2356 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2357 return Err(ignoredError.StealNSResult());
2359 NS_WARNING_ASSERTION(
2360 !ignoredError.Failed(),
2361 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2363 RefPtr<InsertNodeTransaction> transaction =
2364 InsertNodeTransaction::Create(*this, aContentToInsert, aPointToInsert);
2365 nsresult rv = DoTransactionInternal(transaction);
2366 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2367 "EditorBase::DoTransactionInternal() failed");
2369 DebugOnly<nsresult> rvIgnored =
2370 RangeUpdaterRef().SelAdjInsertNode(aPointToInsert);
2371 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2372 "RangeUpdater::SelAdjInsertNode() failed, but ignored");
2374 if (NS_WARN_IF(Destroyed())) {
2375 return Err(NS_ERROR_EDITOR_DESTROYED);
2377 if (NS_WARN_IF(aContentToInsert.GetParentNode() !=
2378 aPointToInsert.GetContainer())) {
2379 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2381 if (NS_FAILED(rv)) {
2382 return Err(rv);
2385 if (IsHTMLEditor()) {
2386 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToInsert);
2389 return CreateNodeResultBase<ContentNodeType>(
2390 &aContentToInsert, transaction->SuggestPointToPutCaret<EditorDOMPoint>());
2393 Result<CreateElementResult, nsresult>
2394 EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction(
2395 const EditorDOMPoint& aPointToInsert) {
2396 MOZ_ASSERT(IsEditActionDataAvailable());
2397 MOZ_ASSERT(IsHTMLEditor() || !aPointToInsert.IsInTextNode());
2399 if (MOZ_UNLIKELY(!aPointToInsert.IsSet())) {
2400 return Err(NS_ERROR_FAILURE);
2403 EditorDOMPoint pointToInsert;
2404 if (IsTextEditor()) {
2405 pointToInsert = aPointToInsert;
2406 } else {
2407 Result<EditorDOMPoint, nsresult> maybePointToInsert =
2408 MOZ_KnownLive(AsHTMLEditor())->PrepareToInsertBRElement(aPointToInsert);
2409 if (maybePointToInsert.isErr()) {
2410 return maybePointToInsert.propagateErr();
2412 MOZ_ASSERT(maybePointToInsert.inspect().IsSetAndValid());
2413 pointToInsert = maybePointToInsert.unwrap();
2416 RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
2417 if (NS_WARN_IF(!newBRElement)) {
2418 return Err(NS_ERROR_FAILURE);
2420 newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
2422 Result<CreateElementResult, nsresult> insertBRElementResult =
2423 InsertNodeWithTransaction<Element>(*newBRElement, pointToInsert);
2424 NS_WARNING_ASSERTION(insertBRElementResult.isOk(),
2425 "EditorBase::InsertNodeWithTransaction() failed");
2426 return insertBRElementResult;
2429 NS_IMETHODIMP EditorBase::DeleteNode(nsINode* aNode, bool aPreserveSelection,
2430 uint8_t aOptionalArgCount) {
2431 MOZ_ASSERT_UNREACHABLE("Do not use this API with TextEditor");
2432 return NS_ERROR_NOT_IMPLEMENTED;
2435 nsresult EditorBase::DeleteNodeWithTransaction(nsIContent& aContent) {
2436 MOZ_ASSERT(IsEditActionDataAvailable());
2437 MOZ_ASSERT_IF(IsTextEditor(), !aContent.IsText());
2439 // Do nothing if the node is read-only.
2440 if (IsHTMLEditor() && NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aContent))) {
2441 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2444 IgnoredErrorResult ignoredError;
2445 AutoEditSubActionNotifier startToHandleEditSubAction(
2446 *this, EditSubAction::eDeleteNode, nsIEditor::ePrevious, ignoredError);
2447 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2448 return ignoredError.StealNSResult();
2450 NS_WARNING_ASSERTION(
2451 !ignoredError.Failed(),
2452 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2454 if (IsHTMLEditor()) {
2455 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContent);
2458 // FYI: DeleteNodeTransaction grabs aContent while it's alive. So, it's safe
2459 // to refer aContent even after calling DoTransaction().
2460 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
2461 DeleteNodeTransaction::MaybeCreate(*this, aContent);
2462 NS_WARNING_ASSERTION(deleteNodeTransaction,
2463 "DeleteNodeTransaction::MaybeCreate() failed");
2464 nsresult rv;
2465 if (deleteNodeTransaction) {
2466 rv = DoTransactionInternal(deleteNodeTransaction);
2467 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2468 "EditorBase::DoTransactionInternal() failed");
2470 if (mTextServicesDocument && NS_SUCCEEDED(rv)) {
2471 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
2472 textServicesDocument->DidDeleteContent(aContent);
2474 } else {
2475 rv = NS_ERROR_FAILURE;
2478 if (!mActionListeners.IsEmpty()) {
2479 for (auto& listener : mActionListeners.Clone()) {
2480 DebugOnly<nsresult> rvIgnored = listener->DidDeleteNode(&aContent, rv);
2481 NS_WARNING_ASSERTION(
2482 NS_SUCCEEDED(rvIgnored),
2483 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
2487 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
2490 NS_IMETHODIMP EditorBase::NotifySelectionChanged(Document* aDocument,
2491 Selection* aSelection,
2492 int16_t aReason,
2493 int32_t aAmount) {
2494 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
2495 return NS_ERROR_INVALID_ARG;
2498 if (mTextInputListener) {
2499 RefPtr<TextInputListener> textInputListener = mTextInputListener;
2500 textInputListener->OnSelectionChange(*aSelection, aReason);
2503 if (mIMEContentObserver) {
2504 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2505 observer->OnSelectionChange(*aSelection);
2508 return NS_OK;
2511 void EditorBase::NotifyEditorObservers(
2512 NotificationForEditorObservers aNotification) {
2513 MOZ_ASSERT(IsEditActionDataAvailable());
2515 switch (aNotification) {
2516 case eNotifyEditorObserversOfEnd:
2517 mIsInEditSubAction = false;
2519 if (mEditActionData) {
2520 mEditActionData->MarkAsHandled();
2523 if (mTextInputListener) {
2524 // TODO: TextInputListener::OnEditActionHandled() may return
2525 // NS_ERROR_OUT_OF_MEMORY. If so and if
2526 // TextControlState::SetValue() setting value with us, we should
2527 // return the result to EditorBase::ReplaceTextAsAction(),
2528 // EditorBase::DeleteSelectionAsAction() and
2529 // TextEditor::InsertTextAsAction(). However, it requires a lot
2530 // of changes in editor classes, but it's not so important since
2531 // editor does not use fallible allocation. Therefore, normally,
2532 // the process must be crashed anyway.
2533 RefPtr<TextInputListener> listener = mTextInputListener;
2534 nsresult rv =
2535 listener->OnEditActionHandled(MOZ_KnownLive(*AsTextEditor()));
2536 MOZ_RELEASE_ASSERT(rv != NS_ERROR_OUT_OF_MEMORY,
2537 "Setting value failed due to out of memory");
2538 NS_WARNING_ASSERTION(
2539 NS_SUCCEEDED(rv),
2540 "TextInputListener::OnEditActionHandled() failed, but ignored");
2543 if (mIMEContentObserver) {
2544 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2545 observer->OnEditActionHandled();
2548 if (!mDispatchInputEvent || IsEditActionAborted() ||
2549 IsEditActionCanceled()) {
2550 break;
2553 DispatchInputEvent();
2554 break;
2555 case eNotifyEditorObserversOfBefore:
2556 if (NS_WARN_IF(mIsInEditSubAction)) {
2557 return;
2560 mIsInEditSubAction = true;
2562 if (mIMEContentObserver) {
2563 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2564 observer->BeforeEditAction();
2566 return;
2567 case eNotifyEditorObserversOfCancel:
2568 mIsInEditSubAction = false;
2570 if (mEditActionData) {
2571 mEditActionData->MarkAsHandled();
2574 if (mIMEContentObserver) {
2575 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2576 observer->CancelEditAction();
2578 break;
2579 default:
2580 MOZ_CRASH("Handle all notifications here");
2581 break;
2584 if (IsHTMLEditor() && !Destroyed()) {
2585 // We may need to show resizing handles or update existing ones after
2586 // all transactions are done. This way of doing is preferred to DOM
2587 // mutation events listeners because all the changes the user can apply
2588 // to a document may result in multiple events, some of them quite hard
2589 // to listen too (in particular when an ancestor of the selection is
2590 // changed but the selection itself is not changed).
2591 DebugOnly<nsresult> rvIgnored =
2592 MOZ_KnownLive(AsHTMLEditor())->RefreshEditingUI();
2593 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2594 "HTMLEditor::RefreshEditingUI() failed, but ignored");
2598 void EditorBase::DispatchInputEvent() {
2599 MOZ_ASSERT(IsEditActionDataAvailable());
2600 MOZ_ASSERT(!IsEditActionCanceled(),
2601 "If preceding beforeinput event is canceled, we shouldn't "
2602 "dispatch input event");
2603 MOZ_ASSERT(
2604 !ShouldAlreadyHaveHandledBeforeInputEventDispatching(),
2605 "We've not handled beforeinput event but trying to dispatch input event");
2607 // We don't need to dispatch multiple input events if there is a pending
2608 // input event. However, it may have different event target. If we resolved
2609 // this issue, we need to manage the pending events in an array. But it's
2610 // overwork. We don't need to do it for the very rare case.
2611 // TODO: However, we start to set InputEvent.inputType. So, each "input"
2612 // event now notifies web app each change. So, perhaps, we should
2613 // not omit input events.
2615 RefPtr<Element> targetElement = GetInputEventTargetElement();
2616 if (NS_WARN_IF(!targetElement)) {
2617 return;
2619 RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
2620 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2621 targetElement, eEditorInput, ToInputType(GetEditAction()), this,
2622 dataTransfer ? InputEventOptions(dataTransfer,
2623 InputEventOptions::NeverCancelable::No)
2624 : InputEventOptions(GetInputEventData(),
2625 InputEventOptions::NeverCancelable::No));
2626 NS_WARNING_ASSERTION(
2627 NS_SUCCEEDED(rvIgnored),
2628 "nsContentUtils::DispatchInputEvent() failed, but ignored");
2631 NS_IMETHODIMP EditorBase::AddEditActionListener(
2632 nsIEditActionListener* aListener) {
2633 if (NS_WARN_IF(!aListener)) {
2634 return NS_ERROR_INVALID_ARG;
2637 // If given edit action listener is text services document for the inline
2638 // spell checker, store it as reference of concrete class for performance
2639 // reason.
2640 if (mInlineSpellChecker) {
2641 EditorSpellCheck* editorSpellCheck =
2642 mInlineSpellChecker->GetEditorSpellCheck();
2643 if (editorSpellCheck) {
2644 mozSpellChecker* spellChecker = editorSpellCheck->GetSpellChecker();
2645 if (spellChecker) {
2646 TextServicesDocument* textServicesDocument =
2647 spellChecker->GetTextServicesDocument();
2648 if (static_cast<nsIEditActionListener*>(textServicesDocument) ==
2649 aListener) {
2650 mTextServicesDocument = textServicesDocument;
2651 return NS_OK;
2657 // Make sure the listener isn't already on the list
2658 if (!mActionListeners.Contains(aListener)) {
2659 mActionListeners.AppendElement(*aListener);
2660 NS_WARNING_ASSERTION(
2661 mActionListeners.Length() != 1,
2662 "nsIEditActionListener installed, this editor becomes slower");
2665 return NS_OK;
2668 NS_IMETHODIMP EditorBase::RemoveEditActionListener(
2669 nsIEditActionListener* aListener) {
2670 if (NS_WARN_IF(!aListener)) {
2671 return NS_ERROR_INVALID_ARG;
2674 if (static_cast<nsIEditActionListener*>(mTextServicesDocument) == aListener) {
2675 mTextServicesDocument = nullptr;
2676 return NS_OK;
2679 NS_WARNING_ASSERTION(mActionListeners.Length() != 1,
2680 "All nsIEditActionListeners have been removed, this "
2681 "editor becomes faster");
2682 mActionListeners.RemoveElement(aListener);
2684 return NS_OK;
2687 NS_IMETHODIMP EditorBase::AddDocumentStateListener(
2688 nsIDocumentStateListener* aListener) {
2689 if (NS_WARN_IF(!aListener)) {
2690 return NS_ERROR_INVALID_ARG;
2693 if (!mDocStateListeners.Contains(aListener)) {
2694 mDocStateListeners.AppendElement(*aListener);
2697 return NS_OK;
2700 NS_IMETHODIMP EditorBase::RemoveDocumentStateListener(
2701 nsIDocumentStateListener* aListener) {
2702 if (NS_WARN_IF(!aListener)) {
2703 return NS_ERROR_INVALID_ARG;
2706 mDocStateListeners.RemoveElement(aListener);
2708 return NS_OK;
2711 NS_IMETHODIMP EditorBase::ForceCompositionEnd() {
2712 nsresult rv = CommitComposition();
2713 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2714 "EditorBase::CommitComposition() failed");
2715 return rv;
2718 nsresult EditorBase::CommitComposition() {
2719 nsPresContext* presContext = GetPresContext();
2720 if (NS_WARN_IF(!presContext)) {
2721 return NS_ERROR_NOT_AVAILABLE;
2724 if (!mComposition) {
2725 return NS_OK;
2727 nsresult rv =
2728 IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, presContext);
2729 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "IMEStateManager::NotifyIME() failed");
2730 return rv;
2733 nsresult EditorBase::GetPreferredIMEState(IMEState* aState) {
2734 if (NS_WARN_IF(!aState)) {
2735 return NS_ERROR_INVALID_ARG;
2738 aState->mEnabled = IMEEnabled::Enabled;
2739 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
2741 if (IsReadonly()) {
2742 aState->mEnabled = IMEEnabled::Disabled;
2743 return NS_OK;
2746 Element* rootElement = GetRoot();
2747 if (NS_WARN_IF(!rootElement)) {
2748 return NS_ERROR_FAILURE;
2751 nsIFrame* frameForRootElement = rootElement->GetPrimaryFrame();
2752 if (NS_WARN_IF(!frameForRootElement)) {
2753 return NS_ERROR_FAILURE;
2756 switch (frameForRootElement->StyleUIReset()->mIMEMode) {
2757 case StyleImeMode::Auto:
2758 if (IsPasswordEditor()) {
2759 aState->mEnabled = IMEEnabled::Password;
2761 break;
2762 case StyleImeMode::Disabled:
2763 // we should use password state for |ime-mode: disabled;|.
2764 aState->mEnabled = IMEEnabled::Password;
2765 break;
2766 case StyleImeMode::Active:
2767 aState->mOpen = IMEState::OPEN;
2768 break;
2769 case StyleImeMode::Inactive:
2770 aState->mOpen = IMEState::CLOSED;
2771 break;
2772 case StyleImeMode::Normal:
2773 break;
2776 return NS_OK;
2779 NS_IMETHODIMP EditorBase::GetComposing(bool* aResult) {
2780 if (NS_WARN_IF(!aResult)) {
2781 return NS_ERROR_INVALID_ARG;
2783 *aResult = IsIMEComposing();
2784 return NS_OK;
2787 NS_IMETHODIMP EditorBase::GetRootElement(Element** aRootElement) {
2788 if (NS_WARN_IF(!aRootElement)) {
2789 return NS_ERROR_INVALID_ARG;
2791 *aRootElement = do_AddRef(mRootElement).take();
2792 return NS_WARN_IF(!*aRootElement) ? NS_ERROR_NOT_AVAILABLE : NS_OK;
2795 void EditorBase::OnStartToHandleTopLevelEditSubAction(
2796 EditSubAction aTopLevelEditSubAction,
2797 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
2798 MOZ_ASSERT(IsEditActionDataAvailable());
2799 MOZ_ASSERT(!aRv.Failed());
2800 mEditActionData->SetTopLevelEditSubAction(aTopLevelEditSubAction,
2801 aDirectionOfTopLevelEditSubAction);
2804 nsresult EditorBase::OnEndHandlingTopLevelEditSubAction() {
2805 MOZ_ASSERT(IsEditActionDataAvailable());
2806 mEditActionData->SetTopLevelEditSubAction(EditSubAction::eNone, eNone);
2807 return NS_OK;
2810 void EditorBase::DoInsertText(Text& aText, uint32_t aOffset,
2811 const nsAString& aStringToInsert,
2812 ErrorResult& aRv) {
2813 aText.InsertData(aOffset, aStringToInsert, aRv);
2814 if (NS_WARN_IF(Destroyed())) {
2815 aRv = NS_ERROR_EDITOR_DESTROYED;
2816 return;
2818 if (aRv.Failed()) {
2819 NS_WARNING("Text::InsertData() failed");
2820 return;
2822 if (IsTextEditor() && !aStringToInsert.IsEmpty()) {
2823 aRv = MOZ_KnownLive(AsTextEditor())
2824 ->DidInsertText(aText.TextLength(), aOffset,
2825 aStringToInsert.Length());
2826 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2830 void EditorBase::DoDeleteText(Text& aText, uint32_t aOffset, uint32_t aCount,
2831 ErrorResult& aRv) {
2832 if (IsTextEditor() && aCount > 0) {
2833 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount);
2835 aText.DeleteData(aOffset, aCount, aRv);
2836 if (NS_WARN_IF(Destroyed())) {
2837 aRv = NS_ERROR_EDITOR_DESTROYED;
2838 return;
2840 NS_WARNING_ASSERTION(!aRv.Failed(), "Text::DeleteData() failed");
2843 void EditorBase::DoReplaceText(Text& aText, uint32_t aOffset, uint32_t aCount,
2844 const nsAString& aStringToInsert,
2845 ErrorResult& aRv) {
2846 if (IsTextEditor() && aCount > 0) {
2847 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount);
2849 aText.ReplaceData(aOffset, aCount, aStringToInsert, aRv);
2850 if (NS_WARN_IF(Destroyed())) {
2851 aRv = NS_ERROR_EDITOR_DESTROYED;
2852 return;
2854 if (aRv.Failed()) {
2855 NS_WARNING("Text::ReplaceData() failed");
2856 return;
2858 if (IsTextEditor() && !aStringToInsert.IsEmpty()) {
2859 aRv = MOZ_KnownLive(AsTextEditor())
2860 ->DidInsertText(aText.TextLength(), aOffset,
2861 aStringToInsert.Length());
2862 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2866 void EditorBase::DoSetText(Text& aText, const nsAString& aStringToSet,
2867 ErrorResult& aRv) {
2868 if (IsTextEditor()) {
2869 uint32_t length = aText.TextLength();
2870 if (length > 0) {
2871 AsTextEditor()->WillDeleteText(length, 0, length);
2874 aText.SetData(aStringToSet, aRv);
2875 if (NS_WARN_IF(Destroyed())) {
2876 aRv = NS_ERROR_EDITOR_DESTROYED;
2877 return;
2879 if (aRv.Failed()) {
2880 NS_WARNING("Text::SetData() failed");
2881 return;
2883 if (IsTextEditor() && !aStringToSet.IsEmpty()) {
2884 aRv = MOZ_KnownLive(AsTextEditor())
2885 ->DidInsertText(aText.Length(), 0, aStringToSet.Length());
2886 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2890 nsresult EditorBase::CloneAttributeWithTransaction(nsAtom& aAttribute,
2891 Element& aDestElement,
2892 Element& aSourceElement) {
2893 nsAutoString attrValue;
2894 if (aSourceElement.GetAttr(&aAttribute, attrValue)) {
2895 nsresult rv =
2896 SetAttributeWithTransaction(aDestElement, aAttribute, attrValue);
2897 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2898 "EditorBase::SetAttributeWithTransaction() failed");
2899 return rv;
2901 nsresult rv = RemoveAttributeWithTransaction(aDestElement, aAttribute);
2902 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2903 "EditorBase::RemoveAttributeWithTransaction() failed");
2904 return rv;
2907 NS_IMETHODIMP EditorBase::CloneAttributes(Element* aDestElement,
2908 Element* aSourceElement) {
2909 if (NS_WARN_IF(!aDestElement) || NS_WARN_IF(!aSourceElement)) {
2910 return NS_ERROR_INVALID_ARG;
2913 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
2914 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2915 if (NS_FAILED(rv)) {
2916 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2917 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2918 return EditorBase::ToGenericNSResult(rv);
2921 CloneAttributesWithTransaction(*aDestElement, *aSourceElement);
2923 return NS_OK;
2926 void EditorBase::CloneAttributesWithTransaction(Element& aDestElement,
2927 Element& aSourceElement) {
2928 AutoPlaceholderBatch treatAsOneTransaction(
2929 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2931 // Use transaction system for undo only if destination is already in the
2932 // document
2933 Element* rootElement = GetRoot();
2934 if (NS_WARN_IF(!rootElement)) {
2935 return;
2938 OwningNonNull<Element> destElement(aDestElement);
2939 OwningNonNull<Element> sourceElement(aSourceElement);
2940 bool isDestElementInBody = rootElement->Contains(destElement);
2942 // Clear existing attributes
2943 AutoTArray<OwningNonNull<Attr>, 16> destElementAttributes;
2944 if (nsDOMAttributeMap* attributes = destElement->Attributes()) {
2945 const uint32_t numberOfAttributes = attributes->Length();
2946 destElementAttributes.SetCapacity(numberOfAttributes);
2947 for (const auto i : IntegerRange(numberOfAttributes)) {
2948 if (Attr* attr = attributes->Item(i)) {
2949 destElementAttributes.AppendElement(*attr);
2953 for (const OwningNonNull<Attr>& attr : destElementAttributes) {
2954 if (isDestElementInBody) {
2955 DebugOnly<nsresult> rvIgnored = RemoveAttributeWithTransaction(
2956 destElement, MOZ_KnownLive(*attr->NodeInfo()->NameAtom()));
2957 NS_WARNING_ASSERTION(
2958 NS_SUCCEEDED(rvIgnored),
2959 "EditorBase::RemoveAttributeWithTransaction() failed, but ignored");
2960 } else {
2961 DebugOnly<nsresult> rvIgnored = destElement->UnsetAttr(
2962 kNameSpaceID_None, attr->NodeInfo()->NameAtom(), true);
2963 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2964 "Element::UnsetAttr() failed, but ignored");
2968 // Set just the attributes that the source element has
2969 AutoTArray<OwningNonNull<Attr>, 16> sourceElementAttributes;
2970 if (nsDOMAttributeMap* attributes = sourceElement->Attributes()) {
2971 const uint32_t numberOfAttributes = attributes->Length();
2972 sourceElementAttributes.SetCapacity(numberOfAttributes);
2973 for (const auto i : IntegerRange(numberOfAttributes)) {
2974 if (Attr* attr = attributes->Item(i)) {
2975 sourceElementAttributes.AppendElement(*attr);
2979 for (const OwningNonNull<Attr>& attr : sourceElementAttributes) {
2980 nsAutoString value;
2981 attr->GetValue(value);
2982 if (isDestElementInBody) {
2983 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
2984 destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
2985 false);
2986 NS_WARNING_ASSERTION(
2987 NS_SUCCEEDED(rvIgnored),
2988 "EditorBase::SetAttributeOrEquivalent() failed, but ignored");
2989 } else {
2990 // The element is not inserted in the document yet, we don't want to put
2991 // a transaction on the UndoStack
2992 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
2993 destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
2994 true);
2995 NS_WARNING_ASSERTION(
2996 NS_SUCCEEDED(rvIgnored),
2997 "EditorBase::SetAttributeOrEquivalent() failed, but ignored");
3002 nsresult EditorBase::ScrollSelectionFocusIntoView() const {
3003 nsISelectionController* selectionController = GetSelectionController();
3004 if (!selectionController) {
3005 return NS_OK;
3008 DebugOnly<nsresult> rvIgnored = selectionController->ScrollSelectionIntoView(
3009 nsISelectionController::SELECTION_NORMAL,
3010 nsISelectionController::SELECTION_FOCUS_REGION,
3011 nsISelectionController::SCROLL_OVERFLOW_HIDDEN);
3012 NS_WARNING_ASSERTION(
3013 NS_SUCCEEDED(rvIgnored),
3014 "nsISelectionController::ScrollSelectionIntoView() failed, but ignored");
3015 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
3018 Result<InsertTextResult, nsresult> EditorBase::InsertTextWithTransaction(
3019 Document& aDocument, const nsAString& aStringToInsert,
3020 const EditorDOMPoint& aPointToInsert) {
3021 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
3022 return Err(NS_ERROR_INVALID_ARG);
3025 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
3027 if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
3028 return InsertTextResult();
3031 // In some cases, the node may be the anonymous div element or a padding
3032 // <br> element for empty last line. Let's try to look for better insertion
3033 // point in the nearest text node if there is.
3034 EditorDOMPoint pointToInsert = [&]() {
3035 if (IsTextEditor()) {
3036 return AsTextEditor()->FindBetterInsertionPoint(aPointToInsert);
3038 return aPointToInsert
3039 .GetPointInTextNodeIfPointingAroundTextNode<EditorDOMPoint>();
3040 }();
3042 if (ShouldHandleIMEComposition()) {
3043 if (!pointToInsert.IsInTextNode()) {
3044 // create a text node
3045 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns);
3046 if (NS_WARN_IF(!newTextNode)) {
3047 return Err(NS_ERROR_FAILURE);
3049 // then we insert it into the dom tree
3050 Result<CreateTextResult, nsresult> insertTextNodeResult =
3051 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert);
3052 if (MOZ_UNLIKELY(insertTextNodeResult.isErr())) {
3053 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3054 return insertTextNodeResult.propagateErr();
3056 insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion();
3057 pointToInsert.Set(newTextNode, 0u);
3059 Result<InsertTextResult, nsresult> insertTextResult =
3060 InsertTextIntoTextNodeWithTransaction(aStringToInsert,
3061 pointToInsert.AsInText());
3062 NS_WARNING_ASSERTION(
3063 insertTextResult.isOk(),
3064 "EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
3065 return insertTextResult;
3068 if (pointToInsert.IsInTextNode()) {
3069 // we are inserting text into an existing text node.
3070 Result<InsertTextResult, nsresult> insertTextResult =
3071 InsertTextIntoTextNodeWithTransaction(aStringToInsert,
3072 pointToInsert.AsInText());
3073 NS_WARNING_ASSERTION(
3074 insertTextResult.isOk(),
3075 "EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
3076 return insertTextResult;
3079 // we are inserting text into a non-text node. first we have to create a
3080 // textnode (this also populates it with the text)
3081 RefPtr<nsTextNode> newTextNode = CreateTextNode(aStringToInsert);
3082 if (NS_WARN_IF(!newTextNode)) {
3083 return Err(NS_ERROR_FAILURE);
3085 // then we insert it into the dom tree
3086 Result<CreateTextResult, nsresult> insertTextNodeResult =
3087 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert);
3088 if (MOZ_UNLIKELY(insertTextNodeResult.isErr())) {
3089 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3090 return Err(insertTextNodeResult.unwrapErr());
3092 insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion();
3093 if (NS_WARN_IF(!newTextNode->IsInComposedDoc())) {
3094 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3096 return InsertTextResult(EditorDOMPointInText::AtEndOf(*newTextNode),
3097 EditorDOMPoint::AtEndOf(*newTextNode));
3100 static bool TextFragmentBeginsWithStringAtOffset(
3101 const nsTextFragment& aTextFragment, const uint32_t aOffset,
3102 const nsAString& aString) {
3103 const uint32_t stringLength = aString.Length();
3105 if (aOffset + stringLength > aTextFragment.GetLength()) {
3106 return false;
3109 if (aTextFragment.Is2b()) {
3110 return aString.Equals(aTextFragment.Get2b() + aOffset);
3113 return aString.EqualsLatin1(aTextFragment.Get1b() + aOffset, stringLength);
3116 static std::tuple<EditorDOMPointInText, EditorDOMPointInText>
3117 AdjustTextInsertionRange(const EditorDOMPointInText& aInsertedPoint,
3118 const nsAString& aInsertedString) {
3119 if (TextFragmentBeginsWithStringAtOffset(
3120 aInsertedPoint.ContainerAs<Text>()->TextFragment(),
3121 aInsertedPoint.Offset(), aInsertedString)) {
3122 return {aInsertedPoint,
3123 EditorDOMPointInText(
3124 aInsertedPoint.ContainerAs<Text>(),
3125 aInsertedPoint.Offset() + aInsertedString.Length())};
3128 return {EditorDOMPointInText(aInsertedPoint.ContainerAs<Text>(), 0),
3129 EditorDOMPointInText::AtEndOf(*aInsertedPoint.ContainerAs<Text>())};
3132 std::tuple<EditorDOMPointInText, EditorDOMPointInText>
3133 EditorBase::ComputeInsertedRange(const EditorDOMPointInText& aInsertedPoint,
3134 const nsAString& aInsertedString) const {
3135 MOZ_ASSERT(aInsertedPoint.IsSet());
3137 // The DOM was potentially modified during the transaction. This is possible
3138 // through mutation event listeners. That is, the node could've been removed
3139 // from the doc or otherwise modified.
3140 if (!MayHaveMutationEventListeners(
3141 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED)) {
3142 EditorDOMPointInText endOfInsertion(
3143 aInsertedPoint.ContainerAs<Text>(),
3144 aInsertedPoint.Offset() + aInsertedString.Length());
3145 return {aInsertedPoint, endOfInsertion};
3147 if (aInsertedPoint.ContainerAs<Text>()->IsInComposedDoc()) {
3148 EditorDOMPointInText begin, end;
3149 return AdjustTextInsertionRange(aInsertedPoint, aInsertedString);
3151 return {EditorDOMPointInText(), EditorDOMPointInText()};
3154 Result<InsertTextResult, nsresult>
3155 EditorBase::InsertTextIntoTextNodeWithTransaction(
3156 const nsAString& aStringToInsert,
3157 const EditorDOMPointInText& aPointToInsert) {
3158 MOZ_ASSERT(IsEditActionDataAvailable());
3159 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
3161 RefPtr<EditTransactionBase> transaction;
3162 bool isIMETransaction = false;
3163 if (ShouldHandleIMEComposition()) {
3164 transaction =
3165 CompositionTransaction::Create(*this, aStringToInsert, aPointToInsert);
3166 isIMETransaction = true;
3167 } else {
3168 transaction =
3169 InsertTextTransaction::Create(*this, aStringToInsert, aPointToInsert);
3172 // XXX We may not need these view batches anymore. This is handled at a
3173 // higher level now I believe.
3174 BeginUpdateViewBatch(__FUNCTION__);
3175 nsresult rv = DoTransactionInternal(transaction);
3176 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3177 "EditorBase::DoTransactionInternal() failed");
3178 EndUpdateViewBatch(__FUNCTION__);
3180 // Don't check whether we've been destroyed here because we need to notify
3181 // listeners and observers below even if we've already destroyed.
3183 auto pointToInsert = [&]() -> EditorDOMPointInText {
3184 if (!isIMETransaction) {
3185 return aPointToInsert;
3187 if (NS_WARN_IF(!mComposition->GetContainerTextNode())) {
3188 return aPointToInsert;
3190 return EditorDOMPointInText(
3191 mComposition->GetContainerTextNode(),
3192 std::min(mComposition->XPOffsetInTextNode(),
3193 mComposition->GetContainerTextNode()->TextDataLength()));
3194 }();
3196 EditorDOMPointInText endOfInsertedText(
3197 pointToInsert.ContainerAs<Text>(),
3198 pointToInsert.Offset() + aStringToInsert.Length());
3200 if (IsHTMLEditor()) {
3201 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
3202 if (begin.IsSet() && end.IsSet()) {
3203 TopLevelEditSubActionDataRef().DidInsertText(
3204 *this, begin.To<EditorRawDOMPoint>(), end.To<EditorRawDOMPoint>());
3206 if (isIMETransaction) {
3207 // Let's mark the text node as "modified frequently" if it interact with
3208 // IME since non-ASCII character may be inserted into it in most cases.
3209 pointToInsert.ContainerAs<Text>()->MarkAsMaybeModifiedFrequently();
3211 // XXX Should we update endOfInsertedText here?
3214 // let listeners know what happened
3215 if (!mActionListeners.IsEmpty()) {
3216 for (auto& listener : mActionListeners.Clone()) {
3217 // TODO: might need adaptation because of mutation event listeners called
3218 // during `DoTransactionInternal`.
3219 DebugOnly<nsresult> rvIgnored = listener->DidInsertText(
3220 pointToInsert.ContainerAs<Text>(),
3221 static_cast<int32_t>(pointToInsert.Offset()), aStringToInsert, rv);
3222 NS_WARNING_ASSERTION(
3223 NS_SUCCEEDED(rvIgnored),
3224 "nsIEditActionListener::DidInsertText() failed, but ignored");
3228 // Added some cruft here for bug 43366. Layout was crashing because we left
3229 // an empty text node lying around in the document. So I delete empty text
3230 // nodes caused by IME. I have to mark the IME transaction as "fixed", which
3231 // means that furure IME txns won't merge with it. This is because we don't
3232 // want future IME txns trying to put their text into a node that is no
3233 // longer in the document. This does not break undo/redo, because all these
3234 // txns are wrapped in a parent PlaceHolder txn, and placeholder txns are
3235 // already savvy to having multiple ime txns inside them.
3237 // Delete empty IME text node if there is one
3238 if (IsHTMLEditor() && isIMETransaction && mComposition) {
3239 RefPtr<Text> textNode = mComposition->GetContainerTextNode();
3240 if (textNode && !textNode->Length()) {
3241 rv = DeleteNodeWithTransaction(*textNode);
3242 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3243 "EditorBase::DeleteNodeTransaction() failed");
3244 if (MOZ_LIKELY(!textNode->IsInComposedDoc())) {
3245 mComposition->OnTextNodeRemoved();
3247 static_cast<CompositionTransaction*>(transaction.get())->MarkFixed();
3251 if (NS_WARN_IF(Destroyed())) {
3252 return Err(NS_ERROR_EDITOR_DESTROYED);
3255 InsertTextTransaction* const insertTextTransaction =
3256 transaction->GetAsInsertTextTransaction();
3257 return insertTextTransaction
3258 ? InsertTextResult(std::move(endOfInsertedText),
3259 insertTextTransaction
3260 ->SuggestPointToPutCaret<EditorDOMPoint>())
3261 : InsertTextResult(std::move(endOfInsertedText));
3264 nsresult EditorBase::NotifyDocumentListeners(
3265 TDocumentListenerNotification aNotificationType) {
3266 switch (aNotificationType) {
3267 case eDocumentCreated:
3268 if (IsTextEditor()) {
3269 return NS_OK;
3271 if (RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3272 AsHTMLEditor()->mComposerCommandsUpdater) {
3273 composerCommandsUpdate->OnHTMLEditorCreated();
3275 return NS_OK;
3277 case eDocumentToBeDestroyed: {
3278 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3279 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr;
3280 if (!mDocStateListeners.Length() && !composerCommandsUpdate) {
3281 return NS_OK;
3283 // Needs to store all listeners before notifying ComposerCommandsUpdate
3284 // since notifying it might change mDocStateListeners.
3285 const AutoDocumentStateListenerArray listeners(
3286 mDocStateListeners.Clone());
3287 if (composerCommandsUpdate) {
3288 composerCommandsUpdate->OnBeforeHTMLEditorDestroyed();
3290 for (auto& listener : listeners) {
3291 // MOZ_KnownLive because 'listeners' is guaranteed to
3292 // keep it alive.
3294 // This can go away once
3295 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3296 nsresult rv = MOZ_KnownLive(listener)->NotifyDocumentWillBeDestroyed();
3297 if (NS_FAILED(rv)) {
3298 NS_WARNING(
3299 "nsIDocumentStateListener::NotifyDocumentWillBeDestroyed() "
3300 "failed");
3301 return rv;
3304 return NS_OK;
3306 case eDocumentStateChanged: {
3307 bool docIsDirty;
3308 nsresult rv = GetDocumentModified(&docIsDirty);
3309 if (NS_FAILED(rv)) {
3310 NS_WARNING("EditorBase::GetDocumentModified() failed");
3311 return rv;
3314 if (static_cast<int8_t>(docIsDirty) == mDocDirtyState) {
3315 return NS_OK;
3318 mDocDirtyState = docIsDirty;
3320 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3321 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr;
3322 if (!mDocStateListeners.Length() && !composerCommandsUpdate) {
3323 return NS_OK;
3325 // Needs to store all listeners before notifying ComposerCommandsUpdate
3326 // since notifying it might change mDocStateListeners.
3327 const AutoDocumentStateListenerArray listeners(
3328 mDocStateListeners.Clone());
3329 if (composerCommandsUpdate) {
3330 composerCommandsUpdate->OnHTMLEditorDirtyStateChanged(mDocDirtyState);
3332 for (auto& listener : listeners) {
3333 // MOZ_KnownLive because 'listeners' is guaranteed to
3334 // keep it alive.
3336 // This can go away once
3337 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3338 nsresult rv =
3339 MOZ_KnownLive(listener)->NotifyDocumentStateChanged(mDocDirtyState);
3340 if (NS_FAILED(rv)) {
3341 NS_WARNING(
3342 "nsIDocumentStateListener::NotifyDocumentStateChanged() failed");
3343 return rv;
3346 return NS_OK;
3348 default:
3349 MOZ_ASSERT_UNREACHABLE("Unknown notification");
3350 return NS_ERROR_FAILURE;
3354 nsresult EditorBase::SetTextNodeWithoutTransaction(const nsAString& aString,
3355 Text& aTextNode) {
3356 MOZ_ASSERT(IsEditActionDataAvailable());
3357 MOZ_ASSERT(IsTextEditor());
3358 MOZ_ASSERT(!IsUndoRedoEnabled());
3360 const uint32_t length = aTextNode.Length();
3362 // Let listeners know what's up
3363 if (!mActionListeners.IsEmpty() && length) {
3364 for (auto& listener : mActionListeners.Clone()) {
3365 DebugOnly<nsresult> rvIgnored =
3366 listener->WillDeleteText(MOZ_KnownLive(&aTextNode), 0, length);
3367 if (NS_WARN_IF(Destroyed())) {
3368 NS_WARNING(
3369 "nsIEditActionListener::WillDeleteText() failed, but ignored");
3370 return NS_ERROR_EDITOR_DESTROYED;
3375 // We don't support undo here, so we don't really need all of the transaction
3376 // machinery, therefore we can run our transaction directly, breaking all of
3377 // the rules!
3378 IgnoredErrorResult error;
3379 DoSetText(aTextNode, aString, error);
3380 if (MOZ_UNLIKELY(error.Failed())) {
3381 NS_WARNING("EditorBase::DoSetText() failed");
3382 return error.StealNSResult();
3385 CollapseSelectionTo(EditorRawDOMPoint(&aTextNode, aString.Length()), error);
3386 if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3387 NS_WARNING("EditorBase::CollapseSelection() caused destroying the editor");
3388 return NS_ERROR_EDITOR_DESTROYED;
3390 NS_ASSERTION(!error.Failed(),
3391 "EditorBase::CollapseSelectionTo() failed, but ignored");
3393 RangeUpdaterRef().SelAdjReplaceText(aTextNode, 0, length, aString.Length());
3395 // Let listeners know what happened
3396 if (!mActionListeners.IsEmpty() && !aString.IsEmpty()) {
3397 for (auto& listener : mActionListeners.Clone()) {
3398 DebugOnly<nsresult> rvIgnored =
3399 listener->DidInsertText(&aTextNode, 0, aString, NS_OK);
3400 if (NS_WARN_IF(Destroyed())) {
3401 return NS_ERROR_EDITOR_DESTROYED;
3403 NS_WARNING_ASSERTION(
3404 NS_SUCCEEDED(rvIgnored),
3405 "nsIEditActionListener::DidInsertText() failed, but ignored");
3409 return NS_OK;
3412 Result<CaretPoint, nsresult> EditorBase::DeleteTextWithTransaction(
3413 Text& aTextNode, uint32_t aOffset, uint32_t aLength) {
3414 MOZ_ASSERT(IsEditActionDataAvailable());
3416 RefPtr<DeleteTextTransaction> transaction =
3417 DeleteTextTransaction::MaybeCreate(*this, aTextNode, aOffset, aLength);
3418 if (MOZ_UNLIKELY(!transaction)) {
3419 NS_WARNING("DeleteTextTransaction::MaybeCreate() failed");
3420 return Err(NS_ERROR_FAILURE);
3423 IgnoredErrorResult ignoredError;
3424 AutoEditSubActionNotifier startToHandleEditSubAction(
3425 *this, EditSubAction::eDeleteText, nsIEditor::ePrevious, ignoredError);
3426 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3427 return Err(ignoredError.StealNSResult());
3429 NS_WARNING_ASSERTION(
3430 !ignoredError.Failed(),
3431 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3433 // Let listeners know what's up
3434 if (!mActionListeners.IsEmpty()) {
3435 for (auto& listener : mActionListeners.Clone()) {
3436 DebugOnly<nsresult> rvIgnored =
3437 listener->WillDeleteText(&aTextNode, aOffset, aLength);
3438 NS_WARNING_ASSERTION(
3439 NS_SUCCEEDED(rvIgnored),
3440 "nsIEditActionListener::WillDeleteText() failed, but ignored");
3444 nsresult rv = DoTransactionInternal(transaction);
3445 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3446 "EditorBase::DoTransactionInternal() failed");
3448 if (IsHTMLEditor()) {
3449 TopLevelEditSubActionDataRef().DidDeleteText(
3450 *this, EditorRawDOMPoint(&aTextNode, aOffset));
3453 if (NS_WARN_IF(Destroyed())) {
3454 return Err(NS_ERROR_EDITOR_DESTROYED);
3456 if (NS_FAILED(rv)) {
3457 return Err(rv);
3460 return CaretPoint(transaction->SuggestPointToPutCaret());
3463 bool EditorBase::IsRoot(const nsINode* inNode) const {
3464 if (NS_WARN_IF(!inNode)) {
3465 return false;
3467 nsINode* rootNode = GetRoot();
3468 return inNode == rootNode;
3471 bool EditorBase::IsDescendantOfRoot(const nsINode* inNode) const {
3472 if (NS_WARN_IF(!inNode)) {
3473 return false;
3475 nsIContent* root = GetRoot();
3476 if (NS_WARN_IF(!root)) {
3477 return false;
3480 return inNode->IsInclusiveDescendantOf(root);
3483 NS_IMETHODIMP EditorBase::IncrementModificationCount(int32_t inNumMods) {
3484 uint32_t oldModCount = mModCount;
3486 mModCount += inNumMods;
3488 if ((!oldModCount && mModCount) || (oldModCount && !mModCount)) {
3489 DebugOnly<nsresult> rvIgnored =
3490 NotifyDocumentListeners(eDocumentStateChanged);
3491 NS_WARNING_ASSERTION(
3492 NS_SUCCEEDED(rvIgnored),
3493 "EditorBase::NotifyDocumentListeners() failed, but ignored");
3495 return NS_OK;
3498 NS_IMETHODIMP EditorBase::GetModificationCount(int32_t* aOutModCount) {
3499 if (NS_WARN_IF(!aOutModCount)) {
3500 return NS_ERROR_INVALID_ARG;
3502 *aOutModCount = mModCount;
3503 return NS_OK;
3506 NS_IMETHODIMP EditorBase::ResetModificationCount() {
3507 bool doNotify = (mModCount != 0);
3509 mModCount = 0;
3511 if (!doNotify) {
3512 return NS_OK;
3515 DebugOnly<nsresult> rvIgnored =
3516 NotifyDocumentListeners(eDocumentStateChanged);
3517 NS_WARNING_ASSERTION(
3518 NS_SUCCEEDED(rvIgnored),
3519 "EditorBase::NotifyDocumentListeners() failed, but ignored");
3520 return NS_OK;
3523 template <typename EditorDOMPointType>
3524 EditorDOMPointType EditorBase::GetFirstSelectionStartPoint() const {
3525 MOZ_ASSERT(IsEditActionDataAvailable());
3526 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
3527 return EditorDOMPointType();
3530 const nsRange* range = SelectionRef().GetRangeAt(0);
3531 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) {
3532 return EditorDOMPointType();
3535 return EditorDOMPointType(range->StartRef());
3538 template <typename EditorDOMPointType>
3539 EditorDOMPointType EditorBase::GetFirstSelectionEndPoint() const {
3540 MOZ_ASSERT(IsEditActionDataAvailable());
3541 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
3542 return EditorDOMPointType();
3545 const nsRange* range = SelectionRef().GetRangeAt(0);
3546 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) {
3547 return EditorDOMPointType();
3550 return EditorDOMPointType(range->EndRef());
3553 // static
3554 nsresult EditorBase::GetEndChildNode(const Selection& aSelection,
3555 nsIContent** aEndNode) {
3556 MOZ_ASSERT(aEndNode);
3558 *aEndNode = nullptr;
3560 if (NS_WARN_IF(!aSelection.RangeCount())) {
3561 return NS_ERROR_FAILURE;
3564 const nsRange* range = aSelection.GetRangeAt(0);
3565 if (NS_WARN_IF(!range)) {
3566 return NS_ERROR_FAILURE;
3569 if (NS_WARN_IF(!range->IsPositioned())) {
3570 return NS_ERROR_FAILURE;
3573 NS_IF_ADDREF(*aEndNode = range->GetChildAtEndOffset());
3574 return NS_OK;
3577 nsresult EditorBase::EnsurePaddingBRElementInMultilineEditor() {
3578 MOZ_ASSERT(IsEditActionDataAvailable());
3579 MOZ_ASSERT(IsTextEditor() || AsHTMLEditor()->IsPlaintextMailComposer());
3580 MOZ_ASSERT(!IsSingleLineEditor());
3582 Element* anonymousDivOrBodyElement = GetRoot();
3583 if (NS_WARN_IF(!anonymousDivOrBodyElement)) {
3584 return NS_ERROR_FAILURE;
3587 // Assuming EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() has been
3588 // called first.
3589 // XXX This assumption is wrong. This method may be called alone. Actually,
3590 // we see this warning in mochitest log. So, we should fix this bug
3591 // later.
3592 if (NS_WARN_IF(!anonymousDivOrBodyElement->GetLastChild())) {
3593 return NS_ERROR_FAILURE;
3596 RefPtr<HTMLBRElement> brElement =
3597 HTMLBRElement::FromNode(anonymousDivOrBodyElement->GetLastChild());
3598 if (!brElement) {
3599 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
3600 // in normal cases. However, it may be required for nested edit
3601 // actions which may be caused by legacy mutation event listeners or
3602 // chrome script.
3603 AutoTransactionsConserveSelection dontChangeMySelection(*this);
3604 EditorDOMPoint endOfAnonymousDiv(
3605 EditorDOMPoint::AtEndOf(*anonymousDivOrBodyElement));
3606 Result<CreateElementResult, nsresult> insertPaddingBRElementResult =
3607 InsertPaddingBRElementForEmptyLastLineWithTransaction(
3608 endOfAnonymousDiv);
3609 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) {
3610 NS_WARNING(
3611 "EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
3612 "failed");
3613 return insertPaddingBRElementResult.unwrapErr();
3615 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion();
3616 return NS_OK;
3619 // Check to see if the trailing BR is a former padding <br> element for empty
3620 // editor - this will have stuck around if we previously morphed a trailing
3621 // node into a padding <br> element.
3622 if (!brElement->IsPaddingForEmptyEditor()) {
3623 return NS_OK;
3626 // Morph it back to a padding <br> element for empty last line.
3627 brElement->UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
3628 brElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
3630 return NS_OK;
3633 void EditorBase::BeginUpdateViewBatch(const char* aRequesterFuncName) {
3634 MOZ_ASSERT(IsEditActionDataAvailable());
3635 MOZ_ASSERT(mUpdateCount >= 0, "bad state");
3637 if (!mUpdateCount) {
3638 // Turn off selection updates and notifications.
3639 SelectionRef().StartBatchChanges(aRequesterFuncName);
3642 mUpdateCount++;
3645 void EditorBase::EndUpdateViewBatch(const char* aRequesterFuncName) {
3646 MOZ_ASSERT(IsEditActionDataAvailable());
3647 MOZ_ASSERT(mUpdateCount > 0, "bad state");
3649 if (NS_WARN_IF(mUpdateCount <= 0)) {
3650 mUpdateCount = 0;
3651 return;
3654 if (--mUpdateCount) {
3655 return;
3658 // Turn selection updating and notifications back on.
3659 SelectionRef().EndBatchChanges(aRequesterFuncName);
3662 TextComposition* EditorBase::GetComposition() const { return mComposition; }
3664 template <typename EditorDOMPointType>
3665 EditorDOMPointType EditorBase::GetFirstIMESelectionStartPoint() const {
3666 return mComposition
3667 ? EditorDOMPointType(mComposition->FirstIMESelectionStartRef())
3668 : EditorDOMPointType();
3671 template <typename EditorDOMPointType>
3672 EditorDOMPointType EditorBase::GetLastIMESelectionEndPoint() const {
3673 return mComposition
3674 ? EditorDOMPointType(mComposition->LastIMESelectionEndRef())
3675 : EditorDOMPointType();
3678 bool EditorBase::IsIMEComposing() const {
3679 return mComposition && mComposition->IsComposing();
3682 bool EditorBase::ShouldHandleIMEComposition() const {
3683 // When the editor is being reframed, the old value may be restored with
3684 // InsertText(). In this time, the text should be inserted as not a part
3685 // of the composition.
3686 return mComposition && mDidPostCreate;
3689 bool EditorBase::EnsureComposition(WidgetCompositionEvent& aCompositionEvent) {
3690 if (mComposition) {
3691 return true;
3693 // The compositionstart event must cause creating new TextComposition
3694 // instance at being dispatched by IMEStateManager.
3695 mComposition = IMEStateManager::GetTextCompositionFor(&aCompositionEvent);
3696 if (!mComposition) {
3697 // However, TextComposition may be committed before the composition
3698 // event comes here.
3699 return false;
3701 mComposition->StartHandlingComposition(this);
3702 return true;
3705 nsresult EditorBase::OnCompositionStart(
3706 WidgetCompositionEvent& aCompositionStartEvent) {
3707 if (mComposition) {
3708 NS_WARNING("There was a composition at receiving compositionstart event");
3709 return NS_OK;
3712 // "beforeinput" event shouldn't be fired before "compositionstart".
3713 AutoEditActionDataSetter editActionData(*this, EditAction::eStartComposition);
3714 if (NS_WARN_IF(!editActionData.CanHandle())) {
3715 return NS_ERROR_NOT_INITIALIZED;
3718 EnsureComposition(aCompositionStartEvent);
3719 NS_WARNING_ASSERTION(mComposition, "Failed to get TextComposition instance?");
3720 return NS_OK;
3723 nsresult EditorBase::OnCompositionChange(
3724 WidgetCompositionEvent& aCompositionChangeEvent) {
3725 MOZ_ASSERT(aCompositionChangeEvent.mMessage == eCompositionChange,
3726 "The event should be eCompositionChange");
3728 if (!mComposition) {
3729 NS_WARNING(
3730 "There is no composition, but receiving compositionchange event");
3731 return NS_ERROR_FAILURE;
3734 AutoEditActionDataSetter editActionData(
3735 *this,
3736 // We need to distinguish whether the composition change is followed by
3737 // compositionend or not (i.e., wether IME has already ended the
3738 // composition or still has the composition) because we need to dispatch
3739 // `textInput` event only for the last composition change.
3740 aCompositionChangeEvent.IsFollowedByCompositionEnd()
3741 ? EditAction::eUpdateCompositionToCommit
3742 : EditAction::eUpdateComposition);
3743 if (NS_WARN_IF(!editActionData.CanHandle())) {
3744 return NS_ERROR_NOT_INITIALIZED;
3746 MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid());
3747 editActionData.SetData(aCompositionChangeEvent.mData);
3749 // If we're an `HTMLEditor` and this is second or later composition change,
3750 // we should set target range to the range of composition string.
3751 // Otherwise, set target ranges to selection ranges (will be done by
3752 // editActionData itself before dispatching `beforeinput` event).
3753 if (IsHTMLEditor() && mComposition->GetContainerTextNode()) {
3754 RefPtr<StaticRange> targetRange = StaticRange::Create(
3755 mComposition->GetContainerTextNode(),
3756 mComposition->XPOffsetInTextNode(),
3757 mComposition->GetContainerTextNode(),
3758 mComposition->XPEndOffsetInTextNode(), IgnoreErrors());
3759 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
3760 "StaticRange::Create() failed");
3761 if (targetRange && targetRange->IsPositioned()) {
3762 editActionData.AppendTargetRange(*targetRange);
3766 // TODO: We need to use different EditAction value for beforeinput event
3767 // if the event is followed by "compositionend" because corresponding
3768 // "input" event will be fired from OnCompositionEnd() later with
3769 // different EditAction value.
3770 // TODO: If Input Events Level 2 is enabled, "beforeinput" event may be
3771 // actually canceled if edit action is eDeleteByComposition. In such
3772 // case, we might need to keep selected text, but insert composition
3773 // string before or after the selection. However, the spec is still
3774 // unstable. We should keep handling the composition since other
3775 // parts including widget may not be ready for such complicated
3776 // behavior.
3777 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
3778 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) {
3779 NS_WARNING("MaybeDispatchBeforeInputEvent() failed");
3780 return EditorBase::ToGenericNSResult(rv);
3783 if (!EnsureComposition(aCompositionChangeEvent)) {
3784 NS_WARNING("EditorBase::EnsureComposition() failed");
3785 return NS_OK;
3788 if (NS_WARN_IF(!GetPresShell())) {
3789 return NS_ERROR_NOT_INITIALIZED;
3792 // NOTE: TextComposition should receive selection change notification before
3793 // CompositionChangeEventHandlingMarker notifies TextComposition of the
3794 // end of handling compositionchange event because TextComposition may
3795 // need to ignore selection changes caused by composition. Therefore,
3796 // CompositionChangeEventHandlingMarker must be destroyed after a call
3797 // of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
3798 // NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
3799 // TextComposition of a selection change.
3800 MOZ_ASSERT(
3801 !mPlaceholderBatch,
3802 "UpdateIMEComposition() must be called without place holder batch");
3803 nsString data(aCompositionChangeEvent.mData);
3804 if (IsHTMLEditor()) {
3805 nsContentUtils::PlatformToDOMLineBreaks(data);
3809 // This needs to be destroyed before dispatching "input" event from
3810 // the following call of `NotifyEditorObservers`. Therefore, we need to
3811 // put this in this block rather than outside of this.
3812 const bool wasComposing = mComposition->IsComposing();
3813 TextComposition::CompositionChangeEventHandlingMarker
3814 compositionChangeEventHandlingMarker(mComposition,
3815 &aCompositionChangeEvent);
3816 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::IMETxnName,
3817 ScrollSelectionIntoView::Yes,
3818 __FUNCTION__);
3820 // XXX Why don't we get caret after the DOM mutation?
3821 RefPtr<nsCaret> caret = GetCaret();
3823 MOZ_ASSERT(
3824 mIsInEditSubAction,
3825 "AutoPlaceholderBatch should've notified the observes of before-edit");
3826 // If we're updating composition, we need to ignore normal selection
3827 // which may be updated by the web content.
3828 rv = InsertTextAsSubAction(data, wasComposing ? SelectionHandling::Ignore
3829 : SelectionHandling::Delete);
3830 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3831 "EditorBase::InsertTextAsSubAction() failed");
3833 if (caret) {
3834 caret->SetSelection(&SelectionRef());
3838 // If still composing, we should fire input event via observer.
3839 // Note that if the composition will be committed by the following
3840 // compositionend event, we don't need to notify editor observes of this
3841 // change.
3842 // NOTE: We must notify after the auto batch will be gone.
3843 if (!aCompositionChangeEvent.IsFollowedByCompositionEnd()) {
3844 // If we're a TextEditor, we'll be initialized with a new anonymous subtree,
3845 // which can be caused by reframing from a "input" event listener. At that
3846 // time, we'll move composition from current text node to the new text node
3847 // with using mComposition's data. Therefore, it's important that
3848 // mComposition already has the latest information here.
3849 MOZ_ASSERT_IF(mComposition, mComposition->String() == data);
3850 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
3853 return EditorBase::ToGenericNSResult(rv);
3856 void EditorBase::OnCompositionEnd(
3857 WidgetCompositionEvent& aCompositionEndEvent) {
3858 if (!mComposition) {
3859 NS_WARNING("There is no composition, but receiving compositionend event");
3860 return;
3863 EditAction editAction = aCompositionEndEvent.mData.IsEmpty()
3864 ? EditAction::eCancelComposition
3865 : EditAction::eCommitComposition;
3866 AutoEditActionDataSetter editActionData(*this, editAction);
3867 // If Input Events Level 2 is enabled, EditAction::eCancelComposition is
3868 // mapped to EditorInputType::eDeleteCompositionText and it requires null
3869 // for InputEvent.data. Therefore, only otherwise, we should set data.
3870 if (ToInputType(editAction) != EditorInputType::eDeleteCompositionText) {
3871 MOZ_ASSERT(
3872 ToInputType(editAction) == EditorInputType::eInsertCompositionText ||
3873 ToInputType(editAction) == EditorInputType::eInsertFromComposition);
3874 MOZ_ASSERT(!aCompositionEndEvent.mData.IsVoid());
3875 editActionData.SetData(aCompositionEndEvent.mData);
3878 // commit the IME transaction..we can get at it via the transaction mgr.
3879 // Note that this means IME won't work without an undo stack!
3880 if (mTransactionManager) {
3881 if (nsCOMPtr<nsITransaction> transaction =
3882 mTransactionManager->PeekUndoStack()) {
3883 if (RefPtr<EditTransactionBase> transactionBase =
3884 transaction->GetAsEditTransactionBase()) {
3885 if (PlaceholderTransaction* placeholderTransaction =
3886 transactionBase->GetAsPlaceholderTransaction()) {
3887 placeholderTransaction->Commit();
3893 // Note that this just marks as that we've already handled "beforeinput" for
3894 // preventing assertions in FireInputEvent(). Note that corresponding
3895 // "beforeinput" event for the following "input" event should've already
3896 // been dispatched from `OnCompositionChange()`.
3897 DebugOnly<nsresult> rvIgnored =
3898 editActionData.MaybeDispatchBeforeInputEvent();
3899 MOZ_ASSERT(rvIgnored != NS_ERROR_EDITOR_ACTION_CANCELED,
3900 "Why beforeinput event was canceled in this case?");
3901 MOZ_ASSERT(NS_SUCCEEDED(rvIgnored),
3902 "MaybeDispatchBeforeInputEvent() should just mark the instance as "
3903 "handled it");
3905 // Composition string may have hidden the caret. Therefore, we need to
3906 // cancel it here.
3907 HideCaret(false);
3909 // FYI: mComposition still keeps storing container text node of committed
3910 // string, its offset and length. However, they will be invalidated
3911 // soon since its Destroy() will be called by IMEStateManager.
3912 mComposition->EndHandlingComposition(this);
3913 mComposition = nullptr;
3915 // notify editor observers of action
3916 // FYI: With current draft, "input" event should be fired from
3917 // OnCompositionChange(), however, it requires a lot of our UI code
3918 // change and does not make sense. See spec issue:
3919 // https://github.com/w3c/uievents/issues/202
3920 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
3923 void EditorBase::DoAfterDoTransaction(nsITransaction* aTransaction) {
3924 bool isTransientTransaction;
3925 MOZ_ALWAYS_SUCCEEDS(aTransaction->GetIsTransient(&isTransientTransaction));
3927 if (!isTransientTransaction) {
3928 // we need to deal here with the case where the user saved after some
3929 // edits, then undid one or more times. Then, the undo count is -ve,
3930 // but we can't let a do take it back to zero. So we flip it up to
3931 // a +ve number.
3932 int32_t modCount;
3933 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount);
3934 NS_WARNING_ASSERTION(
3935 NS_SUCCEEDED(rvIgnored),
3936 "EditorBase::GetModificationCount() failed, but ignored");
3937 if (modCount < 0) {
3938 modCount = -modCount;
3941 // don't count transient transactions
3942 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1));
3946 void EditorBase::DoAfterUndoTransaction() {
3947 // all undoable transactions are non-transient
3948 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(-1));
3951 void EditorBase::DoAfterRedoTransaction() {
3952 // all redoable transactions are non-transient
3953 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1));
3956 already_AddRefed<DeleteMultipleRangesTransaction>
3957 EditorBase::CreateTransactionForDeleteSelection(
3958 HowToHandleCollapsedRange aHowToHandleCollapsedRange,
3959 const AutoRangeArray& aRangesToDelete) {
3960 MOZ_ASSERT(IsEditActionDataAvailable());
3961 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
3963 // Check whether the selection is collapsed and we should do nothing:
3964 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
3965 aHowToHandleCollapsedRange ==
3966 HowToHandleCollapsedRange::Ignore)) {
3967 return nullptr;
3970 // allocate the out-param transaction
3971 RefPtr<DeleteMultipleRangesTransaction> transaction =
3972 DeleteMultipleRangesTransaction::Create();
3973 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
3974 // Same with range as with selection; if it is collapsed and action
3975 // is eNone, do nothing.
3976 if (!range->Collapsed()) {
3977 RefPtr<DeleteRangeTransaction> deleteRangeTransaction =
3978 DeleteRangeTransaction::Create(*this, range);
3979 // XXX Oh, not checking if deleteRangeTransaction can modify the range...
3980 transaction->AppendChild(*deleteRangeTransaction);
3981 continue;
3984 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::Ignore) {
3985 continue;
3988 // Let's extend the collapsed range to delete content around it.
3989 RefPtr<DeleteContentTransactionBase> deleteNodeOrTextTransaction =
3990 CreateTransactionForCollapsedRange(range, aHowToHandleCollapsedRange);
3991 // XXX When there are two or more ranges and at least one of them is
3992 // not editable, deleteNodeOrTextTransaction may be nullptr.
3993 // In such case, should we stop removing other ranges too?
3994 if (!deleteNodeOrTextTransaction) {
3995 NS_WARNING("EditorBase::CreateTransactionForCollapsedRange() failed");
3996 return nullptr;
3998 transaction->AppendChild(*deleteNodeOrTextTransaction);
4001 return transaction.forget();
4004 // XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior
4005 // are not implemented
4006 already_AddRefed<DeleteContentTransactionBase>
4007 EditorBase::CreateTransactionForCollapsedRange(
4008 const nsRange& aCollapsedRange,
4009 HowToHandleCollapsedRange aHowToHandleCollapsedRange) {
4010 MOZ_ASSERT(aCollapsedRange.Collapsed());
4011 MOZ_ASSERT(
4012 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward ||
4013 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward);
4015 EditorRawDOMPoint point(aCollapsedRange.StartRef());
4016 if (NS_WARN_IF(!point.IsSet())) {
4017 return nullptr;
4019 if (IsTextEditor()) {
4020 // There should be only one text node in the anonymous `<div>` (but may
4021 // be followed by a padding `<br>`). We should adjust the point into
4022 // the text node (or return nullptr if there is no text to delete) for
4023 // avoiding finding the text node with complicated API.
4024 if (!point.IsInTextNode()) {
4025 const Element* anonymousDiv = GetRoot();
4026 if (NS_WARN_IF(!anonymousDiv)) {
4027 return nullptr;
4029 if (!anonymousDiv->GetFirstChild() ||
4030 !anonymousDiv->GetFirstChild()->IsText()) {
4031 return nullptr; // The value is empty.
4033 if (point.GetContainer() == anonymousDiv) {
4034 if (point.IsStartOfContainer()) {
4035 point.Set(anonymousDiv->GetFirstChild(), 0);
4036 } else {
4037 point.SetToEndOf(anonymousDiv->GetFirstChild());
4039 } else {
4040 // Must be referring a padding `<br>` element or after the text node.
4041 point.SetToEndOf(anonymousDiv->GetFirstChild());
4044 MOZ_ASSERT(!point.ContainerAs<Text>()->GetPreviousSibling());
4045 MOZ_ASSERT(!point.ContainerAs<Text>()->GetNextSibling() ||
4046 !point.ContainerAs<Text>()->GetNextSibling()->IsText());
4047 if (aHowToHandleCollapsedRange ==
4048 HowToHandleCollapsedRange::ExtendBackward &&
4049 point.IsStartOfContainer()) {
4050 return nullptr;
4052 if (aHowToHandleCollapsedRange ==
4053 HowToHandleCollapsedRange::ExtendForward &&
4054 point.IsEndOfContainer()) {
4055 return nullptr;
4059 // XXX: if the container of point is empty, then we'll need to delete the node
4060 // as well as the 1 child
4062 // build a transaction for deleting the appropriate data
4063 // XXX: this has to come from rule section
4064 const Element* const anonymousDivOrEditingHost =
4065 IsTextEditor() ? GetRoot() : AsHTMLEditor()->ComputeEditingHost();
4066 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward &&
4067 point.IsStartOfContainer()) {
4068 MOZ_ASSERT(IsHTMLEditor());
4069 // We're backspacing from the beginning of a node. Delete the last thing
4070 // of previous editable content.
4071 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
4072 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
4073 IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle
4074 : BlockInlineCheck::UseComputedDisplayOutsideStyle,
4075 anonymousDivOrEditingHost);
4076 if (!previousEditableContent) {
4077 NS_WARNING("There was no editable content before the collapsed range");
4078 return nullptr;
4081 // There is an editable content, so delete its last child (if a text node,
4082 // delete the last char). If it has no children, delete it.
4083 if (previousEditableContent->IsText()) {
4084 uint32_t length = previousEditableContent->Length();
4085 // Bail out for empty text node.
4086 // XXX Do we want to do something else?
4087 // XXX If other browsers delete empty text node, we should follow it.
4088 if (NS_WARN_IF(!length)) {
4089 NS_WARNING("Previous editable content was an empty text node");
4090 return nullptr;
4092 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4093 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
4094 *this, *previousEditableContent->AsText(), length);
4095 if (!deleteTextTransaction) {
4096 NS_WARNING(
4097 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
4098 return nullptr;
4100 return deleteTextTransaction.forget();
4103 if (IsHTMLEditor() &&
4104 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*previousEditableContent))) {
4105 return nullptr;
4107 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
4108 DeleteNodeTransaction::MaybeCreate(*this, *previousEditableContent);
4109 if (!deleteNodeTransaction) {
4110 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed");
4111 return nullptr;
4113 return deleteNodeTransaction.forget();
4116 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward &&
4117 point.IsEndOfContainer()) {
4118 MOZ_ASSERT(IsHTMLEditor());
4119 // We're deleting from the end of a node. Delete the first thing of
4120 // next editable content.
4121 nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
4122 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
4123 IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle
4124 : BlockInlineCheck::UseComputedDisplayOutsideStyle,
4125 anonymousDivOrEditingHost);
4126 if (!nextEditableContent) {
4127 NS_WARNING("There was no editable content after the collapsed range");
4128 return nullptr;
4131 // There is an editable content, so delete its first child (if a text node,
4132 // delete the first char). If it has no children, delete it.
4133 if (nextEditableContent->IsText()) {
4134 uint32_t length = nextEditableContent->Length();
4135 // Bail out for empty text node.
4136 // XXX Do we want to do something else?
4137 // XXX If other browsers delete empty text node, we should follow it.
4138 if (!length) {
4139 NS_WARNING("Next editable content was an empty text node");
4140 return nullptr;
4142 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4143 DeleteTextTransaction::MaybeCreateForNextCharacter(
4144 *this, *nextEditableContent->AsText(), 0);
4145 if (!deleteTextTransaction) {
4146 NS_WARNING(
4147 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
4148 return nullptr;
4150 return deleteTextTransaction.forget();
4153 if (IsHTMLEditor() &&
4154 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*nextEditableContent))) {
4155 return nullptr;
4157 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
4158 DeleteNodeTransaction::MaybeCreate(*this, *nextEditableContent);
4159 if (!deleteNodeTransaction) {
4160 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed");
4161 return nullptr;
4163 return deleteNodeTransaction.forget();
4166 if (point.IsInTextNode()) {
4167 if (aHowToHandleCollapsedRange ==
4168 HowToHandleCollapsedRange::ExtendBackward) {
4169 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4170 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
4171 *this, *point.ContainerAs<Text>(), point.Offset());
4172 NS_WARNING_ASSERTION(
4173 deleteTextTransaction,
4174 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
4175 return deleteTextTransaction.forget();
4177 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4178 DeleteTextTransaction::MaybeCreateForNextCharacter(
4179 *this, *point.ContainerAs<Text>(), point.Offset());
4180 NS_WARNING_ASSERTION(
4181 deleteTextTransaction,
4182 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
4183 return deleteTextTransaction.forget();
4186 nsIContent* editableContent = nullptr;
4187 if (IsHTMLEditor()) {
4188 editableContent =
4189 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward
4190 ? HTMLEditUtils::GetPreviousContent(
4191 point, {WalkTreeOption::IgnoreNonEditableNode},
4192 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4193 anonymousDivOrEditingHost)
4194 : HTMLEditUtils::GetNextContent(
4195 point, {WalkTreeOption::IgnoreNonEditableNode},
4196 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4197 anonymousDivOrEditingHost);
4198 if (!editableContent) {
4199 NS_WARNING("There was no editable content around the collapsed range");
4200 return nullptr;
4202 while (editableContent && editableContent->IsCharacterData() &&
4203 !editableContent->Length()) {
4204 // Can't delete an empty text node (bug 762183)
4205 editableContent =
4206 aHowToHandleCollapsedRange ==
4207 HowToHandleCollapsedRange::ExtendBackward
4208 ? HTMLEditUtils::GetPreviousContent(
4209 *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
4210 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4211 anonymousDivOrEditingHost)
4212 : HTMLEditUtils::GetNextContent(
4213 *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
4214 BlockInlineCheck::UseComputedDisplayOutsideStyle,
4215 anonymousDivOrEditingHost);
4217 if (!editableContent) {
4218 NS_WARNING(
4219 "There was no editable content which is not empty around the "
4220 "collapsed range");
4221 return nullptr;
4223 } else {
4224 MOZ_ASSERT(point.IsInTextNode());
4225 editableContent = point.GetContainerAs<nsIContent>();
4226 if (!editableContent) {
4227 NS_WARNING("If there was no text node, should've been handled first");
4228 return nullptr;
4232 if (editableContent->IsText()) {
4233 if (aHowToHandleCollapsedRange ==
4234 HowToHandleCollapsedRange::ExtendBackward) {
4235 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4236 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
4237 *this, *editableContent->AsText(), editableContent->Length());
4238 NS_WARNING_ASSERTION(
4239 deleteTextTransaction,
4240 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
4241 return deleteTextTransaction.forget();
4244 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4245 DeleteTextTransaction::MaybeCreateForNextCharacter(
4246 *this, *editableContent->AsText(), 0);
4247 NS_WARNING_ASSERTION(
4248 deleteTextTransaction,
4249 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
4250 return deleteTextTransaction.forget();
4253 MOZ_ASSERT(IsHTMLEditor());
4254 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*editableContent))) {
4255 return nullptr;
4257 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
4258 DeleteNodeTransaction::MaybeCreate(*this, *editableContent);
4259 NS_WARNING_ASSERTION(deleteNodeTransaction,
4260 "DeleteNodeTransaction::MaybeCreate() failed");
4261 return deleteNodeTransaction.forget();
4264 bool EditorBase::FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
4265 nsIEditor::EDirection aDirectionAndAmount) const {
4266 MOZ_ASSERT(IsEditActionDataAvailable());
4268 if (NS_WARN_IF(Destroyed())) {
4269 return false;
4271 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
4272 aDirectionAndAmount, SelectionRef())) {
4273 return true;
4275 // Although AutoRangeArray::ExtendAnchorFocusRangeFor() will use
4276 // nsFrameSelection, if it still has dirty frame, nsFrameSelection doesn't
4277 // extend selection since we block script.
4278 if (RefPtr<PresShell> presShell = GetPresShell()) {
4279 presShell->FlushPendingNotifications(FlushType::Layout);
4280 if (NS_WARN_IF(Destroyed())) {
4281 return false;
4284 return true;
4287 nsresult EditorBase::DeleteSelectionAsAction(
4288 nsIEditor::EDirection aDirectionAndAmount,
4289 nsIEditor::EStripWrappers aStripWrappers, nsIPrincipal* aPrincipal) {
4290 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4291 // Showing this assertion is fine if this method is called by outside via
4292 // mutation event listener or something. Otherwise, this is called by
4293 // wrong method.
4294 NS_ASSERTION(
4295 !mPlaceholderBatch,
4296 "Should be called only when this is the only edit action of the "
4297 "operation unless mutation event listener nests some operations");
4299 // If we're a TextEditor instance, we don't need to treat parent elements
4300 // so that we can ignore aStripWrappers for skipping unnecessary cost.
4301 if (IsTextEditor()) {
4302 aStripWrappers = nsIEditor::eNoStrip;
4305 EditAction editAction = EditAction::eDeleteSelection;
4306 switch (aDirectionAndAmount) {
4307 case nsIEditor::ePrevious:
4308 editAction = EditAction::eDeleteBackward;
4309 break;
4310 case nsIEditor::eNext:
4311 editAction = EditAction::eDeleteForward;
4312 break;
4313 case nsIEditor::ePreviousWord:
4314 editAction = EditAction::eDeleteWordBackward;
4315 break;
4316 case nsIEditor::eNextWord:
4317 editAction = EditAction::eDeleteWordForward;
4318 break;
4319 case nsIEditor::eToBeginningOfLine:
4320 editAction = EditAction::eDeleteToBeginningOfSoftLine;
4321 break;
4322 case nsIEditor::eToEndOfLine:
4323 editAction = EditAction::eDeleteToEndOfSoftLine;
4324 break;
4327 AutoEditActionDataSetter editActionData(*this, editAction, aPrincipal);
4328 if (NS_WARN_IF(!editActionData.CanHandle())) {
4329 return NS_ERROR_NOT_INITIALIZED;
4332 // If there is an existing selection when an extended delete is requested,
4333 // platforms that use "caret-style" caret positioning collapse the
4334 // selection to the start and then create a new selection.
4335 // Platforms that use "selection-style" caret positioning just delete the
4336 // existing selection without extending it.
4337 if (!SelectionRef().IsCollapsed()) {
4338 switch (aDirectionAndAmount) {
4339 case eNextWord:
4340 case ePreviousWord:
4341 case eToBeginningOfLine:
4342 case eToEndOfLine: {
4343 if (mCaretStyle != 1) {
4344 aDirectionAndAmount = eNone;
4345 break;
4347 ErrorResult error;
4348 SelectionRef().CollapseToStart(error);
4349 if (NS_WARN_IF(Destroyed())) {
4350 error.SuppressException();
4351 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
4353 if (error.Failed()) {
4354 NS_WARNING("Selection::CollapseToStart() failed");
4355 editActionData.Abort();
4356 return EditorBase::ToGenericNSResult(error.StealNSResult());
4358 break;
4360 default:
4361 break;
4365 // If Selection is still NOT collapsed, it does not important removing
4366 // range of the operation since we'll remove the selected content. However,
4367 // information of direction (backward or forward) may be important for
4368 // web apps. E.g., web apps may want to mark selected range as "deleted"
4369 // and move caret before or after the range. Therefore, we should forget
4370 // only the range information but keep range information. See discussion
4371 // of the spec issue for the detail:
4372 // https://github.com/w3c/input-events/issues/82
4373 if (!SelectionRef().IsCollapsed()) {
4374 switch (editAction) {
4375 case EditAction::eDeleteWordBackward:
4376 case EditAction::eDeleteToBeginningOfSoftLine:
4377 editActionData.UpdateEditAction(EditAction::eDeleteBackward);
4378 break;
4379 case EditAction::eDeleteWordForward:
4380 case EditAction::eDeleteToEndOfSoftLine:
4381 editActionData.UpdateEditAction(EditAction::eDeleteForward);
4382 break;
4383 default:
4384 break;
4388 editActionData.SetSelectionCreatedByDoubleclick(
4389 SelectionRef().GetFrameSelection() &&
4390 SelectionRef().GetFrameSelection()->IsDoubleClickSelection());
4392 if (!FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
4393 aDirectionAndAmount)) {
4394 NS_WARNING("Flusing pending notifications caused destroying the editor");
4395 editActionData.Abort();
4396 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
4399 nsresult rv =
4400 editActionData.MaybeDispatchBeforeInputEvent(aDirectionAndAmount);
4401 if (NS_FAILED(rv)) {
4402 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4403 "MaybeDispatchBeforeInputEvent() failed");
4404 return EditorBase::ToGenericNSResult(rv);
4407 // delete placeholder txns merge.
4408 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName,
4409 ScrollSelectionIntoView::Yes,
4410 __FUNCTION__);
4411 rv = DeleteSelectionAsSubAction(aDirectionAndAmount, aStripWrappers);
4412 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4413 "EditorBase::DeleteSelectionAsSubAction() failed");
4414 return EditorBase::ToGenericNSResult(rv);
4417 nsresult EditorBase::DeleteSelectionAsSubAction(
4418 nsIEditor::EDirection aDirectionAndAmount,
4419 nsIEditor::EStripWrappers aStripWrappers) {
4420 MOZ_ASSERT(IsEditActionDataAvailable());
4421 // If handling edit action is for table editing, this may be called with
4422 // selecting an any table element by the caller, but it's not usual work of
4423 // this so that `MayEditActionDeleteSelection()` returns false.
4424 MOZ_ASSERT(MayEditActionDeleteSelection(GetEditAction()) ||
4425 IsEditActionTableEditing(GetEditAction()));
4426 MOZ_ASSERT(mPlaceholderBatch);
4427 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4428 NS_ASSERTION(IsHTMLEditor() || aStripWrappers == nsIEditor::eNoStrip,
4429 "TextEditor does not support strip wrappers");
4431 if (NS_WARN_IF(!mInitSucceeded)) {
4432 return NS_ERROR_NOT_INITIALIZED;
4435 IgnoredErrorResult ignoredError;
4436 AutoEditSubActionNotifier startToHandleEditSubAction(
4437 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount,
4438 ignoredError);
4439 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4440 return ignoredError.StealNSResult();
4442 NS_WARNING_ASSERTION(
4443 !ignoredError.Failed(),
4444 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4447 Result<EditActionResult, nsresult> result =
4448 HandleDeleteSelection(aDirectionAndAmount, aStripWrappers);
4449 if (MOZ_UNLIKELY(result.isErr())) {
4450 NS_WARNING("TextEditor::HandleDeleteSelection() failed");
4451 return result.unwrapErr();
4453 if (result.inspect().Canceled()) {
4454 return NS_OK;
4458 // XXX This is odd. We just tries to remove empty text node here but we
4459 // refer `Selection`. It may be modified by mutation event listeners
4460 // so that we should remove the empty text node when we make it empty.
4461 const auto atNewStartOfSelection =
4462 GetFirstSelectionStartPoint<EditorDOMPoint>();
4463 if (NS_WARN_IF(!atNewStartOfSelection.IsSet())) {
4464 // XXX And also it seems that we don't need to return error here.
4465 // Why don't we just ignore? `Selection::RemoveAllRanges()` may
4466 // have been called by mutation event listeners.
4467 return NS_ERROR_FAILURE;
4469 if (IsHTMLEditor() && atNewStartOfSelection.IsInTextNode() &&
4470 !atNewStartOfSelection.GetContainer()->Length()) {
4471 nsresult rv = DeleteNodeWithTransaction(
4472 MOZ_KnownLive(*atNewStartOfSelection.ContainerAs<Text>()));
4473 if (NS_FAILED(rv)) {
4474 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4475 return rv;
4479 // XXX I don't think that this is necessary in anonymous `<div>` element of
4480 // TextEditor since there should be at most one text node and at most
4481 // one padding `<br>` element so that `<br>` element won't be before
4482 // caret.
4483 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine) {
4484 // We prevent the caret from sticking on the left of previous `<br>`
4485 // element (i.e. the end of previous line) after this deletion. Bug 92124.
4486 if (MOZ_UNLIKELY(NS_FAILED(SelectionRef().SetInterlinePosition(
4487 InterlinePosition::StartOfNextLine)))) {
4488 NS_WARNING(
4489 "Selection::SetInterlinePosition(InterlinePosition::StartOfNextLine) "
4490 "failed");
4491 return NS_ERROR_FAILURE; // Don't need to return NS_ERROR_NOT_INITIALIZED
4495 return NS_OK;
4498 nsresult EditorBase::HandleDropEvent(DragEvent* aDropEvent) {
4499 if (NS_WARN_IF(!aDropEvent)) {
4500 return NS_ERROR_INVALID_ARG;
4503 DebugOnly<nsresult> rvIgnored = CommitComposition();
4504 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4505 "EditorBase::CommitComposition() failed, but ignored");
4507 AutoEditActionDataSetter editActionData(*this, EditAction::eDrop);
4508 // We need to initialize data or dataTransfer later. Therefore, we cannot
4509 // dispatch "beforeinput" event until then.
4510 if (NS_WARN_IF(!editActionData.CanHandle())) {
4511 return NS_ERROR_NOT_INITIALIZED;
4514 RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer();
4515 if (NS_WARN_IF(!dataTransfer)) {
4516 return NS_ERROR_FAILURE;
4519 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
4520 if (NS_WARN_IF(!dragSession)) {
4521 return NS_ERROR_FAILURE;
4524 nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode();
4526 // If there is no source document, then the drag was from another application
4527 // or another process (such as an out of process subframe). The latter case is
4528 // not currently handled below when checking for a move/copy and deleting the
4529 // existing text.
4530 RefPtr<Document> srcdoc;
4531 if (sourceNode) {
4532 srcdoc = sourceNode->OwnerDoc();
4535 nsCOMPtr<nsIPrincipal> sourcePrincipal;
4536 dragSession->GetTriggeringPrincipal(getter_AddRefs(sourcePrincipal));
4538 if (nsContentUtils::CheckForSubFrameDrop(
4539 dragSession, aDropEvent->WidgetEventPtr()->AsDragEvent())) {
4540 // Don't allow drags from subframe documents with different origins than
4541 // the drop destination.
4542 if (IsSafeToInsertData(sourcePrincipal) == SafeToInsertData::No) {
4543 return NS_OK;
4547 // Current doc is destination
4548 RefPtr<Document> document = GetDocument();
4549 if (NS_WARN_IF(!document)) {
4550 return NS_ERROR_NOT_INITIALIZED;
4553 const uint32_t numItems = dataTransfer->MozItemCount();
4554 if (NS_WARN_IF(!numItems)) {
4555 return NS_ERROR_FAILURE; // Nothing to drop?
4558 // We have to figure out whether to delete and relocate caret only once
4559 // Parent and offset are under the mouse cursor.
4560 int32_t dropOffset = -1;
4561 nsCOMPtr<nsIContent> dropParentContent =
4562 aDropEvent->GetRangeParentContentAndOffset(&dropOffset);
4563 if (dropOffset < 0) {
4564 NS_WARNING(
4565 "DropEvent::GetRangeParentContentAndOffset() returned negative offset");
4566 return NS_ERROR_FAILURE;
4568 EditorDOMPoint droppedAt(dropParentContent,
4569 AssertedCast<uint32_t>(dropOffset));
4570 if (NS_WARN_IF(!droppedAt.IsInContentNode())) {
4571 return NS_ERROR_FAILURE;
4574 // Check if dropping into a selected range. If so and the source comes from
4575 // same document, jump through some hoops to determine if mouse is over
4576 // selection (bail) and whether user wants to copy selection or delete it.
4577 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) {
4578 bool isPointInSelection = nsContentUtils::IsPointInSelection(
4579 SelectionRef(), *droppedAt.GetContainer(), droppedAt.Offset());
4580 if (isPointInSelection) {
4581 // If source document and destination document is same and dropping
4582 // into one of selected ranges, we don't need to do nothing.
4583 // XXX If the source comes from outside of this editor, this check
4584 // means that we don't allow to drop the item in the selected
4585 // range. However, the selection is hidden until the <input> or
4586 // <textarea> gets focus, therefore, this looks odd.
4587 return NS_OK;
4591 // Delete if user doesn't want to copy when user moves selected content
4592 // to different place in same editor.
4593 // XXX Do we need the check whether it's in same document or not?
4594 RefPtr<EditorBase> editorToDeleteSelection;
4595 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) {
4596 if ((dataTransfer->DropEffectInt() &
4597 nsIDragService::DRAGDROP_ACTION_MOVE) &&
4598 !(dataTransfer->DropEffectInt() &
4599 nsIDragService::DRAGDROP_ACTION_COPY)) {
4600 // If the source node is in native anonymous tree, it must be in
4601 // <input> or <textarea> element. If so, its TextEditor can remove it.
4602 if (sourceNode->IsInNativeAnonymousSubtree()) {
4603 if (RefPtr textControlElement = TextControlElement::FromNodeOrNull(
4604 sourceNode
4605 ->GetClosestNativeAnonymousSubtreeRootParentOrHost())) {
4606 editorToDeleteSelection = textControlElement->GetTextEditor();
4609 // Otherwise, must be the content is in HTMLEditor.
4610 else if (IsHTMLEditor()) {
4611 editorToDeleteSelection = this;
4612 } else {
4613 editorToDeleteSelection =
4614 nsContentUtils::GetHTMLEditor(srcdoc->GetPresContext());
4617 // If the found editor isn't modifiable, we should not try to delete
4618 // selection.
4619 if (editorToDeleteSelection && !editorToDeleteSelection->IsModifiable()) {
4620 editorToDeleteSelection = nullptr;
4622 // If the found editor has collapsed selection, we need to delete nothing
4623 // in the editor.
4624 if (editorToDeleteSelection) {
4625 if (Selection* selection = editorToDeleteSelection->GetSelection()) {
4626 if (selection->IsCollapsed()) {
4627 editorToDeleteSelection = nullptr;
4633 // Combine any deletion and drop insertion into one transaction.
4634 AutoPlaceholderBatch treatAsOneTransaction(
4635 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4637 // Don't dispatch "selectionchange" event until inserting all contents.
4638 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
4640 // Track dropped point with nsRange because we shouldn't insert the
4641 // dropped content into different position even if some event listeners
4642 // modify selection. Note that Chrome's behavior is really odd. So,
4643 // we don't need to worry about web-compat about this.
4644 IgnoredErrorResult ignoredError;
4645 RefPtr<nsRange> rangeAtDropPoint =
4646 nsRange::Create(droppedAt.ToRawRangeBoundary(),
4647 droppedAt.ToRawRangeBoundary(), ignoredError);
4648 if (NS_WARN_IF(ignoredError.Failed()) ||
4649 NS_WARN_IF(!rangeAtDropPoint->IsPositioned())) {
4650 editActionData.Abort();
4651 return NS_ERROR_FAILURE;
4654 // Remove selected contents first here because we need to fire a pair of
4655 // "beforeinput" and "input" for deletion and web apps can cancel only
4656 // this deletion. Note that callee may handle insertion asynchronously.
4657 // Therefore, it is the best to remove selected content here.
4658 if (editorToDeleteSelection) {
4659 nsresult rv = editorToDeleteSelection->DeleteSelectionByDragAsAction(
4660 mDispatchInputEvent);
4661 if (NS_WARN_IF(Destroyed())) {
4662 editActionData.Abort();
4663 return NS_OK;
4665 // Ignore the editor instance specific error if it's another editor.
4666 if (this != editorToDeleteSelection &&
4667 (rv == NS_ERROR_NOT_INITIALIZED || rv == NS_ERROR_EDITOR_DESTROYED)) {
4668 rv = NS_OK;
4670 // Don't cancel "insertFromDrop" even if "deleteByDrag" is canceled.
4671 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) {
4672 NS_WARNING("EditorBase::DeleteSelectionByDragAsAction() failed");
4673 editActionData.Abort();
4674 return EditorBase::ToGenericNSResult(rv);
4676 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
4677 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
4678 editActionData.Abort();
4679 return NS_ERROR_FAILURE;
4681 droppedAt = rangeAtDropPoint->StartRef();
4682 MOZ_ASSERT(droppedAt.IsSetAndValid());
4683 MOZ_ASSERT(droppedAt.IsInContentNode());
4686 // Before inserting dropping content, we need to move focus for compatibility
4687 // with Chrome and firing "beforeinput" event on new editing host.
4688 RefPtr<Element> focusedElement, newFocusedElement;
4689 if (IsTextEditor()) {
4690 newFocusedElement = GetExposedRoot();
4691 focusedElement = IsActiveInDOMWindow() ? newFocusedElement : nullptr;
4693 // TODO: We need to add automated tests when dropping something into an
4694 // editing host for contenteditable which is in a shadow DOM tree
4695 // and its host which is in design mode.
4696 else if (!AsHTMLEditor()->IsInDesignMode()) {
4697 focusedElement = AsHTMLEditor()->ComputeEditingHost();
4698 if (focusedElement &&
4699 droppedAt.ContainerAs<nsIContent>()->IsInclusiveDescendantOf(
4700 focusedElement)) {
4701 newFocusedElement = focusedElement;
4702 } else {
4703 newFocusedElement = droppedAt.ContainerAs<nsIContent>()->GetEditingHost();
4706 // Move selection right now. Note that this does not move focus because
4707 // `Selection` moves focus with selection change only when the API caller is
4708 // JS. And also this does not notify selection listeners (nor
4709 // "selectionchange") since we created SelectionBatcher above.
4710 ErrorResult error;
4711 SelectionRef().SetStartAndEnd(droppedAt.ToRawRangeBoundary(),
4712 droppedAt.ToRawRangeBoundary(), error);
4713 if (error.Failed()) {
4714 NS_WARNING("Selection::SetStartAndEnd() failed");
4715 editActionData.Abort();
4716 return error.StealNSResult();
4718 if (NS_WARN_IF(Destroyed())) {
4719 editActionData.Abort();
4720 return NS_OK;
4722 // Then, move focus if necessary. This must cause dispatching "blur" event
4723 // and "focus" event.
4724 if (newFocusedElement && focusedElement != newFocusedElement) {
4725 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
4726 DebugOnly<nsresult> rvIgnored = fm->SetFocus(newFocusedElement, 0);
4727 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4728 "nsFocusManager::SetFocus() failed to set focus "
4729 "to the element, but ignored");
4730 if (NS_WARN_IF(Destroyed())) {
4731 editActionData.Abort();
4732 return NS_OK;
4734 // "blur" or "focus" event listener may have changed the value.
4735 // Let's keep using the original point.
4736 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
4737 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
4738 return NS_ERROR_FAILURE;
4740 droppedAt = rangeAtDropPoint->StartRef();
4741 MOZ_ASSERT(droppedAt.IsSetAndValid());
4743 // If focus is changed to different element and we're handling drop in
4744 // contenteditable, we cannot handle it without focus. So, we should give
4745 // it up.
4746 if (IsHTMLEditor() && !AsHTMLEditor()->IsInDesignMode() &&
4747 NS_WARN_IF(newFocusedElement != AsHTMLEditor()->ComputeEditingHost())) {
4748 editActionData.Abort();
4749 return NS_OK;
4753 nsresult rv = InsertDroppedDataTransferAsAction(editActionData, *dataTransfer,
4754 droppedAt, sourcePrincipal);
4755 if (rv == NS_ERROR_EDITOR_DESTROYED ||
4756 rv == NS_ERROR_EDITOR_ACTION_CANCELED) {
4757 return EditorBase::ToGenericNSResult(rv);
4759 NS_WARNING_ASSERTION(
4760 NS_SUCCEEDED(rv),
4761 "EditorBase::InsertDroppedDataTransferAsAction() failed, but ignored");
4763 rv = ScrollSelectionFocusIntoView();
4764 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4765 "EditorBase::ScrollSelectionFocusIntoView() failed");
4766 return rv;
4769 nsresult EditorBase::DeleteSelectionByDragAsAction(bool aDispatchInputEvent) {
4770 // TODO: Move this method to `EditorBase`.
4771 AutoRestore<bool> saveDispatchInputEvent(mDispatchInputEvent);
4772 mDispatchInputEvent = aDispatchInputEvent;
4773 // Even if we're handling "deleteByDrag" in same editor as "insertFromDrop",
4774 // we need to recreate edit action data here because
4775 // `AutoEditActionDataSetter` needs to manage event state separately.
4776 bool requestedByAnotherEditor = GetEditAction() != EditAction::eDrop;
4777 AutoEditActionDataSetter editActionData(*this, EditAction::eDeleteByDrag);
4778 MOZ_ASSERT(!SelectionRef().IsCollapsed());
4779 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
4780 if (NS_FAILED(rv)) {
4781 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4782 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
4783 return rv;
4785 // But keep using placeholder transaction for "insertFromDrop" if there is.
4786 Maybe<AutoPlaceholderBatch> treatAsOneTransaction;
4787 if (requestedByAnotherEditor) {
4788 treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes,
4789 __FUNCTION__);
4792 // We may need to update the source node to dispatch "dragend" below.
4793 // Chrome restricts the new target under the <body> here. Therefore, we
4794 // should follow it here.
4795 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/editing_utilities.cc;l=254;drc=da35f4ed6398ae287d5adc828b9546eec95f668a
4796 const RefPtr<Element> editingHost =
4797 IsHTMLEditor() ? AsHTMLEditor()->ComputeEditingHost(
4798 HTMLEditor::LimitInBodyElement::Yes)
4799 : nullptr;
4801 rv = DeleteSelectionAsSubAction(nsIEditor::eNone, IsTextEditor()
4802 ? nsIEditor::eNoStrip
4803 : nsIEditor::eStrip);
4804 if (NS_FAILED(rv)) {
4805 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed");
4806 return rv;
4809 if (!mDispatchInputEvent) {
4810 return NS_OK;
4813 if (treatAsOneTransaction.isNothing()) {
4814 DispatchInputEvent();
4817 if (NS_WARN_IF(Destroyed())) {
4818 return NS_ERROR_EDITOR_DESTROYED;
4821 // If we success everything here, we may need to retarget "dragend" event
4822 // target for compatibility with the other browsers. They do this only when
4823 // their builtin editor delete the source node from the document. Then,
4824 // they retarget the source node to the editing host.
4825 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/drag_controller.cc;l=724;drc=d9ba13b8cd8ac0faed7afc3d1f7e4b67ebac2a0b
4826 if (editingHost) {
4827 if (nsCOMPtr<nsIDragService> dragService =
4828 do_GetService("@mozilla.org/widget/dragservice;1")) {
4829 dragService->MaybeEditorDeletedSourceNode(editingHost);
4832 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
4835 nsresult EditorBase::DeleteSelectionWithTransaction(
4836 nsIEditor::EDirection aDirectionAndAmount,
4837 nsIEditor::EStripWrappers aStripWrappers) {
4838 MOZ_ASSERT(IsEditActionDataAvailable());
4839 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4840 if (NS_WARN_IF(Destroyed())) {
4841 return NS_ERROR_EDITOR_DESTROYED;
4844 AutoRangeArray rangesToDelete(SelectionRef());
4845 if (NS_WARN_IF(rangesToDelete.Ranges().IsEmpty())) {
4846 NS_ASSERTION(
4847 false,
4848 "For avoiding to throw incompatible exception for `execCommand`, fix "
4849 "the caller");
4850 return NS_ERROR_FAILURE;
4853 if (IsTextEditor()) {
4854 if (const Text* theTextNode = AsTextEditor()->GetTextNode()) {
4855 rangesToDelete.EnsureRangesInTextNode(*theTextNode);
4859 Result<CaretPoint, nsresult> caretPointOrError = DeleteRangesWithTransaction(
4860 aDirectionAndAmount, aStripWrappers, rangesToDelete);
4861 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
4862 NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
4863 return caretPointOrError.unwrapErr();
4865 nsresult rv = caretPointOrError.inspect().SuggestCaretPointTo(
4866 *this, {SuggestCaret::OnlyIfHasSuggestion,
4867 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
4868 SuggestCaret::AndIgnoreTrivialError});
4869 if (NS_FAILED(rv)) {
4870 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
4872 NS_WARNING_ASSERTION(rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
4873 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
4874 return NS_OK;
4877 Result<CaretPoint, nsresult> EditorBase::DeleteRangeWithTransaction(
4878 nsIEditor::EDirection aDirectionAndAmount,
4879 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete) {
4880 MOZ_ASSERT(IsEditActionDataAvailable());
4881 MOZ_ASSERT(!Destroyed());
4882 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4884 HowToHandleCollapsedRange howToHandleCollapsedRange =
4885 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
4886 if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() &&
4887 howToHandleCollapsedRange ==
4888 HowToHandleCollapsedRange::Ignore)) {
4889 return CaretPoint(EditorDOMPoint(aRangeToDelete.StartRef()));
4892 AutoRangeArray rangesToDelete(aRangeToDelete);
4893 Result<CaretPoint, nsresult> result = DeleteRangesWithTransaction(
4894 aDirectionAndAmount, aStripWrappers, rangesToDelete);
4895 NS_WARNING_ASSERTION(result.isOk(),
4896 "EditorBase::DeleteRangesWithTransaction() failed");
4897 return result;
4900 Result<CaretPoint, nsresult> EditorBase::DeleteRangesWithTransaction(
4901 nsIEditor::EDirection aDirectionAndAmount,
4902 nsIEditor::EStripWrappers aStripWrappers,
4903 const AutoRangeArray& aRangesToDelete) {
4904 MOZ_ASSERT(IsEditActionDataAvailable());
4905 MOZ_ASSERT(!Destroyed());
4906 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4907 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
4909 HowToHandleCollapsedRange howToHandleCollapsedRange =
4910 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
4911 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
4912 howToHandleCollapsedRange ==
4913 HowToHandleCollapsedRange::Ignore)) {
4914 NS_ASSERTION(
4915 false,
4916 "For avoiding to throw incompatible exception for `execCommand`, fix "
4917 "the caller");
4918 return Err(NS_ERROR_FAILURE);
4921 RefPtr<DeleteMultipleRangesTransaction> deleteSelectionTransaction =
4922 CreateTransactionForDeleteSelection(howToHandleCollapsedRange,
4923 aRangesToDelete);
4924 if (MOZ_UNLIKELY(!deleteSelectionTransaction)) {
4925 NS_WARNING("EditorBase::CreateTransactionForDeleteSelection() failed");
4926 return Err(NS_ERROR_FAILURE);
4929 // XXX This is odd, this assumes that there are no multiple collapsed
4930 // ranges in `Selection`, but it's possible scenario.
4931 // XXX This loop looks slow, but it's rarely so because of multiple
4932 // selection is not used so many times.
4933 nsCOMPtr<nsIContent> deleteContent;
4934 uint32_t deleteCharOffset = 0;
4935 for (const OwningNonNull<EditTransactionBase>& transactionBase :
4936 Reversed(deleteSelectionTransaction->ChildTransactions())) {
4937 if (DeleteTextTransaction* deleteTextTransaction =
4938 transactionBase->GetAsDeleteTextTransaction()) {
4939 deleteContent = deleteTextTransaction->GetText();
4940 deleteCharOffset = deleteTextTransaction->Offset();
4941 break;
4943 if (DeleteNodeTransaction* deleteNodeTransaction =
4944 transactionBase->GetAsDeleteNodeTransaction()) {
4945 deleteContent = deleteNodeTransaction->GetContent();
4946 break;
4950 RefPtr<CharacterData> deleteCharData =
4951 CharacterData::FromNodeOrNull(deleteContent);
4952 IgnoredErrorResult ignoredError;
4953 AutoEditSubActionNotifier startToHandleEditSubAction(
4954 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount,
4955 ignoredError);
4956 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4957 return Err(ignoredError.StealNSResult());
4959 NS_WARNING_ASSERTION(
4960 !ignoredError.Failed(),
4961 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4963 if (IsHTMLEditor()) {
4964 if (!deleteContent) {
4965 // XXX We may remove multiple ranges in the following. Therefore,
4966 // this must have a bug since we only add the first range into
4967 // the changed range.
4968 TopLevelEditSubActionDataRef().WillDeleteRange(
4969 *this, aRangesToDelete.GetFirstRangeStartPoint<EditorRawDOMPoint>(),
4970 aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>());
4971 } else if (!deleteCharData) {
4972 TopLevelEditSubActionDataRef().WillDeleteContent(*this, *deleteContent);
4976 // Notify nsIEditActionListener::WillDelete[Selection|Text]
4977 if (!mActionListeners.IsEmpty()) {
4978 if (!deleteContent) {
4979 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
4980 AutoTArray<RefPtr<nsRange>, 8> rangesToDelete(
4981 aRangesToDelete.CloneRanges<RefPtr>());
4982 AutoActionListenerArray listeners(mActionListeners.Clone());
4983 for (auto& listener : listeners) {
4984 DebugOnly<nsresult> rvIgnored =
4985 listener->WillDeleteRanges(rangesToDelete);
4986 NS_WARNING_ASSERTION(
4987 NS_SUCCEEDED(rvIgnored),
4988 "nsIEditActionListener::WillDeleteRanges() failed, but ignored");
4989 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(),
4990 "nsIEditActionListener::WillDeleteRanges() "
4991 "must not destroy the editor");
4993 } else if (deleteCharData) {
4994 AutoActionListenerArray listeners(mActionListeners.Clone());
4995 for (auto& listener : listeners) {
4996 // XXX Why don't we notify listeners of actual length?
4997 DebugOnly<nsresult> rvIgnored =
4998 listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
4999 NS_WARNING_ASSERTION(
5000 NS_SUCCEEDED(rvIgnored),
5001 "nsIEditActionListener::WillDeleteText() failed, but ignored");
5002 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(),
5003 "nsIEditActionListener::WillDeleteText() must "
5004 "not destroy the editor");
5009 // Delete the specified amount
5010 nsresult rv = DoTransactionInternal(deleteSelectionTransaction);
5011 // I'm not sure whether we should keep notifying edit action listeners or
5012 // stop doing it. For now, just keep traditional behavior.
5013 bool destroyedByTransaction = Destroyed();
5014 NS_WARNING_ASSERTION(destroyedByTransaction || NS_SUCCEEDED(rv),
5015 "EditorBase::DoTransactionInternal() failed");
5017 if (IsHTMLEditor() && deleteCharData) {
5018 MOZ_ASSERT(deleteContent);
5019 TopLevelEditSubActionDataRef().DidDeleteText(
5020 *this, EditorRawDOMPoint(deleteContent));
5023 if (mTextServicesDocument && NS_SUCCEEDED(rv) && deleteContent &&
5024 !deleteCharData) {
5025 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
5026 textServicesDocument->DidDeleteContent(*deleteContent);
5027 MOZ_ASSERT(
5028 destroyedByTransaction || !Destroyed(),
5029 "TextServicesDocument::DidDeleteContent() must not destroy the editor");
5032 if (!mActionListeners.IsEmpty() && deleteContent && !deleteCharData) {
5033 for (auto& listener : mActionListeners.Clone()) {
5034 DebugOnly<nsresult> rvIgnored =
5035 listener->DidDeleteNode(deleteContent, rv);
5036 NS_WARNING_ASSERTION(
5037 NS_SUCCEEDED(rvIgnored),
5038 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
5039 MOZ_DIAGNOSTIC_ASSERT(
5040 destroyedByTransaction || !Destroyed(),
5041 "nsIEditActionListener::DidDeleteNode() must not destroy the editor");
5045 if (NS_WARN_IF(destroyedByTransaction)) {
5046 return Err(NS_ERROR_EDITOR_DESTROYED);
5048 if (NS_FAILED(rv)) {
5049 return Err(rv);
5052 EditorDOMPoint pointToPutCaret =
5053 deleteSelectionTransaction->SuggestPointToPutCaret();
5054 if (IsHTMLEditor() && aStripWrappers == nsIEditor::eStrip) {
5055 const nsCOMPtr<nsIContent> anchorContent =
5056 pointToPutCaret.GetContainerAs<nsIContent>();
5057 if (MOZ_LIKELY(anchorContent) &&
5058 MOZ_LIKELY(HTMLEditUtils::IsSimplyEditableNode(*anchorContent)) &&
5059 // FIXME: Perhaps, this should use `HTMLEditor::IsEmptyNode` instead.
5060 !anchorContent->Length()) {
5061 AutoTrackDOMPoint trackPoint(RangeUpdaterRef(), &pointToPutCaret);
5062 nsresult rv =
5063 MOZ_KnownLive(AsHTMLEditor())
5064 ->RemoveEmptyInclusiveAncestorInlineElements(*anchorContent);
5065 if (NS_FAILED(rv)) {
5066 NS_WARNING(
5067 "HTMLEditor::RemoveEmptyInclusiveAncestorInlineElements() "
5068 "failed");
5069 return Err(rv);
5074 return CaretPoint(std::move(pointToPutCaret));
5077 already_AddRefed<Element> EditorBase::CreateHTMLContent(
5078 const nsAtom* aTag) const {
5079 MOZ_ASSERT(aTag);
5081 RefPtr<Document> document = GetDocument();
5082 if (NS_WARN_IF(!document)) {
5083 return nullptr;
5086 // XXX Wallpaper over editor bug (editor tries to create elements with an
5087 // empty nodename).
5088 if (aTag == nsGkAtoms::_empty) {
5089 NS_ERROR(
5090 "Don't pass an empty tag to EditorBase::CreateHTMLContent, "
5091 "check caller.");
5092 return nullptr;
5095 return document->CreateElem(nsDependentAtomString(aTag), nullptr,
5096 kNameSpaceID_XHTML);
5099 already_AddRefed<nsTextNode> EditorBase::CreateTextNode(
5100 const nsAString& aData) const {
5101 MOZ_ASSERT(IsEditActionDataAvailable());
5103 Document* document = GetDocument();
5104 if (NS_WARN_IF(!document)) {
5105 return nullptr;
5107 RefPtr<nsTextNode> text = document->CreateEmptyTextNode();
5108 text->MarkAsMaybeModifiedFrequently();
5109 if (IsPasswordEditor()) {
5110 text->MarkAsMaybeMasked();
5112 // Don't notify; this node is still being created.
5113 DebugOnly<nsresult> rvIgnored = text->SetText(aData, false);
5114 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5115 "Text::SetText() failed, but ignored");
5116 return text.forget();
5119 NS_IMETHODIMP EditorBase::SetAttributeOrEquivalent(Element* aElement,
5120 const nsAString& aAttribute,
5121 const nsAString& aValue,
5122 bool aSuppressTransaction) {
5123 if (NS_WARN_IF(!aElement)) {
5124 return NS_ERROR_NULL_POINTER;
5127 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
5128 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
5129 if (NS_FAILED(rv)) {
5130 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5131 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
5132 return EditorBase::ToGenericNSResult(rv);
5135 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
5136 rv = SetAttributeOrEquivalent(aElement, attribute, aValue,
5137 aSuppressTransaction);
5138 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5139 "EditorBase::SetAttributeOrEquivalent() failed");
5140 return EditorBase::ToGenericNSResult(rv);
5143 NS_IMETHODIMP EditorBase::RemoveAttributeOrEquivalent(
5144 Element* aElement, const nsAString& aAttribute, bool aSuppressTransaction) {
5145 if (NS_WARN_IF(!aElement)) {
5146 return NS_ERROR_NULL_POINTER;
5149 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
5150 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
5151 if (NS_FAILED(rv)) {
5152 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5153 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
5154 return EditorBase::ToGenericNSResult(rv);
5157 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
5158 rv = RemoveAttributeOrEquivalent(aElement, attribute, aSuppressTransaction);
5159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5160 "EditorBase::RemoveAttributeOrEquivalent() failed");
5161 return EditorBase::ToGenericNSResult(rv);
5164 void EditorBase::HandleKeyPressEventInReadOnlyMode(
5165 WidgetKeyboardEvent& aKeyboardEvent) const {
5166 MOZ_ASSERT(IsReadonly());
5167 MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress);
5169 switch (aKeyboardEvent.mKeyCode) {
5170 case NS_VK_BACK:
5171 // If it's a `Backspace` key, let's consume it because it may be mapped
5172 // to "Back" of the history navigation. So, it's possible that user
5173 // tries to delete a character with `Backspace` even in the read-only
5174 // editor.
5175 aKeyboardEvent.PreventDefault();
5176 break;
5178 // XXX How about space key (page up and page down in browser navigation)?
5181 nsresult EditorBase::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
5182 MOZ_ASSERT(!IsReadonly());
5183 MOZ_ASSERT(aKeyboardEvent);
5184 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress);
5186 // NOTE: When you change this method, you should also change:
5187 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
5188 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
5190 // And also when you add new key handling, you need to change the subclass's
5191 // HandleKeyPressEvent()'s switch statement.
5193 switch (aKeyboardEvent->mKeyCode) {
5194 case NS_VK_META:
5195 case NS_VK_WIN:
5196 case NS_VK_SHIFT:
5197 case NS_VK_CONTROL:
5198 case NS_VK_ALT:
5199 MOZ_ASSERT_UNREACHABLE(
5200 "eKeyPress event shouldn't be fired for modifier keys");
5201 return NS_ERROR_UNEXPECTED;
5203 case NS_VK_BACK: {
5204 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
5205 aKeyboardEvent->IsMeta()) {
5206 return NS_OK;
5208 DebugOnly<nsresult> rvIgnored =
5209 DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip);
5210 aKeyboardEvent->PreventDefault();
5211 NS_WARNING_ASSERTION(
5212 NS_SUCCEEDED(rvIgnored),
5213 "EditorBase::DeleteSelectionAsAction() failed, but ignored");
5214 return NS_OK;
5216 case NS_VK_DELETE: {
5217 // on certain platforms (such as windows) the shift key
5218 // modifies what delete does (cmd_cut in this case).
5219 // bailing here to allow the keybindings to do the cut.
5220 if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
5221 aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta()) {
5222 return NS_OK;
5224 DebugOnly<nsresult> rvIgnored =
5225 DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip);
5226 aKeyboardEvent->PreventDefault();
5227 NS_WARNING_ASSERTION(
5228 NS_SUCCEEDED(rvIgnored),
5229 "EditorBase::DeleteSelectionAsAction() failed, but ignored");
5230 return NS_OK;
5233 return NS_OK;
5236 nsresult EditorBase::OnInputText(const nsAString& aStringToInsert) {
5237 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
5238 MOZ_ASSERT(!aStringToInsert.IsVoid());
5239 editActionData.SetData(aStringToInsert);
5240 // FYI: For conforming to current UI Events spec, we should dispatch
5241 // "beforeinput" event before "keypress" event, but here is in a
5242 // "keypress" event listener. However, the other browsers dispatch
5243 // "beforeinput" event after "keypress" event. Therefore, it makes
5244 // sense to follow the other browsers. Spec issue:
5245 // https://github.com/w3c/uievents/issues/220
5246 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
5247 if (NS_FAILED(rv)) {
5248 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5249 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
5250 return EditorBase::ToGenericNSResult(rv);
5253 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
5254 ScrollSelectionIntoView::Yes,
5255 __FUNCTION__);
5256 rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
5257 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5258 "EditorBase::InsertTextAsSubAction() failed");
5259 return EditorBase::ToGenericNSResult(rv);
5262 nsresult EditorBase::ReplaceTextAsAction(
5263 const nsAString& aString, nsRange* aReplaceRange,
5264 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
5265 nsIPrincipal* aPrincipal) {
5266 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound);
5267 MOZ_ASSERT_IF(!aReplaceRange, IsTextEditor());
5269 AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText,
5270 aPrincipal);
5271 if (NS_WARN_IF(!editActionData.CanHandle())) {
5272 return NS_ERROR_NOT_INITIALIZED;
5274 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) {
5275 editActionData.MakeBeforeInputEventNonCancelable();
5278 if (IsTextEditor()) {
5279 editActionData.SetData(aString);
5280 } else {
5281 editActionData.InitializeDataTransfer(aString);
5282 RefPtr<StaticRange> targetRange;
5283 if (aReplaceRange) {
5284 // Compute offset of the range before dispatching `beforeinput` event
5285 // because it may be referred after the DOM tree is changed and the
5286 // range may have not computed the offset yet.
5287 targetRange = StaticRange::Create(
5288 aReplaceRange->GetStartContainer(), aReplaceRange->StartOffset(),
5289 aReplaceRange->GetEndContainer(), aReplaceRange->EndOffset(),
5290 IgnoreErrors());
5291 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
5292 "StaticRange::Create() failed");
5293 } else {
5294 Element* editingHost = AsHTMLEditor()->ComputeEditingHost();
5295 NS_WARNING_ASSERTION(editingHost,
5296 "No active editing host, no target ranges");
5297 if (editingHost) {
5298 targetRange = StaticRange::Create(
5299 editingHost, 0, editingHost, editingHost->Length(), IgnoreErrors());
5300 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
5301 "StaticRange::Create() failed");
5304 if (targetRange && targetRange->IsPositioned()) {
5305 editActionData.AppendTargetRange(*targetRange);
5309 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
5310 if (NS_FAILED(rv)) {
5311 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5312 "MaybeDispatchBeforeInputEvent() failed");
5313 return EditorBase::ToGenericNSResult(rv);
5316 AutoPlaceholderBatch treatAsOneTransaction(
5317 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
5319 // This should emulates inserting text for better undo/redo behavior.
5320 IgnoredErrorResult ignoredError;
5321 AutoEditSubActionNotifier startToHandleEditSubAction(
5322 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
5323 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5324 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
5326 NS_WARNING_ASSERTION(
5327 !ignoredError.Failed(),
5328 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5330 if (!aReplaceRange) {
5331 // Use fast path if we're `TextEditor` because it may be in a hot path.
5332 if (IsTextEditor()) {
5333 nsresult rv = MOZ_KnownLive(AsTextEditor())->SetTextAsSubAction(aString);
5334 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5335 "TextEditor::SetTextAsSubAction() failed");
5336 return EditorBase::ToGenericNSResult(rv);
5339 MOZ_ASSERT_UNREACHABLE("Setting value of `HTMLEditor` isn't supported");
5340 return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE);
5343 if (aString.IsEmpty() && aReplaceRange->Collapsed()) {
5344 NS_WARNING("Setting value was empty and replaced range was empty");
5345 return NS_OK;
5348 // Note that do not notify selectionchange caused by selecting all text
5349 // because it's preparation of our delete implementation so web apps
5350 // shouldn't receive such selectionchange before the first mutation.
5351 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
5353 // Select the range but as far as possible, we should not create new range
5354 // even if it's part of special Selection.
5355 ErrorResult error;
5356 SelectionRef().RemoveAllRanges(error);
5357 if (error.Failed()) {
5358 NS_WARNING("Selection::RemoveAllRanges() failed");
5359 return error.StealNSResult();
5361 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*aReplaceRange,
5362 error);
5363 if (error.Failed()) {
5364 NS_WARNING("Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
5365 return error.StealNSResult();
5368 rv = ReplaceSelectionAsSubAction(aString);
5369 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5370 "EditorBase::ReplaceSelectionAsSubAction() failed");
5371 return EditorBase::ToGenericNSResult(rv);
5374 nsresult EditorBase::ReplaceSelectionAsSubAction(const nsAString& aString) {
5375 if (aString.IsEmpty()) {
5376 nsresult rv = DeleteSelectionAsSubAction(
5377 nsIEditor::eNone,
5378 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
5379 NS_WARNING_ASSERTION(
5380 NS_SUCCEEDED(rv),
5381 "EditorBase::DeleteSelectionAsSubAction(eNone) failed");
5382 return rv;
5385 nsresult rv = InsertTextAsSubAction(aString, SelectionHandling::Delete);
5386 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5387 "EditorBase::InsertTextAsSubAction() failed");
5388 return rv;
5391 nsresult EditorBase::HandleInlineSpellCheck(
5392 const EditorDOMPoint& aPreviouslySelectedStart,
5393 const AbstractRange* aRange) {
5394 MOZ_ASSERT(IsEditActionDataAvailable());
5396 if (!mInlineSpellChecker) {
5397 return NS_OK;
5399 nsresult rv = mInlineSpellChecker->SpellCheckAfterEditorChange(
5400 GetTopLevelEditSubAction(), SelectionRef(),
5401 aPreviouslySelectedStart.GetContainer(),
5402 aPreviouslySelectedStart.Offset(),
5403 aRange ? aRange->GetStartContainer() : nullptr,
5404 aRange ? aRange->StartOffset() : 0,
5405 aRange ? aRange->GetEndContainer() : nullptr,
5406 aRange ? aRange->EndOffset() : 0);
5407 NS_WARNING_ASSERTION(
5408 NS_SUCCEEDED(rv),
5409 "mozInlineSpellChecker::SpellCheckAfterEditorChange() failed");
5410 return rv;
5413 Element* EditorBase::FindSelectionRoot(const nsINode& aNode) const {
5414 return GetRoot();
5417 void EditorBase::InitializeSelectionAncestorLimit(
5418 nsIContent& aAncestorLimit) const {
5419 MOZ_ASSERT(IsEditActionDataAvailable());
5421 SelectionRef().SetAncestorLimiter(&aAncestorLimit);
5424 nsresult EditorBase::InitializeSelection(
5425 const nsINode& aOriginalEventTargetNode) {
5426 MOZ_ASSERT(IsEditActionDataAvailable());
5428 nsCOMPtr<nsIContent> selectionRootContent =
5429 FindSelectionRoot(aOriginalEventTargetNode);
5430 if (!selectionRootContent) {
5431 return NS_OK;
5434 nsCOMPtr<nsISelectionController> selectionController =
5435 GetSelectionController();
5436 if (NS_WARN_IF(!selectionController)) {
5437 return NS_ERROR_FAILURE;
5440 // Init the caret
5441 RefPtr<nsCaret> caret = GetCaret();
5442 if (NS_WARN_IF(!caret)) {
5443 return NS_ERROR_FAILURE;
5445 caret->SetSelection(&SelectionRef());
5446 DebugOnly<nsresult> rvIgnored =
5447 selectionController->SetCaretReadOnly(IsReadonly());
5448 NS_WARNING_ASSERTION(
5449 NS_SUCCEEDED(rvIgnored),
5450 "nsISelectionController::SetCaretReadOnly() failed, but ignored");
5451 rvIgnored = selectionController->SetCaretEnabled(true);
5452 NS_WARNING_ASSERTION(
5453 NS_SUCCEEDED(rvIgnored),
5454 "nsISelectionController::SetCaretEnabled() failed, but ignored");
5455 // NOTE(emilio): It's important for this call to be after
5456 // SetCaretEnabled(true), since that would override mIgnoreUserModify to true.
5458 // Also, make sure to always ignore it for designMode, since that effectively
5459 // overrides everything and we allow to edit stuff with
5460 // contenteditable="false" subtrees in such a document.
5461 caret->SetIgnoreUserModify(aOriginalEventTargetNode.IsInDesignMode());
5463 // Init selection
5464 rvIgnored =
5465 selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
5466 NS_WARNING_ASSERTION(
5467 NS_SUCCEEDED(rvIgnored),
5468 "nsISelectionController::SetSelectionFlags() failed, but ignored");
5470 selectionController->SelectionWillTakeFocus();
5472 // If the computed selection root isn't root content, we should set it
5473 // as selection ancestor limit. However, if that is root element, it means
5474 // there is not limitation of the selection, then, we must set nullptr.
5475 // NOTE: If we set a root element to the ancestor limit, some selection
5476 // methods don't work fine.
5477 if (selectionRootContent->GetParent()) {
5478 InitializeSelectionAncestorLimit(*selectionRootContent);
5479 } else {
5480 SelectionRef().SetAncestorLimiter(nullptr);
5483 // If there is composition when this is called, we may need to restore IME
5484 // selection because if the editor is reframed, this already forgot IME
5485 // selection and the transaction.
5486 if (mComposition && mComposition->IsMovingToNewTextNode()) {
5487 MOZ_DIAGNOSTIC_ASSERT(IsTextEditor());
5488 if (NS_WARN_IF(!IsTextEditor())) {
5489 return NS_ERROR_UNEXPECTED;
5491 // We need to look for the new text node from current selection.
5492 // XXX If selection is changed during reframe, this doesn't work well!
5493 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
5494 if (NS_WARN_IF(!firstRange)) {
5495 return NS_ERROR_FAILURE;
5497 EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
5498 EditorRawDOMPoint betterInsertionPoint =
5499 AsTextEditor()->FindBetterInsertionPoint(atStartOfFirstRange);
5500 RefPtr<Text> textNode = betterInsertionPoint.GetContainerAs<Text>();
5501 MOZ_ASSERT(textNode,
5502 "There must be text node if composition string is not empty");
5503 if (textNode) {
5504 MOZ_ASSERT(textNode->Length() >= mComposition->XPEndOffsetInTextNode(),
5505 "The text node must be different from the old text node");
5506 RefPtr<TextRangeArray> ranges = mComposition->GetRanges();
5507 DebugOnly<nsresult> rvIgnored = CompositionTransaction::SetIMESelection(
5508 *this, textNode, mComposition->XPOffsetInTextNode(),
5509 mComposition->XPLengthInTextNode(), ranges);
5510 NS_WARNING_ASSERTION(
5511 NS_SUCCEEDED(rvIgnored),
5512 "CompositionTransaction::SetIMESelection() failed, but ignored");
5513 mComposition->OnUpdateCompositionInEditor(
5514 mComposition->String(), *textNode,
5515 mComposition->XPOffsetInTextNode());
5519 return NS_OK;
5522 nsresult EditorBase::FinalizeSelection() {
5523 nsCOMPtr<nsISelectionController> selectionController =
5524 GetSelectionController();
5525 if (NS_WARN_IF(!selectionController)) {
5526 return NS_ERROR_FAILURE;
5529 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
5530 if (NS_WARN_IF(!editActionData.CanHandle())) {
5531 return NS_ERROR_NOT_INITIALIZED;
5534 SelectionRef().SetAncestorLimiter(nullptr);
5536 if (NS_WARN_IF(!GetPresShell())) {
5537 return NS_ERROR_NOT_INITIALIZED;
5540 if (RefPtr<nsCaret> caret = GetCaret()) {
5541 caret->SetIgnoreUserModify(true);
5542 DebugOnly<nsresult> rvIgnored = selectionController->SetCaretEnabled(false);
5543 NS_WARNING_ASSERTION(
5544 NS_SUCCEEDED(rvIgnored),
5545 "nsISelectionController::SetCaretEnabled(false) failed, but ignored");
5548 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
5549 if (NS_WARN_IF(!focusManager)) {
5550 return NS_ERROR_NOT_INITIALIZED;
5552 // TODO: Running script from here makes harder to handle blur events. We
5553 // should do this asynchronously.
5554 focusManager->UpdateCaretForCaretBrowsingMode();
5555 if (Element* rootElement = GetExposedRoot()) {
5556 if (rootElement->OwnerDoc()->GetUnretargetedFocusedContent() !=
5557 rootElement) {
5558 selectionController->SelectionWillLoseFocus();
5559 } else {
5560 // We leave this selection as the focused one. When the focus returns, it
5561 // either returns to us (nothing to do), or it returns to something else,
5562 // and nsDocumentViewerFocusListener::HandleEvent fixes it up.
5565 return NS_OK;
5568 Element* EditorBase::GetExposedRoot() const {
5569 Element* rootElement = GetRoot();
5570 if (!rootElement || !rootElement->IsInNativeAnonymousSubtree()) {
5571 return rootElement;
5573 return Element::FromNodeOrNull(
5574 rootElement->GetClosestNativeAnonymousSubtreeRootParentOrHost());
5577 nsresult EditorBase::DetermineCurrentDirection() {
5578 // Get the current root direction from its frame
5579 Element* rootElement = GetExposedRoot();
5580 if (NS_WARN_IF(!rootElement)) {
5581 return NS_ERROR_FAILURE;
5584 // If we don't have an explicit direction, determine our direction
5585 // from the content's direction
5586 if (!IsRightToLeft() && !IsLeftToRight()) {
5587 nsIFrame* frameForRootElement = rootElement->GetPrimaryFrame();
5588 if (NS_WARN_IF(!frameForRootElement)) {
5589 return NS_ERROR_FAILURE;
5592 // Set the flag here, to enable us to use the same code path below.
5593 // It will be flipped before returning from the function.
5594 if (frameForRootElement->StyleVisibility()->mDirection ==
5595 StyleDirection::Rtl) {
5596 mFlags |= nsIEditor::eEditorRightToLeft;
5597 } else {
5598 mFlags |= nsIEditor::eEditorLeftToRight;
5602 return NS_OK;
5605 nsresult EditorBase::ToggleTextDirectionAsAction(nsIPrincipal* aPrincipal) {
5606 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection,
5607 aPrincipal);
5608 if (NS_WARN_IF(!editActionData.CanHandle())) {
5609 return NS_ERROR_NOT_INITIALIZED;
5612 nsresult rv = DetermineCurrentDirection();
5613 if (NS_FAILED(rv)) {
5614 NS_WARNING("EditorBase::DetermineCurrentDirection() failed");
5615 return EditorBase::ToGenericNSResult(rv);
5618 MOZ_ASSERT(IsRightToLeft() || IsLeftToRight());
5619 // Note that we need to consider new direction before dispatching
5620 // "beforeinput" event since "beforeinput" event listener may change it
5621 // but not canceled.
5622 TextDirection newDirection =
5623 IsRightToLeft() ? TextDirection::eLTR : TextDirection::eRTL;
5624 editActionData.SetData(IsRightToLeft() ? u"ltr"_ns : u"rtl"_ns);
5626 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but
5627 // dispatches input event.
5628 rv = editActionData.MaybeDispatchBeforeInputEvent();
5629 if (NS_FAILED(rv)) {
5630 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5631 "MaybeDispatchBeforeInputEvent() failed");
5632 return EditorBase::ToGenericNSResult(rv);
5635 rv = SetTextDirectionTo(newDirection);
5636 if (NS_FAILED(rv)) {
5637 NS_WARNING("EditorBase::SetTextDirectionTo() failed");
5638 return EditorBase::ToGenericNSResult(rv);
5641 editActionData.MarkAsHandled();
5643 // XXX When we don't change the text direction, do we really need to
5644 // dispatch input event?
5645 DispatchInputEvent();
5647 return NS_OK;
5650 void EditorBase::SwitchTextDirectionTo(TextDirection aTextDirection) {
5651 MOZ_ASSERT(aTextDirection == TextDirection::eLTR ||
5652 aTextDirection == TextDirection::eRTL);
5654 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection);
5655 if (NS_WARN_IF(!editActionData.CanHandle())) {
5656 return;
5659 nsresult rv = DetermineCurrentDirection();
5660 if (NS_WARN_IF(NS_FAILED(rv))) {
5661 return;
5664 editActionData.SetData(aTextDirection == TextDirection::eLTR ? u"ltr"_ns
5665 : u"rtl"_ns);
5667 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but
5668 // dispatches input event.
5669 rv = editActionData.MaybeDispatchBeforeInputEvent();
5670 if (NS_FAILED(rv)) {
5671 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5672 "MaybeDispatchBeforeInputEvent() failed");
5673 return;
5676 if ((aTextDirection == TextDirection::eLTR && IsRightToLeft()) ||
5677 (aTextDirection == TextDirection::eRTL && IsLeftToRight())) {
5678 // Do it only when the direction is still different from the original
5679 // new direction. Note that "beforeinput" event listener may have already
5680 // changed the direction here, but they may not cancel the event.
5681 nsresult rv = SetTextDirectionTo(aTextDirection);
5682 if (NS_FAILED(rv)) {
5683 NS_WARNING("EditorBase::SetTextDirectionTo() failed");
5684 return;
5688 editActionData.MarkAsHandled();
5690 // XXX When we don't change the text direction, do we really need to
5691 // dispatch input event?
5692 DispatchInputEvent();
5695 nsresult EditorBase::SetTextDirectionTo(TextDirection aTextDirection) {
5696 Element* const editingHostOrTextControlElement =
5697 IsHTMLEditor() ? AsHTMLEditor()->ComputeEditingHost(
5698 HTMLEditor::LimitInBodyElement::No)
5699 : GetExposedRoot();
5700 if (!editingHostOrTextControlElement) { // Don't warn, HTMLEditor may have no
5701 // active editing host
5702 return NS_OK;
5705 if (aTextDirection == TextDirection::eLTR) {
5706 NS_ASSERTION(!IsLeftToRight(), "Unexpected mutually exclusive flag");
5707 mFlags &= ~nsIEditor::eEditorRightToLeft;
5708 mFlags |= nsIEditor::eEditorLeftToRight;
5709 nsresult rv = editingHostOrTextControlElement->SetAttr(
5710 kNameSpaceID_None, nsGkAtoms::dir, u"ltr"_ns, true);
5711 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5712 "Element::SetAttr(nsGkAtoms::dir, ltr) failed");
5713 return rv;
5716 if (aTextDirection == TextDirection::eRTL) {
5717 NS_ASSERTION(!IsRightToLeft(), "Unexpected mutually exclusive flag");
5718 mFlags |= nsIEditor::eEditorRightToLeft;
5719 mFlags &= ~nsIEditor::eEditorLeftToRight;
5720 nsresult rv = editingHostOrTextControlElement->SetAttr(
5721 kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, true);
5722 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5723 "Element::SetAttr(nsGkAtoms::dir, rtl) failed");
5724 return rv;
5727 return NS_OK;
5730 Element* EditorBase::GetFocusedElement() const {
5731 EventTarget* eventTarget = GetDOMEventTarget();
5732 if (!eventTarget) {
5733 return nullptr;
5736 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5737 if (NS_WARN_IF(!focusManager)) {
5738 return nullptr;
5741 Element* focusedElement = focusManager->GetFocusedElement();
5742 MOZ_ASSERT((focusedElement == eventTarget) ==
5743 SameCOMIdentity(focusedElement, eventTarget));
5745 return (focusedElement == eventTarget) ? focusedElement : nullptr;
5748 bool EditorBase::IsActiveInDOMWindow() const {
5749 EventTarget* piTarget = GetDOMEventTarget();
5750 if (!piTarget) {
5751 return false;
5754 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5755 if (NS_WARN_IF(!focusManager)) {
5756 return false; // Do we need to check the singleton instance??
5759 Document* document = GetDocument();
5760 if (NS_WARN_IF(!document)) {
5761 return false;
5763 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
5764 nsCOMPtr<nsPIDOMWindowOuter> win;
5765 nsIContent* content = nsFocusManager::GetFocusedDescendant(
5766 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win));
5767 return SameCOMIdentity(content, piTarget);
5770 bool EditorBase::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const {
5771 // If the event is trusted, the event should always cause input.
5772 if (NS_WARN_IF(!aGUIEvent)) {
5773 return false;
5776 // If this is dispatched by using cordinates but this editor doesn't have
5777 // focus, we shouldn't handle it.
5778 if (aGUIEvent->IsUsingCoordinates() && !GetFocusedElement()) {
5779 return false;
5782 // If a composition event isn't dispatched via widget, we need to ignore them
5783 // since they cannot be managed by TextComposition. E.g., the event was
5784 // created by chrome JS.
5785 // Note that if we allow to handle such events, editor may be confused by
5786 // strange event order.
5787 bool needsWidget = false;
5788 switch (aGUIEvent->mMessage) {
5789 case eUnidentifiedEvent:
5790 // If events are not created with proper event interface, their message
5791 // are initialized with eUnidentifiedEvent. Let's ignore such event.
5792 return false;
5793 case eCompositionStart:
5794 case eCompositionEnd:
5795 case eCompositionUpdate:
5796 case eCompositionChange:
5797 case eCompositionCommitAsIs:
5798 // Don't allow composition events whose internal event are not
5799 // WidgetCompositionEvent.
5800 if (!aGUIEvent->AsCompositionEvent()) {
5801 return false;
5803 needsWidget = true;
5804 break;
5805 default:
5806 break;
5808 if (needsWidget && !aGUIEvent->mWidget) {
5809 return false;
5812 // Accept all trusted events.
5813 if (aGUIEvent->IsTrusted()) {
5814 return true;
5817 // Ignore untrusted mouse event.
5818 // XXX Why are we handling other untrusted input events?
5819 if (aGUIEvent->AsMouseEventBase()) {
5820 return false;
5823 // Otherwise, we shouldn't handle any input events when we're not an active
5824 // element of the DOM window.
5825 return IsActiveInDOMWindow();
5828 nsresult EditorBase::FlushPendingSpellCheck() {
5829 // If the spell check skip flag is still enabled from creation time,
5830 // disable it because focused editors are allowed to spell check.
5831 if (!ShouldSkipSpellCheck()) {
5832 return NS_OK;
5834 MOZ_ASSERT(!IsHTMLEditor(), "HTMLEditor should not has pending spell checks");
5835 nsresult rv = RemoveFlags(nsIEditor::eEditorSkipSpellCheck);
5836 if (NS_WARN_IF(Destroyed())) {
5837 return NS_ERROR_EDITOR_DESTROYED;
5839 NS_WARNING_ASSERTION(
5840 NS_SUCCEEDED(rv),
5841 "EditorBase::RemoveFlags(nsIEditor::eEditorSkipSpellCheck) failed");
5842 return rv;
5845 bool EditorBase::CanKeepHandlingFocusEvent(
5846 const nsINode& aOriginalEventTargetNode) const {
5847 if (MOZ_UNLIKELY(!IsListeningToEvents() || Destroyed())) {
5848 return false;
5851 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5852 if (MOZ_UNLIKELY(!focusManager)) {
5853 return false;
5856 // If the event target is document mode, we only need to handle the focus
5857 // event when the document is still in designMode. Otherwise, the
5858 // mode has been disabled by somebody while we're handling the focus event.
5859 if (aOriginalEventTargetNode.IsDocument()) {
5860 return IsHTMLEditor() && aOriginalEventTargetNode.IsInDesignMode();
5862 MOZ_ASSERT(aOriginalEventTargetNode.IsContent());
5864 // If nobody has focus, the focus event target has been blurred by somebody
5865 // else. So the editor shouldn't initialize itself to start to handle
5866 // anything.
5867 if (!focusManager->GetFocusedElement()) {
5868 return false;
5871 // If there's an HTMLEditor registered in the target document and we
5872 // are not that HTMLEditor (for cases like nested documents), let
5873 // that HTMLEditor to handle the focus event.
5874 if (IsHTMLEditor()) {
5875 const HTMLEditor* precedentHTMLEditor =
5876 aOriginalEventTargetNode.OwnerDoc()->GetHTMLEditor();
5878 if (precedentHTMLEditor && precedentHTMLEditor != this) {
5879 return false;
5883 const nsIContent* exposedTargetContent =
5884 aOriginalEventTargetNode.AsContent()
5885 ->FindFirstNonChromeOnlyAccessContent();
5886 const nsIContent* exposedFocusedContent =
5887 focusManager->GetFocusedElement()->FindFirstNonChromeOnlyAccessContent();
5888 return exposedTargetContent && exposedFocusedContent &&
5889 exposedTargetContent == exposedFocusedContent;
5892 nsresult EditorBase::OnFocus(const nsINode& aOriginalEventTargetNode) {
5893 MOZ_ASSERT(IsEditActionDataAvailable());
5895 InitializeSelection(aOriginalEventTargetNode);
5896 mSpellCheckerDictionaryUpdated = false;
5897 if (mInlineSpellChecker && CanEnableSpellCheck()) {
5898 DebugOnly<nsresult> rvIgnored =
5899 mInlineSpellChecker->UpdateCurrentDictionary();
5900 NS_WARNING_ASSERTION(
5901 NS_SUCCEEDED(rvIgnored),
5902 "mozInlineSpellCHecker::UpdateCurrentDictionary() failed, but ignored");
5903 mSpellCheckerDictionaryUpdated = true;
5905 // XXX Why don't we stop handling focus with the spell checker immediately
5906 // after calling InitializeSelection?
5907 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
5908 return NS_ERROR_EDITOR_DESTROYED;
5911 const RefPtr<Element> focusedElement = GetFocusedElement();
5912 RefPtr<nsPresContext> presContext =
5913 focusedElement ? focusedElement->GetPresContext(
5914 Element::PresContextFor::eForComposedDoc)
5915 : GetPresContext();
5916 if (NS_WARN_IF(!presContext)) {
5917 return NS_ERROR_FAILURE;
5919 IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this);
5921 return NS_OK;
5924 void EditorBase::HideCaret(bool aHide) {
5925 if (mHidingCaret == aHide) {
5926 return;
5929 RefPtr<nsCaret> caret = GetCaret();
5930 if (NS_WARN_IF(!caret)) {
5931 return;
5934 mHidingCaret = aHide;
5935 if (aHide) {
5936 caret->AddForceHide();
5937 } else {
5938 caret->RemoveForceHide();
5942 NS_IMETHODIMP EditorBase::Unmask(uint32_t aStart, int64_t aEnd,
5943 uint32_t aTimeout, uint8_t aArgc) {
5944 if (NS_WARN_IF(!IsPasswordEditor())) {
5945 return NS_ERROR_NOT_AVAILABLE;
5947 if (NS_WARN_IF(aArgc >= 1 && aStart == UINT32_MAX) ||
5948 NS_WARN_IF(aArgc >= 2 && aEnd == 0) ||
5949 NS_WARN_IF(aArgc >= 2 && aEnd > 0 && aStart >= aEnd)) {
5950 return NS_ERROR_INVALID_ARG;
5953 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
5954 if (NS_WARN_IF(!editActionData.CanHandle())) {
5955 return NS_ERROR_NOT_INITIALIZED;
5958 uint32_t start = aArgc < 1 ? 0 : aStart;
5959 uint32_t length = aArgc < 2 || aEnd < 0 ? UINT32_MAX : aEnd - start;
5960 uint32_t timeout = aArgc < 3 ? 0 : aTimeout;
5961 nsresult rv = MOZ_KnownLive(AsTextEditor())
5962 ->SetUnmaskRangeAndNotify(start, length, timeout);
5963 if (NS_FAILED(rv)) {
5964 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
5965 return EditorBase::ToGenericNSResult(rv);
5968 // Flush pending layout right now since the caller may access us before
5969 // doing it.
5970 if (RefPtr<PresShell> presShell = GetPresShell()) {
5971 presShell->FlushPendingNotifications(FlushType::Layout);
5974 return NS_OK;
5977 NS_IMETHODIMP EditorBase::Mask() {
5978 if (NS_WARN_IF(!IsPasswordEditor())) {
5979 return NS_ERROR_NOT_AVAILABLE;
5982 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
5983 if (NS_WARN_IF(!editActionData.CanHandle())) {
5984 return NS_ERROR_NOT_INITIALIZED;
5987 nsresult rv = MOZ_KnownLive(AsTextEditor())->MaskAllCharactersAndNotify();
5988 if (NS_FAILED(rv)) {
5989 NS_WARNING("TextEditor::MaskAllCharactersAndNotify() failed");
5990 return EditorBase::ToGenericNSResult(rv);
5993 // Flush pending layout right now since the caller may access us before
5994 // doing it.
5995 if (RefPtr<PresShell> presShell = GetPresShell()) {
5996 presShell->FlushPendingNotifications(FlushType::Layout);
5999 return NS_OK;
6002 NS_IMETHODIMP EditorBase::GetUnmaskedStart(uint32_t* aResult) {
6003 if (NS_WARN_IF(!IsPasswordEditor())) {
6004 *aResult = 0;
6005 return NS_ERROR_NOT_AVAILABLE;
6007 *aResult =
6008 AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedStart();
6009 return NS_OK;
6012 NS_IMETHODIMP EditorBase::GetUnmaskedEnd(uint32_t* aResult) {
6013 if (NS_WARN_IF(!IsPasswordEditor())) {
6014 *aResult = 0;
6015 return NS_ERROR_NOT_AVAILABLE;
6017 *aResult = AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedEnd();
6018 return NS_OK;
6021 NS_IMETHODIMP EditorBase::GetAutoMaskingEnabled(bool* aResult) {
6022 if (NS_WARN_IF(!IsPasswordEditor())) {
6023 *aResult = false;
6024 return NS_ERROR_NOT_AVAILABLE;
6026 *aResult = AsTextEditor()->IsMaskingPassword();
6027 return NS_OK;
6030 NS_IMETHODIMP EditorBase::GetPasswordMask(nsAString& aPasswordMask) {
6031 aPasswordMask.Assign(TextEditor::PasswordMask());
6032 return NS_OK;
6035 template <typename PT, typename CT>
6036 EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager(
6037 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount,
6038 const EditorDOMPointBase<PT, CT>& aPointAtCaret) {
6039 MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable());
6041 nsPresContext* presContext = aEditorBase.GetPresContext();
6042 if (NS_WARN_IF(!presContext)) {
6043 mFailed = true;
6044 return;
6047 if (!presContext->BidiEnabled()) {
6048 return; // Perform the deletion
6051 if (!aPointAtCaret.IsInContentNode()) {
6052 mFailed = true;
6053 return;
6056 // XXX Not sure whether this requires strong reference here.
6057 RefPtr<nsFrameSelection> frameSelection =
6058 aEditorBase.SelectionRef().GetFrameSelection();
6059 if (NS_WARN_IF(!frameSelection)) {
6060 mFailed = true;
6061 return;
6064 nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels(
6065 aPointAtCaret.template ContainerAs<nsIContent>(), aPointAtCaret.Offset(),
6066 true);
6068 mozilla::intl::BidiEmbeddingLevel levelBefore = levels.mLevelBefore;
6069 mozilla::intl::BidiEmbeddingLevel levelAfter = levels.mLevelAfter;
6071 mozilla::intl::BidiEmbeddingLevel currentCaretLevel =
6072 frameSelection->GetCaretBidiLevel();
6074 mozilla::intl::BidiEmbeddingLevel levelOfDeletion;
6075 levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount ||
6076 nsIEditor::eNextWord == aDirectionAndAmount)
6077 ? levelAfter
6078 : levelBefore;
6080 if (currentCaretLevel == levelOfDeletion) {
6081 return; // Perform the deletion
6084 // Set the bidi level of the caret to that of the
6085 // character that will be (or would have been) deleted
6086 mNewCaretBidiLevel = Some(levelOfDeletion);
6087 mCanceled =
6088 !StaticPrefs::bidi_edit_delete_immediately() && levelBefore != levelAfter;
6091 void EditorBase::AutoCaretBidiLevelManager::MaybeUpdateCaretBidiLevel(
6092 const EditorBase& aEditorBase) const {
6093 MOZ_ASSERT(!mFailed);
6094 if (mNewCaretBidiLevel.isNothing()) {
6095 return;
6097 RefPtr<nsFrameSelection> frameSelection =
6098 aEditorBase.SelectionRef().GetFrameSelection();
6099 MOZ_ASSERT(frameSelection);
6100 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
6101 mNewCaretBidiLevel.value());
6104 void EditorBase::UndefineCaretBidiLevel() const {
6105 MOZ_ASSERT(IsEditActionDataAvailable());
6108 * After inserting text the caret Bidi level must be set to the level of the
6109 * inserted text.This is difficult, because we cannot know what the level is
6110 * until after the Bidi algorithm is applied to the whole paragraph.
6112 * So we set the caret Bidi level to UNDEFINED here, and the caret code will
6113 * set it correctly later
6115 nsFrameSelection* frameSelection = SelectionRef().GetFrameSelection();
6116 if (frameSelection) {
6117 frameSelection->UndefineCaretBidiLevel();
6121 NS_IMETHODIMP EditorBase::GetTextLength(uint32_t* aCount) {
6122 return NS_ERROR_NOT_IMPLEMENTED;
6125 NS_IMETHODIMP EditorBase::GetNewlineHandling(int32_t* aNewlineHandling) {
6126 if (NS_WARN_IF(!aNewlineHandling)) {
6127 return NS_ERROR_INVALID_ARG;
6129 *aNewlineHandling = mNewlineHandling;
6130 return NS_OK;
6133 NS_IMETHODIMP EditorBase::SetNewlineHandling(int32_t aNewlineHandling) {
6134 switch (aNewlineHandling) {
6135 case nsIEditor::eNewlinesPasteIntact:
6136 case nsIEditor::eNewlinesPasteToFirst:
6137 case nsIEditor::eNewlinesReplaceWithSpaces:
6138 case nsIEditor::eNewlinesStrip:
6139 case nsIEditor::eNewlinesReplaceWithCommas:
6140 case nsIEditor::eNewlinesStripSurroundingWhitespace:
6141 mNewlineHandling = aNewlineHandling;
6142 return NS_OK;
6143 default:
6144 NS_ERROR("SetNewlineHandling() is called with wrong value");
6145 return NS_ERROR_INVALID_ARG;
6149 bool EditorBase::IsSelectionRangeContainerNotContent() const {
6150 MOZ_ASSERT(IsEditActionDataAvailable());
6152 // TODO: Make all callers use !AutoRangeArray::IsInContent() instead.
6153 const uint32_t rangeCount = SelectionRef().RangeCount();
6154 for (const uint32_t i : IntegerRange(rangeCount)) {
6155 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
6156 const nsRange* range = SelectionRef().GetRangeAt(i);
6157 MOZ_ASSERT(range);
6158 if (MOZ_UNLIKELY(!range) || MOZ_UNLIKELY(!range->GetStartContainer()) ||
6159 MOZ_UNLIKELY(!range->GetStartContainer()->IsContent()) ||
6160 MOZ_UNLIKELY(!range->GetEndContainer()) ||
6161 MOZ_UNLIKELY(!range->GetEndContainer()->IsContent())) {
6162 return true;
6165 return false;
6168 NS_IMETHODIMP EditorBase::InsertText(const nsAString& aStringToInsert) {
6169 nsresult rv = InsertTextAsAction(aStringToInsert);
6170 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6171 "EditorBase::InsertTextAsAction() failed");
6172 return rv;
6175 nsresult EditorBase::InsertTextAsAction(const nsAString& aStringToInsert,
6176 nsIPrincipal* aPrincipal) {
6177 // Showing this assertion is fine if this method is called by outside via
6178 // mutation event listener or something. Otherwise, this is called by
6179 // wrong method.
6180 NS_ASSERTION(!mPlaceholderBatch,
6181 "Should be called only when this is the only edit action of the "
6182 "operation "
6183 "unless mutation event listener nests some operations");
6185 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText,
6186 aPrincipal);
6187 // Note that we don't need to replace native line breaks with XP line breaks
6188 // here because Chrome does not do it.
6189 MOZ_ASSERT(!aStringToInsert.IsVoid());
6190 editActionData.SetData(aStringToInsert);
6191 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
6192 if (NS_FAILED(rv)) {
6193 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
6194 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
6195 return EditorBase::ToGenericNSResult(rv);
6198 nsString stringToInsert(aStringToInsert);
6199 if (IsTextEditor()) {
6200 nsContentUtils::PlatformToDOMLineBreaks(stringToInsert);
6202 AutoPlaceholderBatch treatAsOneTransaction(
6203 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6204 rv = InsertTextAsSubAction(stringToInsert, SelectionHandling::Delete);
6205 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6206 "EditorBase::InsertTextAsSubAction() failed");
6207 return EditorBase::ToGenericNSResult(rv);
6210 nsresult EditorBase::InsertTextAsSubAction(
6211 const nsAString& aStringToInsert, SelectionHandling aSelectionHandling) {
6212 MOZ_ASSERT(IsEditActionDataAvailable());
6213 MOZ_ASSERT(mPlaceholderBatch);
6214 MOZ_ASSERT(IsHTMLEditor() ||
6215 aStringToInsert.FindChar(nsCRT::CR) == kNotFound);
6216 MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore, mComposition);
6218 if (NS_WARN_IF(!mInitSucceeded)) {
6219 return NS_ERROR_NOT_INITIALIZED;
6222 if (NS_WARN_IF(Destroyed())) {
6223 return NS_ERROR_EDITOR_DESTROYED;
6226 EditSubAction editSubAction = ShouldHandleIMEComposition()
6227 ? EditSubAction::eInsertTextComingFromIME
6228 : EditSubAction::eInsertText;
6230 IgnoredErrorResult ignoredError;
6231 AutoEditSubActionNotifier startToHandleEditSubAction(
6232 *this, editSubAction, nsIEditor::eNext, ignoredError);
6233 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6234 return ignoredError.StealNSResult();
6236 NS_WARNING_ASSERTION(
6237 !ignoredError.Failed(),
6238 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
6240 Result<EditActionResult, nsresult> result =
6241 HandleInsertText(editSubAction, aStringToInsert, aSelectionHandling);
6242 if (MOZ_UNLIKELY(result.isErr())) {
6243 NS_WARNING("EditorBase::HandleInsertText() failed");
6244 return result.unwrapErr();
6246 return NS_OK;
6249 NS_IMETHODIMP EditorBase::InsertLineBreak() { return NS_ERROR_NOT_IMPLEMENTED; }
6251 /*****************************************************************************
6252 * mozilla::EditorBase::AutoEditActionDataSetter
6253 *****************************************************************************/
6255 EditorBase::AutoEditActionDataSetter::AutoEditActionDataSetter(
6256 const EditorBase& aEditorBase, EditAction aEditAction,
6257 nsIPrincipal* aPrincipal /* = nullptr */)
6258 : mEditorBase(const_cast<EditorBase&>(aEditorBase)),
6259 mPrincipal(aPrincipal),
6260 mParentData(aEditorBase.mEditActionData),
6261 mData(VoidString()),
6262 mRawEditAction(aEditAction),
6263 mTopLevelEditSubAction(EditSubAction::eNone),
6264 mAborted(false),
6265 mHasTriedToDispatchBeforeInputEvent(false),
6266 mBeforeInputEventCanceled(false),
6267 mMakeBeforeInputEventNonCancelable(false),
6268 mHasTriedToDispatchClipboardEvent(false),
6269 mEditorWasDestroyedDuringHandlingEditAction(
6270 mParentData &&
6271 mParentData->mEditorWasDestroyedDuringHandlingEditAction),
6272 mHandled(false) {
6273 // If we're nested edit action, copies necessary data from the parent.
6274 if (mParentData) {
6275 mSelection = mParentData->mSelection;
6276 MOZ_ASSERT(!mSelection ||
6277 (mSelection->GetType() == SelectionType::eNormal));
6279 // If we're not editing something, we should inherit the parent's edit
6280 // action. This may occur if creator or its callee use public methods which
6281 // just returns something.
6282 if (IsEditActionInOrderToEditSomething(aEditAction)) {
6283 mEditAction = aEditAction;
6284 } else {
6285 mEditAction = mParentData->mEditAction;
6286 // If we inherit an edit action whose handler needs to dispatch a
6287 // clipboard event, we should inherit the clipboard dispatching state
6288 // too because this nest occurs by a clipboard event listener or
6289 // a beforeinput/mutation event listener is important for checking
6290 // whether we've already called `MaybeDispatchBeforeInputEvent()`
6291 // property in some points. If the former case, not yet dispatching
6292 // beforeinput event is okay (not fine).
6293 mHasTriedToDispatchClipboardEvent =
6294 mParentData->mHasTriedToDispatchClipboardEvent;
6296 mTopLevelEditSubAction = mParentData->mTopLevelEditSubAction;
6298 // Parent's mTopLevelEditSubActionData should be referred instead so that
6299 // we don't need to set mTopLevelEditSubActionData.mSelectedRange nor
6300 // mTopLevelEditActionData.mChangedRange here.
6302 mDirectionOfTopLevelEditSubAction =
6303 mParentData->mDirectionOfTopLevelEditSubAction;
6304 } else {
6305 mSelection = mEditorBase.GetSelection();
6306 if (NS_WARN_IF(!mSelection)) {
6307 return;
6310 MOZ_ASSERT(mSelection->GetType() == SelectionType::eNormal);
6312 mEditAction = aEditAction;
6313 mDirectionOfTopLevelEditSubAction = eNone;
6314 if (mEditorBase.IsHTMLEditor()) {
6315 mTopLevelEditSubActionData.mSelectedRange =
6316 mEditorBase.AsHTMLEditor()
6317 ->GetSelectedRangeItemForTopLevelEditSubAction();
6318 mTopLevelEditSubActionData.mChangedRange =
6319 mEditorBase.AsHTMLEditor()->GetChangedRangeForTopLevelEditSubAction();
6320 mTopLevelEditSubActionData.mCachedPendingStyles.emplace();
6323 mEditorBase.mEditActionData = this;
6326 EditorBase::AutoEditActionDataSetter::~AutoEditActionDataSetter() {
6327 MOZ_ASSERT(mHasCanHandleChecked);
6329 if (!mSelection || NS_WARN_IF(mEditorBase.mEditActionData != this)) {
6330 return;
6332 mEditorBase.mEditActionData = mParentData;
6334 MOZ_ASSERT(
6335 !mTopLevelEditSubActionData.mSelectedRange ||
6336 (!mTopLevelEditSubActionData.mSelectedRange->mStartContainer &&
6337 !mTopLevelEditSubActionData.mSelectedRange->mEndContainer),
6338 "mTopLevelEditSubActionData.mSelectedRange should've been cleared");
6341 void EditorBase::AutoEditActionDataSetter::UpdateSelectionCache(
6342 Selection& aSelection) {
6343 MOZ_ASSERT(aSelection.GetType() == SelectionType::eNormal);
6345 if (mSelection == &aSelection) {
6346 return;
6349 AutoEditActionDataSetter& topLevelEditActionData =
6350 [&]() -> AutoEditActionDataSetter& {
6351 for (AutoEditActionDataSetter* editActionData = this;;
6352 editActionData = editActionData->mParentData) {
6353 if (!editActionData->mParentData) {
6354 return *editActionData;
6357 MOZ_ASSERT_UNREACHABLE("You do something wrong");
6358 }();
6360 // Keep grabbing the old selection in the top level edit action data until the
6361 // all owners end handling it.
6362 if (mSelection) {
6363 topLevelEditActionData.mRetiredSelections.AppendElement(*mSelection);
6366 // If the old selection is in batch, we should end the batch which
6367 // `EditorBase::BeginUpdateViewBatch` started.
6368 if (mEditorBase.mUpdateCount && mSelection) {
6369 mSelection->EndBatchChanges(__FUNCTION__);
6372 Selection* previousSelection = mSelection;
6373 mSelection = &aSelection;
6374 for (AutoEditActionDataSetter* parentActionData = mParentData;
6375 parentActionData; parentActionData = parentActionData->mParentData) {
6376 if (!parentActionData->mSelection) {
6377 continue;
6379 // Skip scanning mRetiredSelections if we've already handled the selection
6380 // previous time.
6381 if (parentActionData->mSelection != previousSelection) {
6382 if (!topLevelEditActionData.mRetiredSelections.Contains(
6383 OwningNonNull<Selection>(*parentActionData->mSelection))) {
6384 topLevelEditActionData.mRetiredSelections.AppendElement(
6385 *parentActionData->mSelection);
6387 previousSelection = parentActionData->mSelection;
6389 parentActionData->mSelection = &aSelection;
6392 // Restart the batching in the new selection.
6393 if (mEditorBase.mUpdateCount) {
6394 aSelection.StartBatchChanges(__FUNCTION__);
6398 void EditorBase::AutoEditActionDataSetter::SetColorData(
6399 const nsAString& aData) {
6400 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6401 "It's too late to set data since this may have already dispatched "
6402 "a beforeinput event");
6404 if (aData.IsEmpty()) {
6405 // When removing color/background-color, let's use empty string.
6406 mData.Truncate();
6407 MOZ_ASSERT(!mData.IsVoid());
6408 return;
6411 DebugOnly<bool> validColorValue = HTMLEditUtils::GetNormalizedCSSColorValue(
6412 aData, HTMLEditUtils::ZeroAlphaColor::RGBAValue, mData);
6413 MOZ_ASSERT_IF(validColorValue, !mData.IsVoid());
6416 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6417 DataTransfer* aDataTransfer) {
6418 MOZ_ASSERT(aDataTransfer);
6419 MOZ_ASSERT(aDataTransfer->IsReadOnly());
6420 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6421 "It's too late to set dataTransfer since this may have already "
6422 "dispatched a beforeinput event");
6424 mDataTransfer = aDataTransfer;
6427 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6428 nsITransferable* aTransferable) {
6429 MOZ_ASSERT(aTransferable);
6430 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6431 "It's too late to set dataTransfer since this may have already "
6432 "dispatched a beforeinput event");
6434 Document* document = mEditorBase.GetDocument();
6435 nsIGlobalObject* scopeObject =
6436 document ? document->GetScopeObject() : nullptr;
6437 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aTransferable);
6440 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6441 const nsAString& aString) {
6442 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6443 "It's too late to set dataTransfer since this may have already "
6444 "dispatched a beforeinput event");
6445 Document* document = mEditorBase.GetDocument();
6446 nsIGlobalObject* scopeObject =
6447 document ? document->GetScopeObject() : nullptr;
6448 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aString);
6451 void EditorBase::AutoEditActionDataSetter::InitializeDataTransferWithClipboard(
6452 SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType) {
6453 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6454 "It's too late to set dataTransfer since this may have already "
6455 "dispatched a beforeinput event");
6457 Document* document = mEditorBase.GetDocument();
6458 nsIGlobalObject* scopeObject =
6459 document ? document->GetScopeObject() : nullptr;
6460 // mDataTransfer will be used for eEditorInput event, but we can keep
6461 // using ePaste and ePasteNoFormatting here. If we need to use eEditorInput,
6462 // we need to create eEditorInputNoFormatting or something...
6463 mDataTransfer =
6464 new DataTransfer(scopeObject,
6465 aSettingDataTransfer == SettingDataTransfer::eWithFormat
6466 ? ePaste
6467 : ePasteNoFormatting,
6468 true /* is external */, aClipboardType);
6471 void EditorBase::AutoEditActionDataSetter::AppendTargetRange(
6472 StaticRange& aTargetRange) {
6473 mTargetRanges.AppendElement(aTargetRange);
6476 bool EditorBase::AutoEditActionDataSetter::IsBeforeInputEventEnabled() const {
6477 // Don't dispatch "beforeinput" event when the editor user makes us stop
6478 // dispatching input event.
6479 if (mEditorBase.IsSuppressingDispatchingInputEvent()) {
6480 return false;
6482 return EditorBase::TreatAsUserInput(mPrincipal);
6485 // static
6486 bool EditorBase::TreatAsUserInput(nsIPrincipal* aPrincipal) {
6487 // If aPrincipal it not nullptr, it means that the caller is handling an edit
6488 // action which is requested by JS. If it's not chrome script, we shouldn't
6489 // dispatch "beforeinput" event.
6490 if (aPrincipal && !aPrincipal->IsSystemPrincipal()) {
6491 // But if it's content script of an addon, `execCommand` calls are a
6492 // part of browser's default action from point of view of web apps.
6493 // Therefore, we should dispatch `beforeinput` event.
6494 // https://github.com/w3c/input-events/issues/91
6495 if (!aPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
6496 return false;
6500 return true;
6503 nsresult EditorBase::AutoEditActionDataSetter::MaybeFlushPendingNotifications()
6504 const {
6505 MOZ_ASSERT(CanHandle());
6506 if (!MayEditActionRequireLayout(mRawEditAction)) {
6507 return NS_SUCCESS_DOM_NO_OPERATION;
6509 OwningNonNull<EditorBase> editorBase = mEditorBase;
6510 RefPtr<PresShell> presShell = editorBase->GetPresShell();
6511 if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
6512 return NS_ERROR_NOT_AVAILABLE;
6514 presShell->FlushPendingNotifications(FlushType::Layout);
6515 if (MOZ_UNLIKELY(NS_WARN_IF(editorBase->Destroyed()))) {
6516 return NS_ERROR_EDITOR_DESTROYED;
6518 return NS_OK;
6521 void EditorBase::AutoEditActionDataSetter::MarkEditActionCanceled() {
6522 mBeforeInputEventCanceled = true;
6523 if (mEditorBase.IsHTMLEditor()) {
6524 mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true;
6528 nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent(
6529 nsIEditor::EDirection aDeleteDirectionAndAmount /* = nsIEditor::eNone */) {
6530 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6531 "We've already handled beforeinput event");
6532 MOZ_ASSERT(CanHandle());
6533 MOZ_ASSERT_IF(IsBeforeInputEventEnabled(),
6534 ShouldAlreadyHaveHandledBeforeInputEventDispatching());
6535 MOZ_ASSERT_IF(!MayEditActionDeleteAroundCollapsedSelection(mEditAction),
6536 aDeleteDirectionAndAmount == nsIEditor::eNone);
6538 mHasTriedToDispatchBeforeInputEvent = true;
6540 if (!IsBeforeInputEventEnabled()) {
6541 return NS_OK;
6544 // If we're called from OnCompositionEnd(), we shouldn't dispatch
6545 // "beforeinput" event since the preceding OnCompositionChange() call has
6546 // already dispatched "beforeinput" event for this.
6547 if (mEditAction == EditAction::eCommitComposition ||
6548 mEditAction == EditAction::eCancelComposition) {
6549 return NS_OK;
6552 RefPtr<Element> targetElement = mEditorBase.GetInputEventTargetElement();
6553 if (!targetElement) {
6554 // If selection is not in editable element and it is outside of any
6555 // editing hosts, there may be no target element to dispatch `beforeinput`
6556 // event. In this case, the caller shouldn't keep handling the edit
6557 // action since web apps cannot override it with `beforeinput` event
6558 // listener, but for backward compatibility, we should return a special
6559 // success code instead of error.
6560 return NS_OK;
6562 OwningNonNull<EditorBase> editorBase = mEditorBase;
6563 EditorInputType inputType = ToInputType(mEditAction);
6564 if (editorBase->IsHTMLEditor() && mTargetRanges.IsEmpty()) {
6565 // If the edit action will delete selected ranges, compute the range
6566 // strictly.
6567 if (MayEditActionDeleteAroundCollapsedSelection(mEditAction) ||
6568 (!editorBase->SelectionRef().IsCollapsed() &&
6569 MayEditActionDeleteSelection(mEditAction))) {
6570 if (!editorBase
6571 ->FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
6572 aDeleteDirectionAndAmount)) {
6573 NS_WARNING(
6574 "Flusing pending notifications caused destroying the editor");
6575 return NS_ERROR_EDITOR_DESTROYED;
6578 AutoRangeArray rangesToDelete(editorBase->SelectionRef());
6579 if (!rangesToDelete.Ranges().IsEmpty()) {
6580 nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor())
6581 ->ComputeTargetRanges(aDeleteDirectionAndAmount,
6582 rangesToDelete);
6583 if (rv == NS_ERROR_EDITOR_DESTROYED) {
6584 NS_WARNING("HTMLEditor::ComputeTargetRanges() destroyed the editor");
6585 return NS_ERROR_EDITOR_DESTROYED;
6587 if (rv == NS_ERROR_EDITOR_NO_EDITABLE_RANGE) {
6588 // For now, keep dispatching `beforeinput` event even if no selection
6589 // range can be editable.
6590 rv = NS_OK;
6592 NS_WARNING_ASSERTION(
6593 NS_SUCCEEDED(rv),
6594 "HTMLEditor::ComputeTargetRanges() failed, but ignored");
6595 for (auto& range : rangesToDelete.Ranges()) {
6596 RefPtr<StaticRange> staticRange =
6597 StaticRange::Create(range, IgnoreErrors());
6598 if (NS_WARN_IF(!staticRange)) {
6599 continue;
6601 AppendTargetRange(*staticRange);
6605 // Otherwise, just set target ranges to selection ranges.
6606 else if (MayHaveTargetRangesOnHTMLEditor(inputType)) {
6607 if (uint32_t rangeCount = editorBase->SelectionRef().RangeCount()) {
6608 mTargetRanges.SetCapacity(rangeCount);
6609 for (const uint32_t i : IntegerRange(rangeCount)) {
6610 MOZ_ASSERT(editorBase->SelectionRef().RangeCount() == rangeCount);
6611 const nsRange* range = editorBase->SelectionRef().GetRangeAt(i);
6612 MOZ_ASSERT(range);
6613 MOZ_ASSERT(range->IsPositioned());
6614 if (MOZ_UNLIKELY(NS_WARN_IF(!range)) ||
6615 MOZ_UNLIKELY(NS_WARN_IF(!range->IsPositioned()))) {
6616 continue;
6618 // Now, we need to fix the offset of target range because it may
6619 // be referred after modifying the DOM tree and range boundaries
6620 // of `range` may have not computed offset yet.
6621 RefPtr<StaticRange> targetRange = StaticRange::Create(
6622 range->GetStartContainer(), range->StartOffset(),
6623 range->GetEndContainer(), range->EndOffset(), IgnoreErrors());
6624 if (NS_WARN_IF(!targetRange) ||
6625 NS_WARN_IF(!targetRange->IsPositioned())) {
6626 continue;
6628 mTargetRanges.AppendElement(std::move(targetRange));
6633 nsEventStatus status = nsEventStatus_eIgnore;
6634 InputEventOptions::NeverCancelable neverCancelable =
6635 mMakeBeforeInputEventNonCancelable
6636 ? InputEventOptions::NeverCancelable::Yes
6637 : InputEventOptions::NeverCancelable::No;
6638 nsresult rv = nsContentUtils::DispatchInputEvent(
6639 targetElement, eEditorBeforeInput, inputType, editorBase,
6640 mDataTransfer
6641 ? InputEventOptions(mDataTransfer, std::move(mTargetRanges),
6642 neverCancelable)
6643 : InputEventOptions(mData, std::move(mTargetRanges), neverCancelable),
6644 &status);
6645 if (NS_WARN_IF(mEditorBase.Destroyed())) {
6646 return NS_ERROR_EDITOR_DESTROYED;
6648 if (NS_FAILED(rv)) {
6649 NS_WARNING("nsContentUtils::DispatchInputEvent() failed");
6650 return rv;
6652 if (status == nsEventStatus_eConsumeNoDefault) {
6653 MarkEditActionCanceled();
6654 return NS_ERROR_EDITOR_ACTION_CANCELED;
6657 nsCOMPtr<nsIWidget> widget = editorBase->GetWidget();
6658 if (!StaticPrefs::dom_events_textevent_enabled() ||
6659 !targetElement->IsInComposedDoc() || !widget) {
6660 return NS_OK;
6662 nsString textInputData;
6663 RefPtr<DataTransfer> textInputDataTransfer;
6664 switch (inputType) {
6665 case EditorInputType::eInsertCompositionText:
6666 // If the composition is still being composed, we should not dispatch
6667 // textInput event, but we need to dispatch it for the last composition
6668 // change because web apps should know the inserting commit string as
6669 // same as input from keyboard.
6670 if (mEditAction == EditAction::eUpdateComposition) {
6671 return NS_OK;
6673 [[fallthrough]];
6674 case EditorInputType::eInsertText:
6675 textInputData = mData;
6676 break;
6677 case EditorInputType::eInsertFromDrop:
6678 case EditorInputType::eInsertFromPaste:
6679 case EditorInputType::eInsertFromPasteAsQuotation:
6680 if (mDataTransfer) {
6681 textInputDataTransfer = mDataTransfer;
6682 } else {
6683 textInputData = mData;
6685 break;
6686 case EditorInputType::eInsertLineBreak:
6687 case EditorInputType::eInsertParagraph:
6688 // Don't dispatch `textInput` on <input> because Chrome does not do it.
6689 // On the other hand, we need to dispatch it on <textarea> and
6690 // contenteditable.
6691 if (mEditorBase.IsTextEditor() && mEditorBase.IsSingleLineEditor()) {
6692 return NS_OK;
6694 textInputData.Assign(u'\n');
6695 break;
6696 default:
6697 return NS_OK;
6700 InternalLegacyTextEvent textEvent(true, eLegacyTextInput, widget);
6701 textEvent.mData = std::move(textInputData);
6702 textEvent.mDataTransfer = std::move(textInputDataTransfer);
6703 textEvent.mInputType = inputType;
6704 // Make it always cancelable even though we ignore it when inserting or
6705 // deleting composition. This is compatible with Chrome.
6706 // However, if and only if it's unsafe, let's set it not cancelable because of
6707 // asynchronous dispatching.
6708 textEvent.mFlags.mCancelable = nsContentUtils::IsSafeToRunScript();
6710 status = nsEventStatus_eIgnore;
6711 rv = AsyncEventDispatcher::RunDOMEventWhenSafe(*targetElement, textEvent,
6712 &status);
6713 if (NS_WARN_IF(mEditorBase.Destroyed())) {
6714 return NS_ERROR_EDITOR_DESTROYED;
6716 if (NS_FAILED(rv)) {
6717 NS_WARNING("AsyncEventDispatcher::RunDOMEventWhenSafe() failed");
6718 return rv;
6720 if (status == nsEventStatus_eConsumeNoDefault) {
6721 MarkEditActionCanceled();
6722 return NS_ERROR_EDITOR_ACTION_CANCELED;
6724 return NS_OK;
6727 /*****************************************************************************
6728 * mozilla::EditorBase::TopLevelEditSubActionData
6729 *****************************************************************************/
6731 nsresult EditorBase::TopLevelEditSubActionData::AddNodeToChangedRange(
6732 const HTMLEditor& aHTMLEditor, nsINode& aNode) {
6733 EditorRawDOMPoint startPoint(&aNode);
6734 EditorRawDOMPoint endPoint(&aNode);
6735 DebugOnly<bool> advanced = endPoint.AdvanceOffset();
6736 NS_WARNING_ASSERTION(advanced, "Failed to set endPoint to next to aNode");
6737 nsresult rv = AddRangeToChangedRange(aHTMLEditor, startPoint, endPoint);
6738 NS_WARNING_ASSERTION(
6739 NS_SUCCEEDED(rv),
6740 "TopLevelEditSubActionData::AddRangeToChangedRange() failed");
6741 return rv;
6744 nsresult EditorBase::TopLevelEditSubActionData::AddPointToChangedRange(
6745 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aPoint) {
6746 nsresult rv = AddRangeToChangedRange(aHTMLEditor, aPoint, aPoint);
6747 NS_WARNING_ASSERTION(
6748 NS_SUCCEEDED(rv),
6749 "TopLevelEditSubActionData::AddRangeToChangedRange() failed");
6750 return rv;
6753 nsresult EditorBase::TopLevelEditSubActionData::AddRangeToChangedRange(
6754 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aStart,
6755 const EditorRawDOMPoint& aEnd) {
6756 if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet())) {
6757 return NS_ERROR_INVALID_ARG;
6760 if (!aHTMLEditor.IsDescendantOfRoot(aStart.GetContainer()) ||
6761 (aStart.GetContainer() != aEnd.GetContainer() &&
6762 !aHTMLEditor.IsDescendantOfRoot(aEnd.GetContainer()))) {
6763 return NS_OK;
6766 // If mChangedRange hasn't been set, we can just set it to `aStart` and
6767 // `aEnd`.
6768 if (!mChangedRange->IsPositioned()) {
6769 nsresult rv = mChangedRange->SetStartAndEnd(aStart.ToRawRangeBoundary(),
6770 aEnd.ToRawRangeBoundary());
6771 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
6772 return rv;
6775 Maybe<int32_t> relation =
6776 mChangedRange->StartRef().IsSet()
6777 ? nsContentUtils::ComparePoints(mChangedRange->StartRef(),
6778 aStart.ToRawRangeBoundary())
6779 : Some(1);
6780 if (NS_WARN_IF(!relation)) {
6781 return NS_ERROR_FAILURE;
6784 // If aStart is before start of mChangedRange, reset the start.
6785 if (*relation > 0) {
6786 ErrorResult error;
6787 mChangedRange->SetStart(aStart.ToRawRangeBoundary(), error);
6788 if (error.Failed()) {
6789 NS_WARNING("nsRange::SetStart() failed");
6790 return error.StealNSResult();
6794 relation = mChangedRange->EndRef().IsSet()
6795 ? nsContentUtils::ComparePoints(mChangedRange->EndRef(),
6796 aEnd.ToRawRangeBoundary())
6797 : Some(1);
6798 if (NS_WARN_IF(!relation)) {
6799 return NS_ERROR_FAILURE;
6802 // If aEnd is after end of mChangedRange, reset the end.
6803 if (*relation < 0) {
6804 ErrorResult error;
6805 mChangedRange->SetEnd(aEnd.ToRawRangeBoundary(), error);
6806 if (error.Failed()) {
6807 NS_WARNING("nsRange::SetEnd() failed");
6808 return error.StealNSResult();
6812 return NS_OK;
6815 void EditorBase::TopLevelEditSubActionData::DidCreateElement(
6816 EditorBase& aEditorBase, Element& aNewElement) {
6817 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6819 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6820 return; // We have not been initialized yet or already been destroyed.
6823 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6824 return; // Temporarily disabled by edit sub-action handler.
6827 DebugOnly<nsresult> rvIgnored =
6828 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewElement);
6829 NS_WARNING_ASSERTION(
6830 NS_SUCCEEDED(rvIgnored),
6831 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6834 void EditorBase::TopLevelEditSubActionData::DidInsertContent(
6835 EditorBase& aEditorBase, nsIContent& aNewContent) {
6836 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6838 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6839 return; // We have not been initialized yet or already been destroyed.
6842 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6843 return; // Temporarily disabled by edit sub-action handler.
6846 DebugOnly<nsresult> rvIgnored =
6847 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewContent);
6848 NS_WARNING_ASSERTION(
6849 NS_SUCCEEDED(rvIgnored),
6850 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6853 void EditorBase::TopLevelEditSubActionData::WillDeleteContent(
6854 EditorBase& aEditorBase, nsIContent& aRemovingContent) {
6855 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6857 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6858 return; // We have not been initialized yet or already been destroyed.
6861 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6862 return; // Temporarily disabled by edit sub-action handler.
6865 DebugOnly<nsresult> rvIgnored =
6866 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aRemovingContent);
6867 NS_WARNING_ASSERTION(
6868 NS_SUCCEEDED(rvIgnored),
6869 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6872 void EditorBase::TopLevelEditSubActionData::DidSplitContent(
6873 EditorBase& aEditorBase, nsIContent& aSplitContent,
6874 nsIContent& aNewContent) {
6875 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6877 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6878 return; // We have not been initialized yet or already been destroyed.
6881 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6882 return; // Temporarily disabled by edit sub-action handler.
6885 DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange(
6886 *aEditorBase.AsHTMLEditor(), EditorRawDOMPoint::AtEndOf(aSplitContent),
6887 EditorRawDOMPoint::AtEndOf(aNewContent));
6888 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6889 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6890 "failed, but ignored");
6893 void EditorBase::TopLevelEditSubActionData::DidJoinContents(
6894 EditorBase& aEditorBase, const EditorRawDOMPoint& aJoinedPoint) {
6895 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6897 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6898 return; // We have not been initialized yet or already been destroyed.
6901 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6902 return; // Temporarily disabled by edit sub-action handler.
6905 DebugOnly<nsresult> rvIgnored =
6906 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aJoinedPoint);
6907 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6908 "TopLevelEditSubActionData::AddPointToChangedRange() "
6909 "failed, but ignored");
6912 void EditorBase::TopLevelEditSubActionData::DidInsertText(
6913 EditorBase& aEditorBase, const EditorRawDOMPoint& aInsertionBegin,
6914 const EditorRawDOMPoint& aInsertionEnd) {
6915 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6917 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6918 return; // We have not been initialized yet or already been destroyed.
6921 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6922 return; // Temporarily disabled by edit sub-action handler.
6925 DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange(
6926 *aEditorBase.AsHTMLEditor(), aInsertionBegin, aInsertionEnd);
6927 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6928 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6929 "failed, but ignored");
6932 void EditorBase::TopLevelEditSubActionData::DidDeleteText(
6933 EditorBase& aEditorBase, const EditorRawDOMPoint& aStartInTextNode) {
6934 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6936 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6937 return; // We have not been initialized yet or already been destroyed.
6940 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6941 return; // Temporarily disabled by edit sub-action handler.
6944 DebugOnly<nsresult> rvIgnored =
6945 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aStartInTextNode);
6946 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6947 "TopLevelEditSubActionData::AddPointToChangedRange() "
6948 "failed, but ignored");
6951 void EditorBase::TopLevelEditSubActionData::WillDeleteRange(
6952 EditorBase& aEditorBase, const EditorRawDOMPoint& aStart,
6953 const EditorRawDOMPoint& aEnd) {
6954 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6955 MOZ_ASSERT(aStart.IsSet());
6956 MOZ_ASSERT(aEnd.IsSet());
6958 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6959 return; // We have not been initialized yet or already been destroyed.
6962 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6963 return; // Temporarily disabled by edit sub-action handler.
6966 // XXX Looks like that this is wrong. We delete multiple selection ranges
6967 // once, but this adds only first range into the changed range.
6968 // Anyway, we should take the range as an argument.
6969 DebugOnly<nsresult> rvIgnored =
6970 AddRangeToChangedRange(*aEditorBase.AsHTMLEditor(), aStart, aEnd);
6971 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6972 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6973 "failed, but ignored");
6976 nsPIDOMWindowOuter* EditorBase::GetWindow() const {
6977 return mDocument ? mDocument->GetWindow() : nullptr;
6980 nsPIDOMWindowInner* EditorBase::GetInnerWindow() const {
6981 return mDocument ? mDocument->GetInnerWindow() : nullptr;
6984 PresShell* EditorBase::GetPresShell() const {
6985 return mDocument ? mDocument->GetPresShell() : nullptr;
6988 nsPresContext* EditorBase::GetPresContext() const {
6989 PresShell* presShell = GetPresShell();
6990 return presShell ? presShell->GetPresContext() : nullptr;
6993 already_AddRefed<nsCaret> EditorBase::GetCaret() const {
6994 PresShell* presShell = GetPresShell();
6995 if (NS_WARN_IF(!presShell)) {
6996 return nullptr;
6998 return presShell->GetCaret();
7001 nsISelectionController* EditorBase::GetSelectionController() const {
7002 if (mSelectionController) {
7003 return mSelectionController;
7005 if (!mDocument) {
7006 return nullptr;
7008 return mDocument->GetPresShell();
7011 } // namespace mozilla