Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / EditorBase.cpp
blob02211c8e7b09896dd833808ce02324463272e335
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 "mozilla/DebugOnly.h" // for DebugOnly
10 #include <stdio.h> // for nullptr, stdout
11 #include <string.h> // for strcmp
13 #include "ChangeAttributeTransaction.h" // for ChangeAttributeTransaction
14 #include "CompositionTransaction.h" // for CompositionTransaction
15 #include "DeleteNodeTransaction.h" // for DeleteNodeTransaction
16 #include "DeleteRangeTransaction.h" // for DeleteRangeTransaction
17 #include "DeleteTextTransaction.h" // for DeleteTextTransaction
18 #include "EditAggregateTransaction.h" // for EditAggregateTransaction
19 #include "EditTransactionBase.h" // for EditTransactionBase
20 #include "EditorEventListener.h" // for EditorEventListener
21 #include "gfxFontUtils.h" // for gfxFontUtils
22 #include "HTMLEditUtils.h" // for HTMLEditUtils
23 #include "InsertNodeTransaction.h" // for InsertNodeTransaction
24 #include "InsertTextTransaction.h" // for InsertTextTransaction
25 #include "JoinNodesTransaction.h" // for JoinNodesTransaction
26 #include "PlaceholderTransaction.h" // for PlaceholderTransaction
27 #include "SplitNodeTransaction.h" // for SplitNodeTransaction
28 #include "mozilla/intl/BidiEmbeddingLevel.h"
29 #include "mozilla/BasePrincipal.h" // for BasePrincipal
30 #include "mozilla/CheckedInt.h" // for CheckedInt
31 #include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater
32 #include "mozilla/ContentEvents.h" // for InternalClipboardEvent
33 #include "mozilla/CSSEditUtils.h" // for CSSEditUtils
34 #include "mozilla/EditAction.h" // for EditSubAction
35 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
36 #include "mozilla/EditorSpellCheck.h" // for EditorSpellCheck
37 #include "mozilla/EditorUtils.h" // for various helper classes.
38 #include "mozilla/EditTransactionBase.h" // for EditTransactionBase
39 #include "mozilla/Encoding.h" // for Encoding (used in Document::GetDocumentCharacterSet)
40 #include "mozilla/EventDispatcher.h" // for EventChainPreVisitor, etc.
41 #include "mozilla/FlushType.h" // for FlushType::Frames
42 #include "mozilla/HTMLEditor.h" // for HTMLEditor
43 #include "mozilla/IMEContentObserver.h" // for IMEContentObserver
44 #include "mozilla/IMEStateManager.h" // for IMEStateManager
45 #include "mozilla/InputEventOptions.h" // for InputEventOptions
46 #include "mozilla/IntegerRange.h" // for IntegerRange
47 #include "mozilla/InternalMutationEvent.h" // for NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
48 #include "mozilla/mozalloc.h" // for operator new, etc.
49 #include "mozilla/mozInlineSpellChecker.h" // for mozInlineSpellChecker
50 #include "mozilla/mozSpellChecker.h" // for mozSpellChecker
51 #include "mozilla/Preferences.h" // for Preferences
52 #include "mozilla/PresShell.h" // for PresShell
53 #include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary
54 #include "mozilla/Services.h" // for GetObserverService
55 #include "mozilla/ServoCSSParser.h" // for ServoCSSParser
56 #include "mozilla/StaticPrefs_bidi.h" // for StaticPrefs::bidi_*
57 #include "mozilla/StaticPrefs_dom.h" // for StaticPrefs::dom_*
58 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
59 #include "mozilla/StaticPrefs_layout.h" // for StaticPrefs::layout_*
60 #include "mozilla/TextComposition.h" // for TextComposition
61 #include "mozilla/TextControlElement.h" // for TextControlElement
62 #include "mozilla/TextInputListener.h" // for TextInputListener
63 #include "mozilla/TextServicesDocument.h" // for TextServicesDocument
64 #include "mozilla/TextEditor.h"
65 #include "mozilla/TextEvents.h"
66 #include "mozilla/TransactionManager.h" // for TransactionManager
67 #include "mozilla/dom/AbstractRange.h" // for AbstractRange
68 #include "mozilla/dom/Attr.h" // for Attr
69 #include "mozilla/dom/BrowsingContext.h" // for BrowsingContext
70 #include "mozilla/dom/CharacterData.h" // for CharacterData
71 #include "mozilla/dom/DataTransfer.h" // for DataTransfer
72 #include "mozilla/dom/Document.h" // for Document
73 #include "mozilla/dom/DocumentInlines.h" // for GetObservingPresShell
74 #include "mozilla/dom/DragEvent.h" // for DragEvent
75 #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
76 #include "mozilla/dom/EventTarget.h" // for EventTarget
77 #include "mozilla/dom/HTMLBodyElement.h"
78 #include "mozilla/dom/HTMLBRElement.h"
79 #include "mozilla/dom/Selection.h" // for Selection, etc.
80 #include "mozilla/dom/StaticRange.h" // for StaticRange
81 #include "mozilla/dom/Text.h"
82 #include "mozilla/dom/Event.h"
83 #include "nsAString.h" // for nsAString::Length, etc.
84 #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker
85 #include "nsCaret.h" // for nsCaret
86 #include "nsCaseTreatment.h"
87 #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc.
88 #include "nsContentUtils.h" // for nsContentUtils
89 #include "nsCopySupport.h" // for nsCopySupport
90 #include "nsDOMString.h" // for DOMStringIsNull
91 #include "nsDebug.h" // for NS_WARNING, etc.
92 #include "nsError.h" // for NS_OK, etc.
93 #include "nsFocusManager.h" // for nsFocusManager
94 #include "nsFrameSelection.h" // for nsFrameSelection
95 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement
96 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir
97 #include "nsIClipboard.h" // for nsIClipboard
98 #include "nsIContent.h" // for nsIContent
99 #include "nsIContentInlines.h" // for nsINode::IsInDesignMode()
100 #include "nsIDocumentEncoder.h" // for nsIDocumentEncoder
101 #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener
102 #include "nsIDocShell.h" // for nsIDocShell
103 #include "nsIEditActionListener.h" // for nsIEditActionListener
104 #include "nsIFrame.h" // for nsIFrame
105 #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc.
106 #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc.
107 #include "nsINode.h" // for nsINode, etc.
108 #include "nsISelectionController.h" // for nsISelectionController, etc.
109 #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc.
110 #include "nsISupportsBase.h" // for nsISupports
111 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF
112 #include "nsITransferable.h" // for nsITransferable
113 #include "nsITransactionManager.h"
114 #include "nsIWeakReference.h" // for nsISupportsWeakReference
115 #include "nsIWidget.h" // for nsIWidget, IMEState, etc.
116 #include "nsPIDOMWindow.h" // for nsPIDOMWindow
117 #include "nsPresContext.h" // for nsPresContext
118 #include "nsRange.h" // for nsRange
119 #include "nsReadableUtils.h" // for EmptyString, ToNewCString
120 #include "nsString.h" // for nsAutoString, nsString, etc.
121 #include "nsStringFwd.h" // for nsString
122 #include "nsStyleConsts.h" // for StyleDirection::Rtl, etc.
123 #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc.
124 #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc.
125 #include "nsStyleUtil.h" // for nsStyleUtil
126 #include "nsTextNode.h" // for nsTextNode
127 #include "nsThreadUtils.h" // for nsRunnable
128 #include "prtime.h" // for PR_Now
130 class nsIOutputStream;
131 class nsITransferable;
133 namespace mozilla {
135 using namespace dom;
136 using namespace widget;
138 using LeafNodeType = HTMLEditUtils::LeafNodeType;
139 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
140 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
142 /*****************************************************************************
143 * mozilla::EditorBase
144 *****************************************************************************/
145 template EditorDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const;
146 template EditorRawDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const;
147 template EditorDOMPoint EditorBase::GetLastIMESelectionEndPoint() const;
148 template EditorRawDOMPoint EditorBase::GetLastIMESelectionEndPoint() const;
150 template CreateContentResult EditorBase::InsertNodeWithTransaction(
151 nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert);
152 template CreateElementResult EditorBase::InsertNodeWithTransaction(
153 Element& aContentToInsert, const EditorDOMPoint& aPointToInsert);
154 template CreateTextResult EditorBase::InsertNodeWithTransaction(
155 Text& aContentToInsert, const EditorDOMPoint& aPointToInsert);
157 template EditorDOMPoint EditorBase::GetFirstSelectionStartPoint() const;
158 template EditorRawDOMPoint EditorBase::GetFirstSelectionStartPoint() const;
159 template EditorDOMPoint EditorBase::GetFirstSelectionEndPoint() const;
160 template EditorRawDOMPoint EditorBase::GetFirstSelectionEndPoint() const;
162 template EditorDOMPoint EditorBase::FindBetterInsertionPoint(
163 const EditorDOMPoint& aPoint) const;
164 template EditorRawDOMPoint EditorBase::FindBetterInsertionPoint(
165 const EditorRawDOMPoint& aPoint) 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 mWrapColumn(0),
182 mNewlineHandling(StaticPrefs::editor_singleLine_pasteNewlines()),
183 mCaretStyle(StaticPrefs::layout_selection_caret_style()),
184 mDocDirtyState(-1),
185 mSpellcheckCheckboxState(eTriUnset),
186 mInitSucceeded(false),
187 mAllowsTransactionsToChangeSelection(true),
188 mDidPreDestroy(false),
189 mDidPostCreate(false),
190 mDispatchInputEvent(true),
191 mIsInEditSubAction(false),
192 mHidingCaret(false),
193 mSpellCheckerDictionaryUpdated(true),
194 mIsHTMLEditorClass(aEditorType == EditorType::HTML) {
195 #ifdef XP_WIN
196 if (!mCaretStyle && !IsTextEditor()) {
197 // Wordpad-like caret behavior.
198 mCaretStyle = 1;
200 #endif // #ifdef XP_WIN
201 if (mNewlineHandling < nsIEditor::eNewlinesPasteIntact ||
202 mNewlineHandling > nsIEditor::eNewlinesStripSurroundingWhitespace) {
203 mNewlineHandling = nsIEditor::eNewlinesPasteToFirst;
207 EditorBase::~EditorBase() {
208 MOZ_ASSERT(!IsInitialized() || mDidPreDestroy,
209 "Why PreDestroy hasn't been called?");
211 if (mComposition) {
212 mComposition->OnEditorDestroyed();
213 mComposition = nullptr;
215 // If this editor is still hiding the caret, we need to restore it.
216 HideCaret(false);
217 mTransactionManager = nullptr;
220 NS_IMPL_CYCLE_COLLECTION_CLASS(EditorBase)
222 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase)
223 // Remove event listeners first since EditorEventListener may need
224 // mDocument, mEventTarget, etc.
225 if (tmp->mEventListener) {
226 tmp->mEventListener->Disconnect();
227 tmp->mEventListener = nullptr;
230 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
231 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionController)
232 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
233 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMEContentObserver)
234 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker)
235 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextServicesDocument)
236 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextInputListener)
237 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransactionManager)
238 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners)
239 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners)
240 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget)
241 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderTransaction)
242 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)
243 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
244 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
246 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditorBase)
247 Document* currentDoc =
248 tmp->mRootElement ? tmp->mRootElement->GetUncomposedDoc() : nullptr;
249 if (currentDoc && nsCCUncollectableMarker::InGeneration(
250 cb, currentDoc->GetMarkedCCGeneration())) {
251 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
253 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement)
254 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionController)
255 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
256 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver)
257 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker)
258 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextServicesDocument)
259 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextInputListener)
260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransactionManager)
261 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners)
262 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners)
263 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget)
264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener)
265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderTransaction)
266 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder)
267 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
269 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditorBase)
270 NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
271 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
272 NS_INTERFACE_MAP_ENTRY(nsIEditor)
273 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor)
274 NS_INTERFACE_MAP_END
276 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorBase)
277 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorBase)
279 nsresult EditorBase::InitInternal(Document& aDocument, Element* aRootElement,
280 nsISelectionController& aSelectionController,
281 uint32_t aFlags) {
282 MOZ_ASSERT_IF(
283 !mEditActionData ||
284 !mEditActionData->HasEditorDestroyedDuringHandlingEditAction(),
285 GetTopLevelEditSubAction() == EditSubAction::eNone);
287 // First only set flags, but other stuff shouldn't be initialized now.
288 // Note that SetFlags() will be called by PostCreate().
289 mFlags = aFlags;
291 mDocument = &aDocument;
292 // nsISelectionController should be stored only when we're a `TextEditor`.
293 // Otherwise, in `HTMLEditor`, it's `PresShell`, and grabbing it causes
294 // a circular reference and memory leak.
295 // XXX Should we move `mSelectionController to `TextEditor`?
296 MOZ_ASSERT_IF(!IsTextEditor(), &aSelectionController == GetPresShell());
297 if (IsTextEditor()) {
298 MOZ_ASSERT(&aSelectionController != GetPresShell());
299 mSelectionController = &aSelectionController;
302 if (mEditActionData) {
303 // During edit action, selection is cached. But this selection is invalid
304 // now since selection controller is updated, so we have to update this
305 // cache.
306 Selection* selection = aSelectionController.GetSelection(
307 nsISelectionController::SELECTION_NORMAL);
308 NS_WARNING_ASSERTION(selection,
309 "SelectionController::GetSelection() failed");
310 if (selection) {
311 mEditActionData->UpdateSelectionCache(*selection);
315 // set up root element if we are passed one.
316 if (aRootElement) {
317 mRootElement = aRootElement;
320 // If this is an editor for <input> or <textarea>, the text node which
321 // has composition string is always recreated with same content. Therefore,
322 // we need to nodify mComposition of text node destruction and replacing
323 // composing string when this receives eCompositionChange event next time.
324 if (mComposition && mComposition->GetContainerTextNode() &&
325 !mComposition->GetContainerTextNode()->IsInComposedDoc()) {
326 mComposition->OnTextNodeRemoved();
329 // Show the caret.
330 DebugOnly<nsresult> rvIgnored = aSelectionController.SetCaretReadOnly(false);
331 NS_WARNING_ASSERTION(
332 NS_SUCCEEDED(rvIgnored),
333 "nsISelectionController::SetCaretReadOnly(false) failed, but ignored");
334 // Show all the selection reflected to user.
335 rvIgnored =
336 aSelectionController.SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
337 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
338 "nsISelectionController::SetSelectionFlags("
339 "nsISelectionDisplay::DISPLAY_ALL) failed, but ignored");
341 MOZ_ASSERT(IsInitialized());
343 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
344 if (NS_WARN_IF(!editActionData.CanHandle())) {
345 return NS_ERROR_FAILURE;
348 SelectionRef().AddSelectionListener(this);
350 // Make sure that the editor will be destroyed properly
351 mDidPreDestroy = false;
352 // Make sure that the editor will be created properly
353 mDidPostCreate = false;
355 return NS_OK;
358 nsresult EditorBase::EnsureEmptyTextFirstChild() {
359 MOZ_ASSERT(IsTextEditor());
360 RefPtr<Element> root = GetRoot();
361 nsIContent* firstChild = root->GetFirstChild();
363 if (!firstChild || !firstChild->IsText()) {
364 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns);
365 if (!newTextNode) {
366 NS_WARNING("EditorBase::CreateTextNode() failed");
367 return NS_ERROR_UNEXPECTED;
369 IgnoredErrorResult ignoredError;
370 root->InsertChildBefore(newTextNode, root->GetFirstChild(), true,
371 ignoredError);
372 MOZ_ASSERT(!ignoredError.Failed());
375 return NS_OK;
378 nsresult EditorBase::InitEditorContentAndSelection() {
379 MOZ_ASSERT(IsEditActionDataAvailable());
381 if (IsTextEditor()) {
382 MOZ_TRY(EnsureEmptyTextFirstChild());
383 } else {
384 nsresult rv = MOZ_KnownLive(AsHTMLEditor())
385 ->MaybeCreatePaddingBRElementForEmptyEditor();
386 if (NS_FAILED(rv)) {
387 NS_WARNING(
388 "HTMLEditor::MaybeCreatePaddingBRElementForEmptyEditor() failed");
389 return rv;
393 // If the selection hasn't been set up yet, set it up collapsed to the end of
394 // our editable content.
395 // XXX I think that this shouldn't do it in `HTMLEditor` because it maybe
396 // removed by the web app and if they call `Selection::AddRange()`,
397 // it may cause multiple selection ranges.
398 if (!SelectionRef().RangeCount()) {
399 nsresult rv = CollapseSelectionToEndOfLastLeafNode();
400 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
401 NS_WARNING("EditorBase::CollapseSelectionToEndOfLastLeafNode() failed");
402 return rv;
406 if (IsInPlaintextMode() && !IsSingleLineEditor()) {
407 nsresult rv = EnsurePaddingBRElementInMultilineEditor();
408 if (NS_FAILED(rv)) {
409 NS_WARNING(
410 "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed");
411 return rv;
415 return NS_OK;
418 nsresult EditorBase::PostCreateInternal() {
419 MOZ_ASSERT(IsEditActionDataAvailable());
421 // Synchronize some stuff for the flags. SetFlags() will initialize
422 // something by the flag difference. This is first time of that, so, all
423 // initializations must be run. For such reason, we need to invert mFlags
424 // value first.
425 mFlags = ~mFlags;
426 nsresult rv = SetFlags(~mFlags);
427 if (NS_FAILED(rv)) {
428 NS_WARNING("EditorBase::SetFlags() failed");
429 return EditorBase::ToGenericNSResult(rv);
432 // These operations only need to happen on the first PostCreate call
433 if (!mDidPostCreate) {
434 mDidPostCreate = true;
436 // Set up listeners
437 CreateEventListeners();
438 nsresult rv = InstallEventListeners();
439 if (NS_FAILED(rv)) {
440 NS_WARNING("EditorBase::InstallEventListeners() failed");
441 return EditorBase::ToGenericNSResult(rv);
444 // nuke the modification count, so the doc appears unmodified
445 // do this before we notify listeners
446 DebugOnly<nsresult> rvIgnored = ResetModificationCount();
447 NS_WARNING_ASSERTION(
448 NS_SUCCEEDED(rvIgnored),
449 "EditorBase::ResetModificationCount() failed, but ignored");
451 // update the UI with our state
452 rvIgnored = NotifyDocumentListeners(eDocumentCreated);
453 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
454 "EditorBase::NotifyDocumentListeners(eDocumentCreated)"
455 " failed, but ignored");
456 rvIgnored = NotifyDocumentListeners(eDocumentStateChanged);
457 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
458 "EditorBase::NotifyDocumentListeners("
459 "eDocumentStateChanged) failed, but ignored");
462 // update nsTextStateManager and caret if we have focus
463 if (RefPtr<Element> focusedElement = GetFocusedElement()) {
464 DebugOnly<nsresult> rvIgnored = InitializeSelection(*focusedElement);
465 NS_WARNING_ASSERTION(
466 NS_SUCCEEDED(rvIgnored),
467 "EditorBase::InitializeSelection() failed, but ignored");
469 // If the text control gets reframed during focus, Focus() would not be
470 // called, so take a chance here to see if we need to spell check the text
471 // control.
472 nsresult rv = FlushPendingSpellCheck();
473 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
474 NS_WARNING(
475 "EditorBase::FlushPendingSpellCheck() caused destroying the editor");
476 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
478 NS_WARNING_ASSERTION(
479 NS_SUCCEEDED(rv),
480 "EditorBase::FlushPendingSpellCheck() failed, but ignored");
482 IMEState newState;
483 rv = GetPreferredIMEState(&newState);
484 if (NS_FAILED(rv)) {
485 NS_WARNING("EditorBase::GetPreferredIMEState() failed");
486 return NS_OK;
488 IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
491 // FYI: This call might cause destroying this editor.
492 IMEStateManager::OnEditorInitialized(*this);
494 return NS_OK;
497 void EditorBase::SetTextInputListener(TextInputListener* aTextInputListener) {
498 MOZ_ASSERT(!mTextInputListener || !aTextInputListener ||
499 mTextInputListener == aTextInputListener);
500 mTextInputListener = aTextInputListener;
503 void EditorBase::SetIMEContentObserver(
504 IMEContentObserver* aIMEContentObserver) {
505 MOZ_ASSERT(!mIMEContentObserver || !aIMEContentObserver ||
506 mIMEContentObserver == aIMEContentObserver);
507 mIMEContentObserver = aIMEContentObserver;
510 void EditorBase::CreateEventListeners() {
511 // Don't create the handler twice
512 if (!mEventListener) {
513 mEventListener = new EditorEventListener();
517 nsresult EditorBase::InstallEventListeners() {
518 if (NS_WARN_IF(!IsInitialized()) || NS_WARN_IF(!mEventListener)) {
519 return NS_ERROR_NOT_INITIALIZED;
522 // Initialize the event target.
523 mEventTarget = GetExposedRoot();
524 if (NS_WARN_IF(!mEventTarget)) {
525 return NS_ERROR_NOT_AVAILABLE;
528 nsresult rv = mEventListener->Connect(this);
529 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
530 "EditorEventListener::Connect() failed");
531 if (mComposition) {
532 // If mComposition has already been destroyed, we should forget it.
533 // This may happen if it ended while we don't listen to composition
534 // events.
535 if (mComposition->Destroyed()) {
536 // XXX We may need to fix existing composition transaction here.
537 // However, this may be called when it's not safe.
538 // Perhaps, we should stop handling composition with events.
539 mComposition = nullptr;
541 // Otherwise, Restart to handle composition with new editor contents.
542 else {
543 mComposition->StartHandlingComposition(this);
546 return rv;
549 void EditorBase::RemoveEventListeners() {
550 if (!IsInitialized() || !mEventListener) {
551 return;
553 mEventListener->Disconnect();
554 if (mComposition) {
555 // Even if this is called, don't release mComposition because this is
556 // may be reused after reframing.
557 mComposition->EndHandlingComposition(this);
559 mEventTarget = nullptr;
562 bool EditorBase::IsListeningToEvents() const {
563 return IsInitialized() && mEventListener &&
564 !mEventListener->DetachedFromEditor();
567 bool EditorBase::GetDesiredSpellCheckState() {
568 // Check user override on this element
569 if (mSpellcheckCheckboxState != eTriUnset) {
570 return (mSpellcheckCheckboxState == eTriTrue);
573 // Check user preferences
574 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
576 if (!spellcheckLevel) {
577 return false; // Spellchecking forced off globally
580 if (!CanEnableSpellCheck()) {
581 return false;
584 PresShell* presShell = GetPresShell();
585 if (presShell) {
586 nsPresContext* context = presShell->GetPresContext();
587 if (context && !context->IsDynamic()) {
588 return false;
592 // Check DOM state
593 nsCOMPtr<nsIContent> content = GetExposedRoot();
594 if (!content) {
595 return false;
598 auto element = nsGenericHTMLElement::FromNode(content);
599 if (!element) {
600 return false;
603 if (!IsInPlaintextMode()) {
604 // Some of the page content might be editable and some not, if spellcheck=
605 // is explicitly set anywhere, so if there's anything editable on the page,
606 // return true and let the spellchecker figure it out.
607 Document* doc = content->GetComposedDoc();
608 return doc && doc->IsEditingOn();
611 return element->Spellcheck();
614 void EditorBase::PreDestroyInternal() {
615 MOZ_ASSERT(!mDidPreDestroy);
617 mInitSucceeded = false;
619 Selection* selection = GetSelection();
620 if (selection) {
621 selection->RemoveSelectionListener(this);
624 IMEStateManager::OnEditorDestroying(*this);
626 // Let spellchecker clean up its observers etc. It is important not to
627 // actually free the spellchecker here, since the spellchecker could have
628 // caused flush notifications, which could have gotten here if a textbox
629 // is being removed. Setting the spellchecker to nullptr could free the
630 // object that is still in use! It will be freed when the editor is
631 // destroyed.
632 if (mInlineSpellChecker) {
633 DebugOnly<nsresult> rvIgnored =
634 mInlineSpellChecker->Cleanup(IsTextEditor());
635 NS_WARNING_ASSERTION(
636 NS_SUCCEEDED(rvIgnored),
637 "mozInlineSpellChecker::Cleanup() failed, but ignored");
640 // tell our listeners that the doc is going away
641 DebugOnly<nsresult> rvIgnored =
642 NotifyDocumentListeners(eDocumentToBeDestroyed);
643 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
644 "EditorBase::NotifyDocumentListeners("
645 "eDocumentToBeDestroyed) failed, but ignored");
647 // Unregister event listeners
648 RemoveEventListeners();
649 // If this editor is still hiding the caret, we need to restore it.
650 HideCaret(false);
651 mActionListeners.Clear();
652 mDocStateListeners.Clear();
653 mInlineSpellChecker = nullptr;
654 mTextServicesDocument = nullptr;
655 mTextInputListener = nullptr;
656 mSpellcheckCheckboxState = eTriUnset;
657 mRootElement = nullptr;
659 // Transaction may grab this instance. Therefore, they should be released
660 // here for stopping the circular reference with this instance.
661 if (mTransactionManager) {
662 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo();
663 NS_WARNING_ASSERTION(disabledUndoRedo,
664 "EditorBase::DisableUndoRedo() failed, but ignored");
665 mTransactionManager = nullptr;
668 if (mEditActionData) {
669 mEditActionData->OnEditorDestroy();
672 mDidPreDestroy = true;
675 NS_IMETHODIMP EditorBase::GetFlags(uint32_t* aFlags) {
676 // NOTE: If you need to override this method, you need to make Flags()
677 // virtual.
678 *aFlags = Flags();
679 return NS_OK;
682 NS_IMETHODIMP EditorBase::SetFlags(uint32_t aFlags) {
683 if (mFlags == aFlags) {
684 return NS_OK;
687 // If we're a `TextEditor` instance, the plaintext mode should always be set.
688 // If we're an `HTMLEditor` instance, either is fine.
689 MOZ_ASSERT_IF(IsTextEditor(), !!(aFlags & nsIEditor::eEditorPlaintextMask));
690 // If we're an `HTMLEditor` instance, we cannot treat it as a single line
691 // editor. So, eEditorSingleLineMask is available only when we're a
692 // `TextEditor` instance.
693 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorSingleLineMask));
694 // If we're an `HTMLEditor` instance, we cannot treat it as a password editor.
695 // So, eEditorPasswordMask is available only when we're a `TextEditor`
696 // instance.
697 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorPasswordMask));
698 // eEditorAllowInteraction changes the behavior of `HTMLEditor`. So, it's
699 // not available with `TextEditor` instance.
700 MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorAllowInteraction));
702 const bool isCalledByPostCreate = (mFlags == ~aFlags);
703 // We don't support dynamic password flag change.
704 MOZ_ASSERT_IF(!isCalledByPostCreate,
705 !((mFlags ^ aFlags) & nsIEditor::eEditorPasswordMask));
706 bool spellcheckerWasEnabled = !isCalledByPostCreate && CanEnableSpellCheck();
707 mFlags = aFlags;
709 if (!IsInitialized()) {
710 // If we're initializing, we shouldn't do anything now.
711 // SetFlags() will be called by PostCreate(),
712 // we should synchronize some stuff for the flags at that time.
713 return NS_OK;
716 // The flag change may cause the spellchecker state change
717 if (CanEnableSpellCheck() != spellcheckerWasEnabled) {
718 SyncRealTimeSpell();
721 // If this is called from PostCreate(), it will update the IME state if it's
722 // necessary.
723 if (!mDidPostCreate) {
724 return NS_OK;
727 // Might be changing editable state, so, we need to reset current IME state
728 // if we're focused and the flag change causes IME state change.
729 if (RefPtr<Element> focusedElement = GetFocusedElement()) {
730 IMEState newState;
731 nsresult rv = GetPreferredIMEState(&newState);
732 NS_WARNING_ASSERTION(
733 NS_SUCCEEDED(rv),
734 "EditorBase::GetPreferredIMEState() failed, but ignored");
735 if (NS_SUCCEEDED(rv)) {
736 // NOTE: When the enabled state isn't going to be modified, this method
737 // is going to do nothing.
738 IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
742 return NS_OK;
745 NS_IMETHODIMP EditorBase::GetIsSelectionEditable(bool* aIsSelectionEditable) {
746 if (NS_WARN_IF(!aIsSelectionEditable)) {
747 return NS_ERROR_INVALID_ARG;
749 *aIsSelectionEditable = IsSelectionEditable();
750 return NS_OK;
753 bool EditorBase::IsSelectionEditable() {
754 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
755 if (NS_WARN_IF(!editActionData.CanHandle())) {
756 return false;
759 if (IsTextEditor()) {
760 // XXX we just check that the anchor node is editable at the moment
761 // we should check that all nodes in the selection are editable
762 const nsINode* anchorNode = SelectionRef().GetAnchorNode();
763 return anchorNode && anchorNode->IsContent() && anchorNode->IsEditable();
766 const nsINode* anchorNode = SelectionRef().GetAnchorNode();
767 const nsINode* focusNode = SelectionRef().GetFocusNode();
768 if (!anchorNode || !focusNode) {
769 return false;
772 // if anchorNode or focusNode is in a native anonymous subtree, HTMLEditor
773 // shouldn't edit content in it.
774 // XXX This must be a bug of Selection API.
775 if (MOZ_UNLIKELY(anchorNode->IsInNativeAnonymousSubtree() ||
776 focusNode->IsInNativeAnonymousSubtree())) {
777 return false;
780 // Per the editing spec as of June 2012: we have to have a selection whose
781 // start and end nodes are editable, and which share an ancestor editing
782 // host. (Bug 766387.)
783 bool isSelectionEditable = SelectionRef().RangeCount() &&
784 anchorNode->IsEditable() &&
785 focusNode->IsEditable();
786 if (!isSelectionEditable) {
787 return false;
790 const nsINode* commonAncestor =
791 SelectionRef().GetAnchorFocusRange()->GetClosestCommonInclusiveAncestor();
792 while (commonAncestor && !commonAncestor->IsEditable()) {
793 commonAncestor = commonAncestor->GetParentNode();
795 // If there is no editable common ancestor, return false.
796 return !!commonAncestor;
799 NS_IMETHODIMP EditorBase::GetIsDocumentEditable(bool* aIsDocumentEditable) {
800 if (NS_WARN_IF(!aIsDocumentEditable)) {
801 return NS_ERROR_INVALID_ARG;
803 RefPtr<Document> document = GetDocument();
804 *aIsDocumentEditable = document && IsModifiable();
805 return NS_OK;
808 NS_IMETHODIMP EditorBase::GetDocument(Document** aDocument) {
809 if (NS_WARN_IF(!aDocument)) {
810 return NS_ERROR_INVALID_ARG;
812 *aDocument = do_AddRef(mDocument).take();
813 return NS_WARN_IF(!*aDocument) ? NS_ERROR_NOT_INITIALIZED : NS_OK;
816 already_AddRefed<nsIWidget> EditorBase::GetWidget() const {
817 nsPresContext* presContext = GetPresContext();
818 if (NS_WARN_IF(!presContext)) {
819 return nullptr;
821 nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
822 return NS_WARN_IF(!widget) ? nullptr : widget.forget();
825 NS_IMETHODIMP EditorBase::GetContentsMIMEType(nsAString& aContentsMIMEType) {
826 aContentsMIMEType = mContentMIMEType;
827 return NS_OK;
830 NS_IMETHODIMP EditorBase::SetContentsMIMEType(
831 const nsAString& aContentsMIMEType) {
832 mContentMIMEType.Assign(aContentsMIMEType);
833 return NS_OK;
836 NS_IMETHODIMP EditorBase::GetSelectionController(
837 nsISelectionController** aSelectionController) {
838 if (NS_WARN_IF(!aSelectionController)) {
839 return NS_ERROR_INVALID_ARG;
841 *aSelectionController = do_AddRef(GetSelectionController()).take();
842 return NS_WARN_IF(!*aSelectionController) ? NS_ERROR_FAILURE : NS_OK;
845 NS_IMETHODIMP EditorBase::DeleteSelection(EDirection aAction,
846 EStripWrappers aStripWrappers) {
847 nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers);
848 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
849 "EditorBase::DeleteSelectionAsAction() failed");
850 return rv;
853 NS_IMETHODIMP EditorBase::GetSelection(Selection** aSelection) {
854 nsresult rv = GetSelection(SelectionType::eNormal, aSelection);
855 NS_WARNING_ASSERTION(
856 NS_SUCCEEDED(rv),
857 "EditorBase::GetSelection(SelectionType::eNormal) failed");
858 return rv;
861 nsresult EditorBase::GetSelection(SelectionType aSelectionType,
862 Selection** aSelection) const {
863 if (NS_WARN_IF(!aSelection)) {
864 return NS_ERROR_INVALID_ARG;
866 if (IsEditActionDataAvailable()) {
867 *aSelection = do_AddRef(&SelectionRef()).take();
868 return NS_OK;
870 nsISelectionController* selectionController = GetSelectionController();
871 if (NS_WARN_IF(!selectionController)) {
872 *aSelection = nullptr;
873 return NS_ERROR_NOT_INITIALIZED;
875 *aSelection = do_AddRef(selectionController->GetSelection(
876 ToRawSelectionType(aSelectionType)))
877 .take();
878 return NS_WARN_IF(!*aSelection) ? NS_ERROR_FAILURE : NS_OK;
881 nsresult EditorBase::DoTransactionInternal(nsITransaction* aTransaction) {
882 MOZ_ASSERT(IsEditActionDataAvailable());
883 MOZ_ASSERT(!ShouldAlreadyHaveHandledBeforeInputEventDispatching(),
884 "beforeinput event hasn't been dispatched yet");
886 if (mPlaceholderBatch && !mPlaceholderTransaction) {
887 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderName);
888 mPlaceholderTransaction = PlaceholderTransaction::Create(
889 *this, *mPlaceholderName, std::move(mSelState));
890 MOZ_ASSERT(mSelState.isNothing());
892 // We will recurse, but will not hit this case in the nested call
893 RefPtr<PlaceholderTransaction> placeholderTransaction =
894 mPlaceholderTransaction;
895 DebugOnly<nsresult> rvIgnored =
896 DoTransactionInternal(placeholderTransaction);
897 NS_WARNING_ASSERTION(
898 NS_SUCCEEDED(rvIgnored),
899 "EditorBase::DoTransactionInternal() failed, but ignored");
901 if (mTransactionManager) {
902 if (nsCOMPtr<nsITransaction> topTransaction =
903 mTransactionManager->PeekUndoStack()) {
904 if (RefPtr<EditTransactionBase> topTransactionBase =
905 topTransaction->GetAsEditTransactionBase()) {
906 if (PlaceholderTransaction* topPlaceholderTransaction =
907 topTransactionBase->GetAsPlaceholderTransaction()) {
908 // there is a placeholder transaction on top of the undo stack. It
909 // is either the one we just created, or an earlier one that we are
910 // now merging into. From here on out remember this placeholder
911 // instead of the one we just created.
912 mPlaceholderTransaction = topPlaceholderTransaction;
919 if (aTransaction) {
920 // XXX: Why are we doing selection specific batching stuff here?
921 // XXX: Most entry points into the editor have auto variables that
922 // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make
923 // XXX: these selection batch calls no-ops.
924 // XXX:
925 // XXX: I suspect that this was placed here to avoid multiple
926 // XXX: selection changed notifications from happening until after
927 // XXX: the transaction was done. I suppose that can still happen
928 // XXX: if an embedding application called DoTransaction() directly
929 // XXX: to pump its own transactions through the system, but in that
930 // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or
931 // XXX: its auto equivalent AutoUpdateViewBatch to ensure that
932 // XXX: selection listeners have access to accurate frame data?
933 // XXX:
934 // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls
935 // XXX: we will need to make sure that they are disabled during
936 // XXX: the init of the editor for text widgets to avoid layout
937 // XXX: re-entry during initial reflow. - kin
939 // get the selection and start a batch change
940 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
942 if (mTransactionManager) {
943 RefPtr<TransactionManager> transactionManager(mTransactionManager);
944 nsresult rv = transactionManager->DoTransaction(aTransaction);
945 if (NS_FAILED(rv)) {
946 NS_WARNING("TransactionManager::DoTransaction() failed");
947 return rv;
949 } else {
950 nsresult rv = aTransaction->DoTransaction();
951 if (NS_FAILED(rv)) {
952 NS_WARNING("nsITransaction::DoTransaction() failed");
953 return rv;
957 DoAfterDoTransaction(aTransaction);
960 return NS_OK;
963 NS_IMETHODIMP EditorBase::EnableUndo(bool aEnable) {
964 // XXX Should we return NS_ERROR_FAILURE if EdnableUndoRedo() or
965 // DisableUndoRedo() returns false?
966 if (aEnable) {
967 DebugOnly<bool> enabledUndoRedo = EnableUndoRedo();
968 NS_WARNING_ASSERTION(enabledUndoRedo,
969 "EditorBase::EnableUndoRedo() failed, but ignored");
970 return NS_OK;
972 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo();
973 NS_WARNING_ASSERTION(disabledUndoRedo,
974 "EditorBase::DisableUndoRedo() failed, but ignored");
975 return NS_OK;
978 NS_IMETHODIMP EditorBase::GetTransactionManager(
979 nsITransactionManager** aTransactionManager) {
980 if (NS_WARN_IF(!aTransactionManager)) {
981 return NS_ERROR_INVALID_ARG;
983 if (NS_WARN_IF(!mTransactionManager)) {
984 return NS_ERROR_FAILURE;
986 *aTransactionManager = do_AddRef(mTransactionManager).take();
987 return NS_OK;
990 NS_IMETHODIMP EditorBase::Undo(uint32_t aCount) {
991 nsresult rv = UndoAsAction(aCount);
992 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed");
993 return rv;
996 NS_IMETHODIMP EditorBase::CanUndo(bool* aIsEnabled, bool* aCanUndo) {
997 if (NS_WARN_IF(!aIsEnabled) || NS_WARN_IF(!aCanUndo)) {
998 return NS_ERROR_INVALID_ARG;
1000 *aCanUndo = CanUndo();
1001 *aIsEnabled = IsUndoRedoEnabled();
1002 return NS_OK;
1005 NS_IMETHODIMP EditorBase::Redo(uint32_t aCount) {
1006 nsresult rv = RedoAsAction(aCount);
1007 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::RedoAsAction() failed");
1008 return rv;
1011 NS_IMETHODIMP EditorBase::CanRedo(bool* aIsEnabled, bool* aCanRedo) {
1012 if (NS_WARN_IF(!aIsEnabled) || NS_WARN_IF(!aCanRedo)) {
1013 return NS_ERROR_INVALID_ARG;
1015 *aCanRedo = CanRedo();
1016 *aIsEnabled = IsUndoRedoEnabled();
1017 return NS_OK;
1020 nsresult EditorBase::UndoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) {
1021 if (aCount == 0 || IsReadonly()) {
1022 return NS_OK;
1025 // If we don't have transaction in the undo stack, we shouldn't notify
1026 // anybody of trying to undo since it's not useful notification but we
1027 // need to pay some runtime cost.
1028 if (!CanUndo()) {
1029 return NS_OK;
1032 // If there is composition, we shouldn't allow to undo with committing
1033 // composition since Chrome doesn't allow it and it doesn't make sense
1034 // because committing composition causes one transaction and Undo(1)
1035 // undoes the committing composition.
1036 if (GetComposition()) {
1037 return NS_OK;
1040 AutoEditActionDataSetter editActionData(*this, EditAction::eUndo, aPrincipal);
1041 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1042 if (NS_FAILED(rv)) {
1043 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1044 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1045 return EditorBase::ToGenericNSResult(rv);
1048 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
1050 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1051 if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) {
1052 return NS_ERROR_FAILURE;
1055 rv = NS_OK;
1057 IgnoredErrorResult ignoredError;
1058 AutoEditSubActionNotifier startToHandleEditSubAction(
1059 *this, EditSubAction::eUndo, nsIEditor::eNone, ignoredError);
1060 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1061 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
1063 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1064 "TextEditor::OnStartToHandleTopLevelEditSubAction() "
1065 "failed, but ignored");
1067 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1068 for (uint32_t i = 0; i < aCount; ++i) {
1069 if (NS_FAILED(transactionManager->Undo())) {
1070 NS_WARNING("TransactionManager::Undo() failed");
1071 break;
1073 DoAfterUndoTransaction();
1076 if (IsHTMLEditor()) {
1077 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor();
1081 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1082 return EditorBase::ToGenericNSResult(rv);
1085 nsresult EditorBase::RedoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) {
1086 if (aCount == 0 || IsReadonly()) {
1087 return NS_OK;
1090 // If we don't have transaction in the redo stack, we shouldn't notify
1091 // anybody of trying to redo since it's not useful notification but we
1092 // need to pay some runtime cost.
1093 if (!CanRedo()) {
1094 return NS_OK;
1097 // If there is composition, we shouldn't allow to redo with committing
1098 // composition since Chrome doesn't allow it and it doesn't make sense
1099 // because committing composition causes removing all transactions from
1100 // the redo queue. So, it becomes impossible to redo anything.
1101 if (GetComposition()) {
1102 return NS_OK;
1105 AutoEditActionDataSetter editActionData(*this, EditAction::eRedo, aPrincipal);
1106 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1107 if (NS_FAILED(rv)) {
1108 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1109 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1110 return EditorBase::ToGenericNSResult(rv);
1113 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
1115 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1116 if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) {
1117 return NS_ERROR_FAILURE;
1120 rv = NS_OK;
1122 IgnoredErrorResult ignoredError;
1123 AutoEditSubActionNotifier startToHandleEditSubAction(
1124 *this, EditSubAction::eRedo, nsIEditor::eNone, ignoredError);
1125 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1126 return ignoredError.StealNSResult();
1128 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1129 "TextEditor::OnStartToHandleTopLevelEditSubAction() "
1130 "failed, but ignored");
1132 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1133 for (uint32_t i = 0; i < aCount; ++i) {
1134 if (NS_FAILED(transactionManager->Redo())) {
1135 NS_WARNING("TransactionManager::Redo() failed");
1136 break;
1138 DoAfterRedoTransaction();
1141 if (IsHTMLEditor()) {
1142 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor();
1146 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1147 return EditorBase::ToGenericNSResult(rv);
1150 NS_IMETHODIMP EditorBase::BeginTransaction() {
1151 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown);
1152 if (NS_WARN_IF(!editActionData.CanHandle())) {
1153 return NS_ERROR_FAILURE;
1156 BeginTransactionInternal(__FUNCTION__);
1157 return NS_OK;
1160 void EditorBase::BeginTransactionInternal(const char* aRequesterFuncName) {
1161 BeginUpdateViewBatch(aRequesterFuncName);
1163 if (NS_WARN_IF(!mTransactionManager)) {
1164 return;
1167 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1168 DebugOnly<nsresult> rvIgnored = transactionManager->BeginBatch(nullptr);
1169 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1170 "TransactionManager::BeginBatch() failed, but ignored");
1173 NS_IMETHODIMP EditorBase::EndTransaction() {
1174 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown);
1175 if (NS_WARN_IF(!editActionData.CanHandle())) {
1176 return NS_ERROR_FAILURE;
1179 EndTransactionInternal(__FUNCTION__);
1180 return NS_OK;
1183 void EditorBase::EndTransactionInternal(const char* aRequesterFuncName) {
1184 if (mTransactionManager) {
1185 RefPtr<TransactionManager> transactionManager(mTransactionManager);
1186 DebugOnly<nsresult> rvIgnored = transactionManager->EndBatch(false);
1187 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1188 "TransactionManager::EndBatch() failed, but ignored");
1191 EndUpdateViewBatch(aRequesterFuncName);
1194 void EditorBase::BeginPlaceholderTransaction(nsStaticAtom& aTransactionName,
1195 const char* aRequesterFuncName) {
1196 MOZ_ASSERT(IsEditActionDataAvailable());
1197 MOZ_ASSERT(mPlaceholderBatch >= 0, "negative placeholder batch count!");
1199 if (!mPlaceholderBatch) {
1200 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1201 // time to turn on the batch
1202 BeginUpdateViewBatch(aRequesterFuncName);
1203 mPlaceholderTransaction = nullptr;
1204 mPlaceholderName = &aTransactionName;
1205 mSelState.emplace();
1206 mSelState->SaveSelection(SelectionRef());
1207 // Composition transaction can modify multiple nodes and it merges text
1208 // node for ime into single text node.
1209 // So if current selection is into IME text node, it might be failed
1210 // to restore selection by UndoTransaction.
1211 // So we need update selection by range updater.
1212 if (mPlaceholderName == nsGkAtoms::IMETxnName) {
1213 RangeUpdaterRef().RegisterSelectionState(*mSelState);
1216 mPlaceholderBatch++;
1219 void EditorBase::EndPlaceholderTransaction(
1220 ScrollSelectionIntoView aScrollSelectionIntoView,
1221 const char* aRequesterFuncName) {
1222 MOZ_ASSERT(IsEditActionDataAvailable());
1223 MOZ_ASSERT(mPlaceholderBatch > 0,
1224 "zero or negative placeholder batch count when ending batch!");
1226 if (!(--mPlaceholderBatch)) {
1227 // By making the assumption that no reflow happens during the calls
1228 // to EndUpdateViewBatch and ScrollSelectionFocusIntoView, we are able to
1229 // allow the selection to cache a frame offset which is used by the
1230 // caret drawing code. We only enable this cache here; at other times,
1231 // we have no way to know whether reflow invalidates it
1232 // See bugs 35296 and 199412.
1233 SelectionRef().SetCanCacheFrameOffset(true);
1235 // time to turn off the batch
1236 EndUpdateViewBatch(aRequesterFuncName);
1237 // make sure selection is in view
1239 // After ScrollSelectionFocusIntoView(), the pending notifications might be
1240 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1241 // XXX Even if we're destroyed, we need to keep handling below because
1242 // this method changes a lot of status. We should rewrite this safer.
1243 if (aScrollSelectionIntoView == ScrollSelectionIntoView::Yes) {
1244 DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView();
1245 NS_WARNING_ASSERTION(
1246 NS_SUCCEEDED(rvIgnored),
1247 "EditorBase::ScrollSelectionFocusIntoView() failed, but Ignored");
1250 // cached for frame offset are Not available now
1251 SelectionRef().SetCanCacheFrameOffset(false);
1253 if (mSelState) {
1254 // we saved the selection state, but never got to hand it to placeholder
1255 // (else we ould have nulled out this pointer), so destroy it to prevent
1256 // leaks.
1257 if (mPlaceholderName == nsGkAtoms::IMETxnName) {
1258 RangeUpdaterRef().DropSelectionState(*mSelState);
1260 mSelState.reset();
1262 // We might have never made a placeholder if no action took place.
1263 if (mPlaceholderTransaction) {
1264 // FYI: Disconnect placeholder transaction before dispatching "input"
1265 // event because an input event listener may start other things.
1266 // TODO: We should forget EditActionDataSetter too.
1267 RefPtr<PlaceholderTransaction> placeholderTransaction =
1268 std::move(mPlaceholderTransaction);
1269 DebugOnly<nsresult> rvIgnored =
1270 placeholderTransaction->EndPlaceHolderBatch();
1271 NS_WARNING_ASSERTION(
1272 NS_SUCCEEDED(rvIgnored),
1273 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored");
1274 // notify editor observers of action but if composing, it's done by
1275 // compositionchange event handler.
1276 if (!mComposition) {
1277 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1279 } else {
1280 NotifyEditorObservers(eNotifyEditorObserversOfCancel);
1285 NS_IMETHODIMP EditorBase::SetShouldTxnSetSelection(bool aShould) {
1286 MakeThisAllowTransactionsToChangeSelection(aShould);
1287 return NS_OK;
1290 NS_IMETHODIMP EditorBase::GetDocumentIsEmpty(bool* aDocumentIsEmpty) {
1291 MOZ_ASSERT(aDocumentIsEmpty);
1292 *aDocumentIsEmpty = IsEmpty();
1293 return NS_OK;
1296 // XXX: The rule system should tell us which node to select all on (ie, the
1297 // root, or the body)
1298 NS_IMETHODIMP EditorBase::SelectAll() {
1299 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1300 if (NS_WARN_IF(!editActionData.CanHandle())) {
1301 return NS_ERROR_NOT_INITIALIZED;
1304 nsresult rv = SelectAllInternal();
1305 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SelectAllInternal() failed");
1306 // This is low level API for XUL applcation. So, we should return raw
1307 // error code here.
1308 return rv;
1311 nsresult EditorBase::SelectAllInternal() {
1312 MOZ_ASSERT(IsInitialized());
1314 DebugOnly<nsresult> rvIgnored = CommitComposition();
1315 if (NS_WARN_IF(Destroyed())) {
1316 return NS_ERROR_EDITOR_DESTROYED;
1318 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1319 "EditorBase::CommitComposition() failed, but ignored");
1321 // XXX Do we need to keep handling after committing composition causes moving
1322 // focus to different element? Although TextEditor has independent
1323 // selection, so, we may not see any odd behavior even in such case.
1325 nsresult rv = SelectEntireDocument();
1326 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1327 "EditorBase::SelectEntireDocument() failed");
1328 return rv;
1331 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP EditorBase::BeginningOfDocument() {
1332 MOZ_ASSERT(IsTextEditor());
1334 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1335 if (NS_WARN_IF(!editActionData.CanHandle())) {
1336 return NS_ERROR_NOT_INITIALIZED;
1339 // get the root element
1340 RefPtr<Element> rootElement = GetRoot();
1341 if (NS_WARN_IF(!rootElement)) {
1342 return NS_ERROR_NULL_POINTER;
1345 // find first editable thingy
1346 nsCOMPtr<nsIContent> firstEditableLeaf;
1347 // If we're `TextEditor`, the first editable leaf node is a text node or
1348 // padding `<br>` element. In the first case, we need to collapse selection
1349 // into it.
1350 if (rootElement->GetFirstChild() && rootElement->GetFirstChild()->IsText()) {
1351 firstEditableLeaf = rootElement->GetFirstChild();
1353 if (!firstEditableLeaf) {
1354 // just the root node, set selection to inside the root
1355 nsresult rv = CollapseSelectionToStartOf(*rootElement);
1356 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1357 "EditorBase::CollapseSelectionToStartOf() failed");
1358 return rv;
1361 if (firstEditableLeaf->IsText()) {
1362 // If firstEditableLeaf is text, set selection to beginning of the text
1363 // node.
1364 nsresult rv = CollapseSelectionToStartOf(*firstEditableLeaf);
1365 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1366 "EditorBase::CollapseSelectionToStartOf() failed");
1367 return rv;
1370 // Otherwise, it's a leaf node and we set the selection just in front of it.
1371 nsCOMPtr<nsIContent> parent = firstEditableLeaf->GetParent();
1372 if (NS_WARN_IF(!parent)) {
1373 return NS_ERROR_NULL_POINTER;
1376 MOZ_ASSERT(
1377 parent->ComputeIndexOf(firstEditableLeaf).valueOr(UINT32_MAX) == 0,
1378 "How come the first node isn't the left most child in its parent?");
1379 nsresult rv = CollapseSelectionToStartOf(*parent);
1380 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1381 "EditorBase::CollapseSelectionToStartOf() failed");
1382 return rv;
1385 NS_IMETHODIMP EditorBase::EndOfDocument() {
1386 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1387 if (NS_WARN_IF(!editActionData.CanHandle())) {
1388 return NS_ERROR_NOT_INITIALIZED;
1390 nsresult rv = CollapseSelectionToEndOfLastLeafNode();
1391 NS_WARNING_ASSERTION(
1392 NS_SUCCEEDED(rv),
1393 "EditorBase::CollapseSelectionToEndOfLastLeafNode() failed");
1394 // This is low level API for XUL applcation. So, we should return raw
1395 // error code here.
1396 return rv;
1399 nsresult EditorBase::CollapseSelectionToEndOfLastLeafNode() const {
1400 MOZ_ASSERT(IsEditActionDataAvailable());
1402 // XXX Why doesn't this check if the document is alive?
1403 if (NS_WARN_IF(!IsInitialized())) {
1404 return NS_ERROR_NOT_INITIALIZED;
1407 // get the root element
1408 Element* rootElement = GetRoot();
1409 if (NS_WARN_IF(!rootElement)) {
1410 return NS_ERROR_NULL_POINTER;
1413 nsIContent* lastLeafContent = rootElement;
1414 if (IsTextEditor()) {
1415 lastLeafContent = rootElement->GetFirstChild();
1416 MOZ_ASSERT(lastLeafContent && lastLeafContent->IsText());
1417 } else {
1418 for (nsIContent* child = lastLeafContent->GetLastChild();
1419 child && HTMLEditUtils::IsContainerNode(*child);
1420 child = child->GetLastChild()) {
1421 lastLeafContent = child;
1425 nsresult rv =
1426 CollapseSelectionToEndOf(OwningNonNull<nsINode>(*lastLeafContent));
1427 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1428 "EditorBase::CollapseSelectionToEndOf() failed");
1429 return rv;
1432 NS_IMETHODIMP EditorBase::GetDocumentModified(bool* aOutDocModified) {
1433 if (NS_WARN_IF(!aOutDocModified)) {
1434 return NS_ERROR_INVALID_ARG;
1437 int32_t modCount = 0;
1438 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount);
1439 NS_WARNING_ASSERTION(
1440 NS_SUCCEEDED(rvIgnored),
1441 "EditorBase::GetModificationCount() failed, but ignored");
1443 *aOutDocModified = (modCount != 0);
1444 return NS_OK;
1447 NS_IMETHODIMP EditorBase::GetDocumentCharacterSet(nsACString& aCharacterSet) {
1448 return NS_ERROR_NOT_AVAILABLE;
1451 nsresult EditorBase::GetDocumentCharsetInternal(nsACString& aCharset) const {
1452 Document* document = GetDocument();
1453 if (NS_WARN_IF(!document)) {
1454 return NS_ERROR_NOT_INITIALIZED;
1456 document->GetDocumentCharacterSet()->Name(aCharset);
1457 return NS_OK;
1460 NS_IMETHODIMP EditorBase::SetDocumentCharacterSet(
1461 const nsACString& aCharacterSet) {
1462 return NS_ERROR_NOT_AVAILABLE;
1465 NS_IMETHODIMP EditorBase::OutputToString(const nsAString& aFormatType,
1466 uint32_t aDocumentEncoderFlags,
1467 nsAString& aOutputString) {
1468 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1469 if (NS_WARN_IF(!editActionData.CanHandle())) {
1470 return NS_ERROR_NOT_INITIALIZED;
1473 nsresult rv =
1474 ComputeValueInternal(aFormatType, aDocumentEncoderFlags, aOutputString);
1475 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1476 "EditorBase::ComputeValueInternal() failed");
1477 // This is low level API for XUL application. So, we should return raw
1478 // error code here.
1479 return rv;
1482 nsresult EditorBase::ComputeValueInternal(const nsAString& aFormatType,
1483 uint32_t aDocumentEncoderFlags,
1484 nsAString& aOutputString) const {
1485 MOZ_ASSERT(IsEditActionDataAvailable());
1487 // First, let's try to get the value simply only from text node if the
1488 // caller wants plaintext value.
1489 if (aFormatType.LowerCaseEqualsLiteral("text/plain") &&
1490 !(aDocumentEncoderFlags & (nsIDocumentEncoder::OutputSelectionOnly |
1491 nsIDocumentEncoder::OutputWrap))) {
1492 // Shortcut for empty editor case.
1493 if (IsEmpty()) {
1494 aOutputString.Truncate();
1495 return NS_OK;
1497 // NOTE: If it's neither <input type="text"> nor <textarea>, e.g., an HTML
1498 // editor which is in plaintext mode (e.g., plaintext email composer on
1499 // Thunderbird), it should be handled by the expensive path.
1500 if (IsTextEditor()) {
1501 // If it's necessary to check selection range or the editor wraps hard,
1502 // we need some complicated handling. In such case, we need to use the
1503 // expensive path.
1504 // XXX Anything else what we cannot return the text node data simply?
1505 EditActionResult result =
1506 AsTextEditor()->ComputeValueFromTextNodeAndBRElement(aOutputString);
1507 if (result.Failed() || result.Canceled() || result.Handled()) {
1508 NS_WARNING_ASSERTION(
1509 result.Succeeded(),
1510 "TextEditor::ComputeValueFromTextNodeAndBRElement() failed");
1511 return result.Rv();
1516 nsAutoCString charset;
1517 nsresult rv = GetDocumentCharsetInternal(charset);
1518 if (NS_FAILED(rv) || charset.IsEmpty()) {
1519 charset.AssignLiteral("windows-1252"); // XXX Why don't we use "UTF-8"?
1522 nsCOMPtr<nsIDocumentEncoder> encoder =
1523 GetAndInitDocEncoder(aFormatType, aDocumentEncoderFlags, charset);
1524 if (!encoder) {
1525 NS_WARNING("EditorBase::GetAndInitDocEncoder() failed");
1526 return NS_ERROR_FAILURE;
1529 rv = encoder->EncodeToString(aOutputString);
1530 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1531 "nsIDocumentEncoder::EncodeToString() failed");
1532 return rv;
1535 already_AddRefed<nsIDocumentEncoder> EditorBase::GetAndInitDocEncoder(
1536 const nsAString& aFormatType, uint32_t aDocumentEncoderFlags,
1537 const nsACString& aCharset) const {
1538 MOZ_ASSERT(IsEditActionDataAvailable());
1540 nsCOMPtr<nsIDocumentEncoder> docEncoder;
1541 if (!mCachedDocumentEncoder ||
1542 !mCachedDocumentEncoderType.Equals(aFormatType)) {
1543 nsAutoCString formatType;
1544 LossyAppendUTF16toASCII(aFormatType, formatType);
1545 docEncoder = do_createDocumentEncoder(PromiseFlatCString(formatType).get());
1546 if (NS_WARN_IF(!docEncoder)) {
1547 return nullptr;
1549 mCachedDocumentEncoder = docEncoder;
1550 mCachedDocumentEncoderType = aFormatType;
1551 } else {
1552 docEncoder = mCachedDocumentEncoder;
1555 RefPtr<Document> doc = GetDocument();
1556 NS_ASSERTION(doc, "Need a document");
1558 nsresult rv = docEncoder->NativeInit(
1559 doc, aFormatType,
1560 aDocumentEncoderFlags | nsIDocumentEncoder::RequiresReinitAfterOutput);
1561 if (NS_FAILED(rv)) {
1562 NS_WARNING("nsIDocumentEncoder::NativeInit() failed");
1563 return nullptr;
1566 if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) {
1567 DebugOnly<nsresult> rvIgnored = docEncoder->SetCharset(aCharset);
1568 NS_WARNING_ASSERTION(
1569 NS_SUCCEEDED(rvIgnored),
1570 "nsIDocumentEncoder::SetCharset() failed, but ignored");
1573 const int32_t wrapWidth = std::max(WrapWidth(), 0);
1574 DebugOnly<nsresult> rvIgnored = docEncoder->SetWrapColumn(wrapWidth);
1575 NS_WARNING_ASSERTION(
1576 NS_SUCCEEDED(rvIgnored),
1577 "nsIDocumentEncoder::SetWrapColumn() failed, but ignored");
1579 // Set the selection, if appropriate.
1580 // We do this either if the OutputSelectionOnly flag is set,
1581 // in which case we use our existing selection ...
1582 if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) {
1583 if (NS_FAILED(docEncoder->SetSelection(&SelectionRef()))) {
1584 NS_WARNING("nsIDocumentEncoder::SetSelection() failed");
1585 return nullptr;
1588 // ... or if the root element is not a body,
1589 // in which case we set the selection to encompass the root.
1590 else {
1591 Element* rootElement = GetRoot();
1592 if (NS_WARN_IF(!rootElement)) {
1593 return nullptr;
1595 if (!rootElement->IsHTMLElement(nsGkAtoms::body)) {
1596 if (NS_FAILED(docEncoder->SetContainerNode(rootElement))) {
1597 NS_WARNING("nsIDocumentEncoder::SetContainerNode() failed");
1598 return nullptr;
1603 return docEncoder.forget();
1606 bool EditorBase::AreClipboardCommandsUnconditionallyEnabled() const {
1607 Document* document = GetDocument();
1608 return document && document->AreClipboardCommandsUnconditionallyEnabled();
1611 bool EditorBase::CheckForClipboardCommandListener(
1612 nsAtom* aCommand, EventMessage aEventMessage) const {
1613 RefPtr<Document> document = GetDocument();
1614 if (!document) {
1615 return false;
1618 // We exclude XUL and chrome docs here to maintain current behavior where
1619 // in these cases the editor element alone is expected to handle clipboard
1620 // command availability.
1621 if (!document->AreClipboardCommandsUnconditionallyEnabled()) {
1622 return false;
1625 // So in web content documents, "unconditionally" enabled Cut/Copy are not
1626 // really unconditional; they're enabled if there is a listener that wants
1627 // to handle them. What they're not conditional on here is whether there is
1628 // currently a selection in the editor.
1629 RefPtr<PresShell> presShell = document->GetObservingPresShell();
1630 if (!presShell) {
1631 return false;
1633 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
1634 if (!presContext) {
1635 return false;
1638 RefPtr<EventTarget> et = GetDOMEventTarget();
1639 while (et) {
1640 EventListenerManager* elm = et->GetExistingListenerManager();
1641 if (elm && elm->HasListenersFor(aCommand)) {
1642 return true;
1644 InternalClipboardEvent event(true, aEventMessage);
1645 EventChainPreVisitor visitor(presContext, &event, nullptr,
1646 nsEventStatus_eIgnore, false, et);
1647 et->GetEventTargetParent(visitor);
1648 et = visitor.GetParentTarget();
1651 return false;
1654 bool EditorBase::FireClipboardEvent(EventMessage aEventMessage,
1655 int32_t aClipboardType,
1656 bool* aActionTaken) {
1657 MOZ_ASSERT(IsEditActionDataAvailable());
1659 if (aEventMessage == ePaste) {
1660 CommitComposition();
1663 RefPtr<PresShell> presShell = GetPresShell();
1664 if (NS_WARN_IF(!presShell)) {
1665 return false;
1668 RefPtr<Selection> sel = &SelectionRef();
1669 if (IsHTMLEditor() && aEventMessage == eCopy && sel->IsCollapsed()) {
1670 // If we don't have a usable selection for copy and we're an HTML editor
1671 // (which is global for the document) try to use the last focused selection
1672 // instead.
1673 sel = nsCopySupport::GetSelectionForCopy(GetDocument());
1676 const bool clipboardEventCanceled = !nsCopySupport::FireClipboardEvent(
1677 aEventMessage, aClipboardType, presShell, sel, aActionTaken);
1678 NotifyOfDispatchingClipboardEvent();
1680 // If the event handler caused the editor to be destroyed, return false.
1681 // Otherwise return true if the event was not cancelled.
1682 return !clipboardEventCanceled && !mDidPreDestroy;
1685 NS_IMETHODIMP EditorBase::Cut() {
1686 nsresult rv = CutAsAction();
1687 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed");
1688 return rv;
1691 nsresult EditorBase::CutAsAction(nsIPrincipal* aPrincipal) {
1692 AutoEditActionDataSetter editActionData(*this, EditAction::eCut, aPrincipal);
1693 if (NS_WARN_IF(!editActionData.CanHandle())) {
1694 return NS_ERROR_NOT_INITIALIZED;
1697 bool actionTaken = false;
1698 if (!FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
1699 return EditorBase::ToGenericNSResult(
1700 actionTaken ? NS_OK : NS_ERROR_EDITOR_ACTION_CANCELED);
1703 // Dispatch "beforeinput" event after dispatching "cut" event.
1704 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
1705 if (NS_FAILED(rv)) {
1706 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1707 "MaybeDispatchBeforeInputEvent() failed");
1708 return EditorBase::ToGenericNSResult(rv);
1710 // XXX This transaction name is referred by PlaceholderTransaction::Merge()
1711 // so that we need to keep using it here.
1712 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName,
1713 ScrollSelectionIntoView::Yes,
1714 __FUNCTION__);
1715 rv = DeleteSelectionAsSubAction(
1716 eNone, IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
1717 NS_WARNING_ASSERTION(
1718 NS_SUCCEEDED(rv),
1719 "EditorBase::DeleteSelectionAsSubAction(eNone) failed, but ignored");
1720 return EditorBase::ToGenericNSResult(rv);
1723 NS_IMETHODIMP EditorBase::CanCut(bool* aCanCut) {
1724 if (NS_WARN_IF(!aCanCut)) {
1725 return NS_ERROR_INVALID_ARG;
1727 *aCanCut = IsCutCommandEnabled();
1728 return NS_OK;
1731 bool EditorBase::IsCutCommandEnabled() const {
1732 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1733 if (NS_WARN_IF(!editActionData.CanHandle())) {
1734 return false;
1737 if (IsModifiable() && IsCopyToClipboardAllowedInternal()) {
1738 return true;
1741 // If there's an event listener for "cut", we always enable the command
1742 // as we don't really know what the listener may want to do in response.
1743 // We look up the event target chain for a possible listener on a parent
1744 // in addition to checking the immediate target.
1745 return CheckForClipboardCommandListener(nsGkAtoms::oncut, eCut);
1748 NS_IMETHODIMP EditorBase::Copy() {
1749 AutoEditActionDataSetter editActionData(*this, EditAction::eCopy);
1750 if (NS_WARN_IF(!editActionData.CanHandle())) {
1751 return NS_ERROR_NOT_INITIALIZED;
1754 bool actionTaken = false;
1755 FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken);
1757 return EditorBase::ToGenericNSResult(
1758 actionTaken ? NS_OK : NS_ERROR_EDITOR_ACTION_CANCELED);
1761 NS_IMETHODIMP EditorBase::CanCopy(bool* aCanCopy) {
1762 if (NS_WARN_IF(!aCanCopy)) {
1763 return NS_ERROR_INVALID_ARG;
1765 *aCanCopy = IsCopyCommandEnabled();
1766 return NS_OK;
1769 bool EditorBase::IsCopyCommandEnabled() const {
1770 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1771 if (NS_WARN_IF(!editActionData.CanHandle())) {
1772 return false;
1775 if (IsCopyToClipboardAllowedInternal()) {
1776 return true;
1779 // Like "cut", always enable "copy" if there's a listener.
1780 return CheckForClipboardCommandListener(nsGkAtoms::oncopy, eCopy);
1783 NS_IMETHODIMP EditorBase::Paste(int32_t aClipboardType) {
1784 return NS_ERROR_NOT_IMPLEMENTED;
1787 nsresult EditorBase::PrepareToInsertContent(
1788 const EditorDOMPoint& aPointToInsert, bool aDoDeleteSelection) {
1789 // TODO: Move this method to `EditorBase`.
1790 MOZ_ASSERT(IsEditActionDataAvailable());
1792 MOZ_ASSERT(aPointToInsert.IsSet());
1794 EditorDOMPoint pointToInsert(aPointToInsert);
1795 if (aDoDeleteSelection) {
1796 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
1797 nsresult rv = DeleteSelectionAsSubAction(
1798 nsIEditor::eNone,
1799 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
1800 if (NS_FAILED(rv)) {
1801 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed");
1802 return rv;
1806 nsresult rv = CollapseSelectionTo(pointToInsert);
1807 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1808 "EditorBase::CollapseSelectionTo() failed");
1809 return rv;
1812 nsresult EditorBase::InsertTextAt(const nsAString& aStringToInsert,
1813 const EditorDOMPoint& aPointToInsert,
1814 bool aDoDeleteSelection) {
1815 MOZ_ASSERT(IsEditActionDataAvailable());
1816 MOZ_ASSERT(aPointToInsert.IsSet());
1818 nsresult rv = PrepareToInsertContent(aPointToInsert, aDoDeleteSelection);
1819 if (NS_FAILED(rv)) {
1820 NS_WARNING("EditorBase::PrepareToInsertContent() failed");
1821 return rv;
1824 rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
1825 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1826 "EditorBase::InsertTextAsSubAction() failed");
1827 return rv;
1830 bool EditorBase::IsSafeToInsertData(nsIPrincipal* aSourcePrincipal) const {
1831 // Try to determine whether we should use a sanitizing fragment sink
1832 bool isSafe = false;
1834 RefPtr<Document> destdoc = GetDocument();
1835 NS_ASSERTION(destdoc, "Where is our destination doc?");
1837 nsIDocShell* docShell = nullptr;
1838 if (RefPtr<BrowsingContext> bc = destdoc->GetBrowsingContext()) {
1839 RefPtr<BrowsingContext> root = bc->Top();
1840 MOZ_ASSERT(root, "root should not be null");
1842 docShell = root->GetDocShell();
1845 isSafe = docShell && docShell->GetAppType() == nsIDocShell::APP_TYPE_EDITOR;
1847 if (!isSafe && aSourcePrincipal) {
1848 nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
1849 NS_ASSERTION(destPrincipal, "How come we don't have a principal?");
1850 DebugOnly<nsresult> rvIgnored =
1851 aSourcePrincipal->Subsumes(destPrincipal, &isSafe);
1852 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1853 "nsIPrincipal::Subsumes() failed, but ignored");
1856 return isSafe;
1859 NS_IMETHODIMP EditorBase::PasteTransferable(nsITransferable* aTransferable) {
1860 nsresult rv = PasteTransferableAsAction(aTransferable);
1861 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1862 "EditorBase::PasteTransferableAsAction() failed");
1863 return rv;
1866 NS_IMETHODIMP EditorBase::CanPaste(int32_t aClipboardType, bool* aCanPaste) {
1867 if (NS_WARN_IF(!aCanPaste)) {
1868 return NS_ERROR_INVALID_ARG;
1870 *aCanPaste = CanPaste(aClipboardType);
1871 return NS_OK;
1874 NS_IMETHODIMP EditorBase::SetAttribute(Element* aElement,
1875 const nsAString& aAttribute,
1876 const nsAString& aValue) {
1877 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) {
1878 return NS_ERROR_INVALID_ARG;
1881 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
1882 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1883 if (NS_FAILED(rv)) {
1884 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1885 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1886 return EditorBase::ToGenericNSResult(rv);
1889 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
1890 rv = SetAttributeWithTransaction(*aElement, *attribute, aValue);
1891 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1892 "EditorBase::SetAttributeWithTransaction() failed");
1893 return EditorBase::ToGenericNSResult(rv);
1896 nsresult EditorBase::SetAttributeWithTransaction(Element& aElement,
1897 nsAtom& aAttribute,
1898 const nsAString& aValue) {
1899 RefPtr<ChangeAttributeTransaction> transaction =
1900 ChangeAttributeTransaction::Create(aElement, aAttribute, aValue);
1901 nsresult rv = DoTransactionInternal(transaction);
1902 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1903 "EditorBase::DoTransactionInternal() failed");
1904 return rv;
1907 NS_IMETHODIMP EditorBase::RemoveAttribute(Element* aElement,
1908 const nsAString& aAttribute) {
1909 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) {
1910 return NS_ERROR_INVALID_ARG;
1913 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
1914 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1915 if (NS_FAILED(rv)) {
1916 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1917 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
1918 return EditorBase::ToGenericNSResult(rv);
1921 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
1922 rv = RemoveAttributeWithTransaction(*aElement, *attribute);
1923 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1924 "EditorBase::RemoveAttributeWithTransaction() failed");
1925 return EditorBase::ToGenericNSResult(rv);
1928 nsresult EditorBase::RemoveAttributeWithTransaction(Element& aElement,
1929 nsAtom& aAttribute) {
1930 // XXX If aElement doesn't have aAttribute, shouldn't we stop creating
1931 // the transaction? Otherwise, there will be added a transaction
1932 // which does nothing at doing undo/redo.
1933 RefPtr<ChangeAttributeTransaction> transaction =
1934 ChangeAttributeTransaction::CreateToRemove(aElement, aAttribute);
1935 nsresult rv = DoTransactionInternal(transaction);
1936 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1937 "EditorBase::DoTransactionInternal() failed");
1938 return rv;
1941 nsresult EditorBase::MarkElementDirty(Element& aElement) const {
1942 // Mark the node dirty, but not for webpages (bug 599983)
1943 if (!OutputsMozDirty()) {
1944 return NS_OK;
1946 DebugOnly<nsresult> rvIgnored =
1947 aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::mozdirty, u""_ns, false);
1948 NS_WARNING_ASSERTION(
1949 NS_SUCCEEDED(rvIgnored),
1950 "Element::SetAttr(nsGkAtoms::mozdirty) failed, but ignored");
1951 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
1954 NS_IMETHODIMP EditorBase::GetInlineSpellChecker(
1955 bool aAutoCreate, nsIInlineSpellChecker** aInlineSpellChecker) {
1956 if (NS_WARN_IF(!aInlineSpellChecker)) {
1957 return NS_ERROR_INVALID_ARG;
1960 if (mDidPreDestroy) {
1961 // Don't allow people to get or create the spell checker once the editor
1962 // is going away.
1963 *aInlineSpellChecker = nullptr;
1964 return aAutoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK;
1967 // We don't want to show the spell checking UI if there are no spell check
1968 // dictionaries available.
1969 if (!mozInlineSpellChecker::CanEnableInlineSpellChecking()) {
1970 *aInlineSpellChecker = nullptr;
1971 return NS_ERROR_FAILURE;
1974 if (!mInlineSpellChecker && aAutoCreate) {
1975 mInlineSpellChecker = new mozInlineSpellChecker();
1978 if (mInlineSpellChecker) {
1979 nsresult rv = mInlineSpellChecker->Init(this);
1980 if (NS_FAILED(rv)) {
1981 NS_WARNING("mozInlineSpellChecker::Init() failed");
1982 mInlineSpellChecker = nullptr;
1983 return rv;
1987 *aInlineSpellChecker = do_AddRef(mInlineSpellChecker).take();
1988 return NS_OK;
1991 void EditorBase::SyncRealTimeSpell() {
1992 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1993 if (NS_WARN_IF(!editActionData.CanHandle())) {
1994 return;
1997 bool enable = GetDesiredSpellCheckState();
1999 // Initializes mInlineSpellChecker
2000 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
2001 DebugOnly<nsresult> rvIgnored =
2002 GetInlineSpellChecker(enable, getter_AddRefs(spellChecker));
2003 NS_WARNING_ASSERTION(
2004 NS_SUCCEEDED(rvIgnored),
2005 "EditorBase::GetInlineSpellChecker() failed, but ignored");
2007 if (mInlineSpellChecker) {
2008 if (!mSpellCheckerDictionaryUpdated && enable) {
2009 DebugOnly<nsresult> rvIgnored =
2010 mInlineSpellChecker->UpdateCurrentDictionary();
2011 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2012 "mozInlineSpellChecker::UpdateCurrentDictionary() "
2013 "failed, but ignored");
2014 mSpellCheckerDictionaryUpdated = true;
2017 // We might have a mInlineSpellChecker even if there are no dictionaries
2018 // available since we don't destroy the mInlineSpellChecker when the last
2019 // dictionariy is removed, but in that case spellChecker is null
2020 DebugOnly<nsresult> rvIgnored =
2021 mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker);
2022 NS_WARNING_ASSERTION(
2023 NS_SUCCEEDED(rvIgnored),
2024 "mozInlineSpellChecker::SetEnableRealTimeSpell() failed, but ignored");
2028 NS_IMETHODIMP EditorBase::SetSpellcheckUserOverride(bool enable) {
2029 mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse;
2030 SyncRealTimeSpell();
2031 return NS_OK;
2034 NS_IMETHODIMP EditorBase::InsertNode(nsINode* aNodeToInsert,
2035 nsINode* aContainer, uint32_t aOffset) {
2036 nsCOMPtr<nsIContent> contentToInsert = do_QueryInterface(aNodeToInsert);
2037 if (NS_WARN_IF(!contentToInsert) || NS_WARN_IF(!aContainer)) {
2038 return NS_ERROR_NULL_POINTER;
2041 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertNode);
2042 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2043 if (NS_FAILED(rv)) {
2044 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2045 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2046 return EditorBase::ToGenericNSResult(rv);
2049 const uint32_t offset = std::min(aOffset, aContainer->Length());
2050 CreateContentResult insertContentResult = InsertNodeWithTransaction(
2051 *contentToInsert, EditorDOMPoint(aContainer, offset));
2052 if (insertContentResult.isErr()) {
2053 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
2054 return EditorBase::ToGenericNSResult(insertContentResult.unwrapErr());
2056 rv = insertContentResult.SuggestCaretPointTo(
2057 *this, {SuggestCaret::OnlyIfHasSuggestion,
2058 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2059 SuggestCaret::AndIgnoreTrivialError});
2060 if (NS_FAILED(rv)) {
2061 NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
2062 return EditorBase::ToGenericNSResult(rv);
2064 NS_WARNING_ASSERTION(
2065 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2066 "CreateContentResult::SuggestCaretPointTo() failed, but ignored");
2067 return NS_OK;
2070 template <typename ContentNodeType>
2071 CreateNodeResultBase<ContentNodeType> EditorBase::InsertNodeWithTransaction(
2072 ContentNodeType& aContentToInsert, const EditorDOMPoint& aPointToInsert) {
2073 MOZ_ASSERT(IsEditActionDataAvailable());
2074 MOZ_ASSERT_IF(IsTextEditor(), !aContentToInsert.IsText());
2076 using ResultType = CreateNodeResultBase<ContentNodeType>;
2078 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
2079 return ResultType(NS_ERROR_INVALID_ARG);
2081 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
2083 IgnoredErrorResult ignoredError;
2084 AutoEditSubActionNotifier startToHandleEditSubAction(
2085 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError);
2086 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2087 return ResultType(ignoredError.StealNSResult());
2089 NS_WARNING_ASSERTION(
2090 !ignoredError.Failed(),
2091 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2093 RefPtr<InsertNodeTransaction> transaction =
2094 InsertNodeTransaction::Create(*this, aContentToInsert, aPointToInsert);
2095 nsresult rv = DoTransactionInternal(transaction);
2096 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2097 "EditorBase::DoTransactionInternal() failed");
2099 DebugOnly<nsresult> rvIgnored =
2100 RangeUpdaterRef().SelAdjInsertNode(aPointToInsert);
2101 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2102 "RangeUpdater::SelAdjInsertNode() failed, but ignored");
2104 if (IsHTMLEditor()) {
2105 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToInsert);
2108 if (NS_WARN_IF(Destroyed())) {
2109 return ResultType(NS_ERROR_EDITOR_DESTROYED);
2111 if (NS_FAILED(rv)) {
2112 return ResultType(rv);
2115 return ResultType(&aContentToInsert,
2116 transaction->SuggestPointToPutCaret<EditorDOMPoint>());
2119 CreateElementResult
2120 EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction(
2121 const EditorDOMPoint& aPointToInsert) {
2122 MOZ_ASSERT(IsEditActionDataAvailable());
2123 MOZ_ASSERT(IsHTMLEditor() || !aPointToInsert.IsInTextNode());
2125 if (MOZ_UNLIKELY(!aPointToInsert.IsSet())) {
2126 return CreateElementResult(NS_ERROR_FAILURE);
2129 EditorDOMPoint pointToInsert;
2130 if (IsTextEditor()) {
2131 pointToInsert = aPointToInsert;
2132 } else {
2133 Result<EditorDOMPoint, nsresult> maybePointToInsert =
2134 MOZ_KnownLive(AsHTMLEditor())->PrepareToInsertBRElement(aPointToInsert);
2135 if (maybePointToInsert.isErr()) {
2136 return CreateElementResult(maybePointToInsert.unwrapErr());
2138 MOZ_ASSERT(maybePointToInsert.inspect().IsSetAndValid());
2139 pointToInsert = maybePointToInsert.unwrap();
2142 RefPtr<Element> newBRElement = CreateHTMLContent(nsGkAtoms::br);
2143 if (NS_WARN_IF(!newBRElement)) {
2144 return CreateElementResult(NS_ERROR_FAILURE);
2146 newBRElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
2148 CreateElementResult insertBRElementResult =
2149 InsertNodeWithTransaction<Element>(*newBRElement, pointToInsert);
2150 NS_WARNING_ASSERTION(insertBRElementResult.isOk(),
2151 "EditorBase::InsertNodeWithTransaction() failed");
2152 return insertBRElementResult;
2155 NS_IMETHODIMP EditorBase::DeleteNode(nsINode* aNode) {
2156 if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aNode->IsContent())) {
2157 return NS_ERROR_INVALID_ARG;
2160 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveNode);
2161 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2162 if (NS_FAILED(rv)) {
2163 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2164 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2165 return EditorBase::ToGenericNSResult(rv);
2168 rv = DeleteNodeWithTransaction(MOZ_KnownLive(*aNode->AsContent()));
2169 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2170 "EditorBase::DeleteNodeWithTransaction() failed");
2171 return EditorBase::ToGenericNSResult(rv);
2174 nsresult EditorBase::DeleteNodeWithTransaction(nsIContent& aContent) {
2175 MOZ_ASSERT(IsEditActionDataAvailable());
2176 MOZ_ASSERT_IF(IsTextEditor(), !aContent.IsText());
2178 // Do nothing if the node is read-only.
2179 if (IsHTMLEditor() && NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aContent))) {
2180 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2183 IgnoredErrorResult ignoredError;
2184 AutoEditSubActionNotifier startToHandleEditSubAction(
2185 *this, EditSubAction::eDeleteNode, nsIEditor::ePrevious, ignoredError);
2186 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2187 return ignoredError.StealNSResult();
2189 NS_WARNING_ASSERTION(
2190 !ignoredError.Failed(),
2191 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2193 if (IsHTMLEditor()) {
2194 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContent);
2197 // FYI: DeleteNodeTransaction grabs aContent while it's alive. So, it's safe
2198 // to refer aContent even after calling DoTransaction().
2199 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
2200 DeleteNodeTransaction::MaybeCreate(*this, aContent);
2201 NS_WARNING_ASSERTION(deleteNodeTransaction,
2202 "DeleteNodeTransaction::MaybeCreate() failed");
2203 nsresult rv;
2204 if (deleteNodeTransaction) {
2205 rv = DoTransactionInternal(deleteNodeTransaction);
2206 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2207 "EditorBase::DoTransactionInternal() failed");
2209 if (mTextServicesDocument && NS_SUCCEEDED(rv)) {
2210 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
2211 textServicesDocument->DidDeleteContent(aContent);
2213 } else {
2214 rv = NS_ERROR_FAILURE;
2217 if (!mActionListeners.IsEmpty()) {
2218 for (auto& listener : mActionListeners.Clone()) {
2219 DebugOnly<nsresult> rvIgnored = listener->DidDeleteNode(&aContent, rv);
2220 NS_WARNING_ASSERTION(
2221 NS_SUCCEEDED(rvIgnored),
2222 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
2226 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
2229 NS_IMETHODIMP EditorBase::NotifySelectionChanged(Document* aDocument,
2230 Selection* aSelection,
2231 int16_t aReason,
2232 int32_t aAmount) {
2233 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
2234 return NS_ERROR_INVALID_ARG;
2237 if (mTextInputListener) {
2238 RefPtr<TextInputListener> textInputListener = mTextInputListener;
2239 textInputListener->OnSelectionChange(*aSelection, aReason);
2242 if (mIMEContentObserver) {
2243 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2244 observer->OnSelectionChange(*aSelection);
2247 return NS_OK;
2250 void EditorBase::NotifyEditorObservers(
2251 NotificationForEditorObservers aNotification) {
2252 MOZ_ASSERT(IsEditActionDataAvailable());
2254 switch (aNotification) {
2255 case eNotifyEditorObserversOfEnd:
2256 mIsInEditSubAction = false;
2258 if (mEditActionData) {
2259 mEditActionData->MarkAsHandled();
2262 if (mTextInputListener) {
2263 // TODO: TextInputListener::OnEditActionHandled() may return
2264 // NS_ERROR_OUT_OF_MEMORY. If so and if
2265 // TextControlState::SetValue() setting value with us, we should
2266 // return the result to EditorBase::ReplaceTextAsAction(),
2267 // EditorBase::DeleteSelectionAsAction() and
2268 // TextEditor::InsertTextAsAction(). However, it requires a lot
2269 // of changes in editor classes, but it's not so important since
2270 // editor does not use fallible allocation. Therefore, normally,
2271 // the process must be crashed anyway.
2272 RefPtr<TextInputListener> listener = mTextInputListener;
2273 nsresult rv =
2274 listener->OnEditActionHandled(MOZ_KnownLive(*AsTextEditor()));
2275 MOZ_RELEASE_ASSERT(rv != NS_ERROR_OUT_OF_MEMORY,
2276 "Setting value failed due to out of memory");
2277 NS_WARNING_ASSERTION(
2278 NS_SUCCEEDED(rv),
2279 "TextInputListener::OnEditActionHandled() failed, but ignored");
2282 if (mIMEContentObserver) {
2283 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2284 observer->OnEditActionHandled();
2287 if (!mDispatchInputEvent || IsEditActionAborted() ||
2288 IsEditActionCanceled()) {
2289 break;
2292 DispatchInputEvent();
2293 break;
2294 case eNotifyEditorObserversOfBefore:
2295 if (NS_WARN_IF(mIsInEditSubAction)) {
2296 return;
2299 mIsInEditSubAction = true;
2301 if (mIMEContentObserver) {
2302 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2303 observer->BeforeEditAction();
2305 return;
2306 case eNotifyEditorObserversOfCancel:
2307 mIsInEditSubAction = false;
2309 if (mEditActionData) {
2310 mEditActionData->MarkAsHandled();
2313 if (mIMEContentObserver) {
2314 RefPtr<IMEContentObserver> observer = mIMEContentObserver;
2315 observer->CancelEditAction();
2317 break;
2318 default:
2319 MOZ_CRASH("Handle all notifications here");
2320 break;
2323 if (IsHTMLEditor() && !Destroyed()) {
2324 // We may need to show resizing handles or update existing ones after
2325 // all transactions are done. This way of doing is preferred to DOM
2326 // mutation events listeners because all the changes the user can apply
2327 // to a document may result in multiple events, some of them quite hard
2328 // to listen too (in particular when an ancestor of the selection is
2329 // changed but the selection itself is not changed).
2330 DebugOnly<nsresult> rvIgnored =
2331 MOZ_KnownLive(AsHTMLEditor())->RefreshEditingUI();
2332 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2333 "HTMLEditor::RefreshEditingUI() failed, but ignored");
2337 void EditorBase::DispatchInputEvent() {
2338 MOZ_ASSERT(IsEditActionDataAvailable());
2339 MOZ_ASSERT(!IsEditActionCanceled(),
2340 "If preceding beforeinput event is canceled, we shouldn't "
2341 "dispatch input event");
2342 MOZ_ASSERT(
2343 !ShouldAlreadyHaveHandledBeforeInputEventDispatching(),
2344 "We've not handled beforeinput event but trying to dispatch input event");
2346 // We don't need to dispatch multiple input events if there is a pending
2347 // input event. However, it may have different event target. If we resolved
2348 // this issue, we need to manage the pending events in an array. But it's
2349 // overwork. We don't need to do it for the very rare case.
2350 // TODO: However, we start to set InputEvent.inputType. So, each "input"
2351 // event now notifies web app each change. So, perhaps, we should
2352 // not omit input events.
2354 RefPtr<Element> targetElement = GetInputEventTargetElement();
2355 if (NS_WARN_IF(!targetElement)) {
2356 return;
2358 RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer();
2359 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
2360 targetElement, eEditorInput, ToInputType(GetEditAction()), this,
2361 dataTransfer ? InputEventOptions(dataTransfer,
2362 InputEventOptions::NeverCancelable::No)
2363 : InputEventOptions(GetInputEventData(),
2364 InputEventOptions::NeverCancelable::No));
2365 NS_WARNING_ASSERTION(
2366 NS_SUCCEEDED(rvIgnored),
2367 "nsContentUtils::DispatchInputEvent() failed, but ignored");
2370 NS_IMETHODIMP EditorBase::AddEditActionListener(
2371 nsIEditActionListener* aListener) {
2372 if (NS_WARN_IF(!aListener)) {
2373 return NS_ERROR_INVALID_ARG;
2376 // If given edit action listener is text services document for the inline
2377 // spell checker, store it as reference of concrete class for performance
2378 // reason.
2379 if (mInlineSpellChecker) {
2380 EditorSpellCheck* editorSpellCheck =
2381 mInlineSpellChecker->GetEditorSpellCheck();
2382 if (editorSpellCheck) {
2383 mozSpellChecker* spellChecker = editorSpellCheck->GetSpellChecker();
2384 if (spellChecker) {
2385 TextServicesDocument* textServicesDocument =
2386 spellChecker->GetTextServicesDocument();
2387 if (static_cast<nsIEditActionListener*>(textServicesDocument) ==
2388 aListener) {
2389 mTextServicesDocument = textServicesDocument;
2390 return NS_OK;
2396 // Make sure the listener isn't already on the list
2397 if (!mActionListeners.Contains(aListener)) {
2398 mActionListeners.AppendElement(*aListener);
2399 NS_WARNING_ASSERTION(
2400 mActionListeners.Length() != 1,
2401 "nsIEditActionListener installed, this editor becomes slower");
2404 return NS_OK;
2407 NS_IMETHODIMP EditorBase::RemoveEditActionListener(
2408 nsIEditActionListener* aListener) {
2409 if (NS_WARN_IF(!aListener)) {
2410 return NS_ERROR_INVALID_ARG;
2413 if (static_cast<nsIEditActionListener*>(mTextServicesDocument) == aListener) {
2414 mTextServicesDocument = nullptr;
2415 return NS_OK;
2418 NS_WARNING_ASSERTION(mActionListeners.Length() != 1,
2419 "All nsIEditActionListeners have been removed, this "
2420 "editor becomes faster");
2421 mActionListeners.RemoveElement(aListener);
2423 return NS_OK;
2426 NS_IMETHODIMP EditorBase::AddDocumentStateListener(
2427 nsIDocumentStateListener* aListener) {
2428 if (NS_WARN_IF(!aListener)) {
2429 return NS_ERROR_INVALID_ARG;
2432 if (!mDocStateListeners.Contains(aListener)) {
2433 mDocStateListeners.AppendElement(*aListener);
2436 return NS_OK;
2439 NS_IMETHODIMP EditorBase::RemoveDocumentStateListener(
2440 nsIDocumentStateListener* aListener) {
2441 if (NS_WARN_IF(!aListener)) {
2442 return NS_ERROR_INVALID_ARG;
2445 mDocStateListeners.RemoveElement(aListener);
2447 return NS_OK;
2450 NS_IMETHODIMP EditorBase::ForceCompositionEnd() {
2451 nsresult rv = CommitComposition();
2452 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2453 "EditorBase::CommitComposition() failed");
2454 return rv;
2457 nsresult EditorBase::CommitComposition() {
2458 nsPresContext* presContext = GetPresContext();
2459 if (NS_WARN_IF(!presContext)) {
2460 return NS_ERROR_NOT_AVAILABLE;
2463 if (!mComposition) {
2464 return NS_OK;
2466 nsresult rv =
2467 IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, presContext);
2468 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "IMEStateManager::NotifyIME() failed");
2469 return rv;
2472 nsresult EditorBase::GetPreferredIMEState(IMEState* aState) {
2473 if (NS_WARN_IF(!aState)) {
2474 return NS_ERROR_INVALID_ARG;
2477 aState->mEnabled = IMEEnabled::Enabled;
2478 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
2480 if (IsReadonly()) {
2481 aState->mEnabled = IMEEnabled::Disabled;
2482 return NS_OK;
2485 Element* rootElement = GetRoot();
2486 if (NS_WARN_IF(!rootElement)) {
2487 return NS_ERROR_FAILURE;
2490 nsIFrame* frameForRootElement = rootElement->GetPrimaryFrame();
2491 if (NS_WARN_IF(!frameForRootElement)) {
2492 return NS_ERROR_FAILURE;
2495 switch (frameForRootElement->StyleUIReset()->mIMEMode) {
2496 case StyleImeMode::Auto:
2497 if (IsPasswordEditor()) {
2498 aState->mEnabled = IMEEnabled::Password;
2500 break;
2501 case StyleImeMode::Disabled:
2502 // we should use password state for |ime-mode: disabled;|.
2503 aState->mEnabled = IMEEnabled::Password;
2504 break;
2505 case StyleImeMode::Active:
2506 aState->mOpen = IMEState::OPEN;
2507 break;
2508 case StyleImeMode::Inactive:
2509 aState->mOpen = IMEState::CLOSED;
2510 break;
2511 case StyleImeMode::Normal:
2512 break;
2515 return NS_OK;
2518 NS_IMETHODIMP EditorBase::GetComposing(bool* aResult) {
2519 if (NS_WARN_IF(!aResult)) {
2520 return NS_ERROR_INVALID_ARG;
2522 *aResult = IsIMEComposing();
2523 return NS_OK;
2526 NS_IMETHODIMP EditorBase::GetRootElement(Element** aRootElement) {
2527 if (NS_WARN_IF(!aRootElement)) {
2528 return NS_ERROR_INVALID_ARG;
2530 *aRootElement = do_AddRef(mRootElement).take();
2531 return NS_WARN_IF(!*aRootElement) ? NS_ERROR_NOT_AVAILABLE : NS_OK;
2534 void EditorBase::OnStartToHandleTopLevelEditSubAction(
2535 EditSubAction aTopLevelEditSubAction,
2536 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
2537 MOZ_ASSERT(IsEditActionDataAvailable());
2538 MOZ_ASSERT(!aRv.Failed());
2539 mEditActionData->SetTopLevelEditSubAction(aTopLevelEditSubAction,
2540 aDirectionOfTopLevelEditSubAction);
2543 nsresult EditorBase::OnEndHandlingTopLevelEditSubAction() {
2544 MOZ_ASSERT(IsEditActionDataAvailable());
2545 mEditActionData->SetTopLevelEditSubAction(EditSubAction::eNone, eNone);
2546 return NS_OK;
2549 void EditorBase::DoInsertText(Text& aText, uint32_t aOffset,
2550 const nsAString& aStringToInsert,
2551 ErrorResult& aRv) {
2552 aText.InsertData(aOffset, aStringToInsert, aRv);
2553 if (NS_WARN_IF(Destroyed())) {
2554 aRv = NS_ERROR_EDITOR_DESTROYED;
2555 return;
2557 if (aRv.Failed()) {
2558 NS_WARNING("Text::InsertData() failed");
2559 return;
2561 if (IsTextEditor() && !aStringToInsert.IsEmpty()) {
2562 aRv = MOZ_KnownLive(AsTextEditor())
2563 ->DidInsertText(aText.TextLength(), aOffset,
2564 aStringToInsert.Length());
2565 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2569 void EditorBase::DoDeleteText(Text& aText, uint32_t aOffset, uint32_t aCount,
2570 ErrorResult& aRv) {
2571 if (IsTextEditor() && aCount > 0) {
2572 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount);
2574 aText.DeleteData(aOffset, aCount, aRv);
2575 if (NS_WARN_IF(Destroyed())) {
2576 aRv = NS_ERROR_EDITOR_DESTROYED;
2577 return;
2579 NS_WARNING_ASSERTION(!aRv.Failed(), "Text::DeleteData() failed");
2582 void EditorBase::DoReplaceText(Text& aText, uint32_t aOffset, uint32_t aCount,
2583 const nsAString& aStringToInsert,
2584 ErrorResult& aRv) {
2585 if (IsTextEditor() && aCount > 0) {
2586 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount);
2588 aText.ReplaceData(aOffset, aCount, aStringToInsert, aRv);
2589 if (NS_WARN_IF(Destroyed())) {
2590 aRv = NS_ERROR_EDITOR_DESTROYED;
2591 return;
2593 if (aRv.Failed()) {
2594 NS_WARNING("Text::ReplaceData() failed");
2595 return;
2597 if (IsTextEditor() && !aStringToInsert.IsEmpty()) {
2598 aRv = MOZ_KnownLive(AsTextEditor())
2599 ->DidInsertText(aText.TextLength(), aOffset,
2600 aStringToInsert.Length());
2601 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2605 void EditorBase::DoSetText(Text& aText, const nsAString& aStringToSet,
2606 ErrorResult& aRv) {
2607 if (IsTextEditor()) {
2608 uint32_t length = aText.TextLength();
2609 if (length > 0) {
2610 AsTextEditor()->WillDeleteText(length, 0, length);
2613 aText.SetData(aStringToSet, aRv);
2614 if (NS_WARN_IF(Destroyed())) {
2615 aRv = NS_ERROR_EDITOR_DESTROYED;
2616 return;
2618 if (aRv.Failed()) {
2619 NS_WARNING("Text::SetData() failed");
2620 return;
2622 if (IsTextEditor() && !aStringToSet.IsEmpty()) {
2623 aRv = MOZ_KnownLive(AsTextEditor())
2624 ->DidInsertText(aText.Length(), 0, aStringToSet.Length());
2625 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed");
2629 nsresult EditorBase::CloneAttributeWithTransaction(nsAtom& aAttribute,
2630 Element& aDestElement,
2631 Element& aSourceElement) {
2632 nsAutoString attrValue;
2633 if (aSourceElement.GetAttr(kNameSpaceID_None, &aAttribute, attrValue)) {
2634 nsresult rv =
2635 SetAttributeWithTransaction(aDestElement, aAttribute, attrValue);
2636 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2637 "EditorBase::SetAttributeWithTransaction() failed");
2638 return rv;
2640 nsresult rv = RemoveAttributeWithTransaction(aDestElement, aAttribute);
2641 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2642 "EditorBase::RemoveAttributeWithTransaction() failed");
2643 return rv;
2646 NS_IMETHODIMP EditorBase::CloneAttributes(Element* aDestElement,
2647 Element* aSourceElement) {
2648 if (NS_WARN_IF(!aDestElement) || NS_WARN_IF(!aSourceElement)) {
2649 return NS_ERROR_INVALID_ARG;
2652 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
2653 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2654 if (NS_FAILED(rv)) {
2655 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2656 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
2657 return EditorBase::ToGenericNSResult(rv);
2660 CloneAttributesWithTransaction(*aDestElement, *aSourceElement);
2662 return NS_OK;
2665 void EditorBase::CloneAttributesWithTransaction(Element& aDestElement,
2666 Element& aSourceElement) {
2667 AutoPlaceholderBatch treatAsOneTransaction(
2668 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2670 // Use transaction system for undo only if destination is already in the
2671 // document
2672 Element* rootElement = GetRoot();
2673 if (NS_WARN_IF(!rootElement)) {
2674 return;
2677 OwningNonNull<Element> destElement(aDestElement);
2678 OwningNonNull<Element> sourceElement(aSourceElement);
2679 bool isDestElementInBody = rootElement->Contains(destElement);
2681 // Clear existing attributes
2682 RefPtr<nsDOMAttributeMap> destAttributes = destElement->Attributes();
2683 while (RefPtr<Attr> attr = destAttributes->Item(0)) {
2684 if (isDestElementInBody) {
2685 DebugOnly<nsresult> rvIgnored = RemoveAttributeWithTransaction(
2686 destElement, MOZ_KnownLive(*attr->NodeInfo()->NameAtom()));
2687 NS_WARNING_ASSERTION(
2688 NS_SUCCEEDED(rvIgnored),
2689 "EditorBase::RemoveAttributeWithTransaction() failed, but ignored");
2690 } else {
2691 DebugOnly<nsresult> rvIgnored = destElement->UnsetAttr(
2692 kNameSpaceID_None, attr->NodeInfo()->NameAtom(), true);
2693 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2694 "Element::UnsetAttr() failed, but ignored");
2698 // Set just the attributes that the source element has
2699 RefPtr<nsDOMAttributeMap> sourceAttributes = sourceElement->Attributes();
2700 uint32_t sourceCount = sourceAttributes->Length();
2701 for (uint32_t i = 0; i < sourceCount; i++) {
2702 RefPtr<Attr> attr = sourceAttributes->Item(i);
2703 nsAutoString value;
2704 attr->GetValue(value);
2705 if (isDestElementInBody) {
2706 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
2707 destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
2708 false);
2709 NS_WARNING_ASSERTION(
2710 NS_SUCCEEDED(rvIgnored),
2711 "EditorBase::SetAttributeOrEquivalent() failed, but ignored");
2712 } else {
2713 // The element is not inserted in the document yet, we don't want to put
2714 // a transaction on the UndoStack
2715 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent(
2716 destElement, MOZ_KnownLive(attr->NodeInfo()->NameAtom()), value,
2717 true);
2718 NS_WARNING_ASSERTION(
2719 NS_SUCCEEDED(rvIgnored),
2720 "EditorBase::SetAttributeOrEquivalent() failed, but ignored");
2725 nsresult EditorBase::ScrollSelectionFocusIntoView() const {
2726 nsISelectionController* selectionController = GetSelectionController();
2727 if (!selectionController) {
2728 return NS_OK;
2731 DebugOnly<nsresult> rvIgnored = selectionController->ScrollSelectionIntoView(
2732 nsISelectionController::SELECTION_NORMAL,
2733 nsISelectionController::SELECTION_FOCUS_REGION,
2734 nsISelectionController::SCROLL_OVERFLOW_HIDDEN);
2735 NS_WARNING_ASSERTION(
2736 NS_SUCCEEDED(rvIgnored),
2737 "nsISelectionController::ScrollSelectionIntoView() failed, but ignored");
2738 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
2741 template <typename EditorDOMPointType>
2742 EditorDOMPointType EditorBase::FindBetterInsertionPoint(
2743 const EditorDOMPointType& aPoint) const {
2744 if (MOZ_UNLIKELY(NS_WARN_IF(!aPoint.IsInContentNode()))) {
2745 return aPoint;
2748 MOZ_ASSERT(aPoint.IsSetAndValid());
2750 if (aPoint.IsInTextNode()) {
2751 // There is no "better" insertion point.
2752 return aPoint;
2755 if (!IsInPlaintextMode()) {
2756 // We cannot find "better" insertion point in HTML editor.
2757 // WARNING: When you add some code to find better node in HTML editor,
2758 // you need to call this before calling InsertTextWithTransaction()
2759 // in HTMLEditor.
2760 return aPoint;
2763 RefPtr<Element> rootElement = GetRoot();
2764 if (aPoint.GetContainer() == rootElement) {
2765 // In some cases, aNode is the anonymous DIV, and offset is 0. To avoid
2766 // injecting unneeded text nodes, we first look to see if we have one
2767 // available. In that case, we'll just adjust node and offset accordingly.
2768 if (aPoint.IsStartOfContainer() && aPoint.GetContainer()->HasChildren() &&
2769 aPoint.GetContainer()->GetFirstChild()->IsText()) {
2770 return EditorDOMPointType(aPoint.GetContainer()->GetFirstChild(), 0u);
2773 // In some other cases, aNode is the anonymous DIV, and offset points to
2774 // the terminating padding <br> element for empty last line. In that case,
2775 // we'll adjust aInOutNode and aInOutOffset to the preceding text node,
2776 // if any.
2777 if (!aPoint.IsStartOfContainer()) {
2778 if (IsHTMLEditor()) {
2779 // Fall back to a slow path that uses GetChildAt_Deprecated() for
2780 // Thunderbird's plaintext editor.
2781 nsIContent* child = aPoint.GetPreviousSiblingOfChild();
2782 if (child && child->IsText()) {
2783 return EditorDOMPointType::AtEndOf(*child);
2785 } else {
2786 // If we're in a real plaintext editor, use a fast path that avoids
2787 // calling GetChildAt_Deprecated() which may perform a linear search.
2788 nsIContent* child = aPoint.GetContainer()->GetLastChild();
2789 while (child) {
2790 if (child->IsText()) {
2791 return EditorDOMPointType::AtEndOf(*child);
2793 child = child->GetPreviousSibling();
2799 // Sometimes, aNode is the padding <br> element itself. In that case, we'll
2800 // adjust the insertion point to the previous text node, if one exists, or
2801 // to the parent anonymous DIV.
2802 if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
2803 *aPoint.ContainerAsContent()) &&
2804 aPoint.IsStartOfContainer()) {
2805 nsIContent* previousSibling = aPoint.GetContainer()->GetPreviousSibling();
2806 if (previousSibling && previousSibling->IsText()) {
2807 return EditorDOMPointType::AtEndOf(*previousSibling);
2810 nsINode* parentOfContainer = aPoint.GetContainerParent();
2811 if (parentOfContainer && parentOfContainer == rootElement) {
2812 return EditorDOMPointType(parentOfContainer, aPoint.ContainerAsContent(),
2813 0u);
2817 return aPoint;
2820 Result<EditorDOMPoint, nsresult> EditorBase::InsertTextWithTransaction(
2821 Document& aDocument, const nsAString& aStringToInsert,
2822 const EditorDOMPoint& aPointToInsert) {
2823 MOZ_ASSERT(
2824 ShouldHandleIMEComposition() || !AllowsTransactionsToChangeSelection(),
2825 "caller must have already used AutoTransactionsConserveSelection "
2826 "if this is not for updating composition string");
2828 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
2829 return Err(NS_ERROR_INVALID_ARG);
2832 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
2834 if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
2835 return aPointToInsert;
2838 // In some cases, the node may be the anonymous div element or a padding
2839 // <br> element for empty last line. Let's try to look for better insertion
2840 // point in the nearest text node if there is.
2841 EditorDOMPoint pointToInsert = FindBetterInsertionPoint(aPointToInsert);
2843 // If a neighboring text node already exists, use that
2844 if (!pointToInsert.IsInTextNode()) {
2845 nsIContent* child = nullptr;
2846 if (!pointToInsert.IsStartOfContainer() &&
2847 (child = pointToInsert.GetPreviousSiblingOfChild()) &&
2848 child->IsText()) {
2849 pointToInsert.Set(child, child->Length());
2850 } else if (!pointToInsert.IsEndOfContainer() &&
2851 (child = pointToInsert.GetChild()) && child->IsText()) {
2852 pointToInsert.Set(child, 0);
2856 if (ShouldHandleIMEComposition()) {
2857 CheckedUint32 newOffset;
2858 if (!pointToInsert.IsInTextNode()) {
2859 // create a text node
2860 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns);
2861 if (NS_WARN_IF(!newTextNode)) {
2862 return Err(NS_ERROR_FAILURE);
2864 // then we insert it into the dom tree
2865 CreateTextResult insertTextNodeResult =
2866 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert);
2867 if (insertTextNodeResult.isErr()) {
2868 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
2869 return Err(insertTextNodeResult.unwrapErr());
2871 nsresult rv = insertTextNodeResult.SuggestCaretPointTo(
2872 *this, {SuggestCaret::OnlyIfHasSuggestion,
2873 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2874 SuggestCaret::AndIgnoreTrivialError});
2875 if (NS_FAILED(rv)) {
2876 NS_WARNING("CreateTextResult::SuggestCaretPointTo() failed");
2877 return Err(rv);
2879 NS_WARNING_ASSERTION(
2880 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2881 "CreateTextResult::SuggestCaretPointTo() failed, but ignored");
2882 pointToInsert.Set(newTextNode, 0u);
2883 newOffset = aStringToInsert.Length();
2884 } else {
2885 newOffset = aStringToInsert.Length();
2886 newOffset += pointToInsert.Offset();
2887 if (NS_WARN_IF(!newOffset.isValid())) {
2888 return Err(NS_ERROR_FAILURE);
2891 nsresult rv = InsertTextIntoTextNodeWithTransaction(
2892 aStringToInsert, pointToInsert.AsInText());
2893 if (MOZ_UNLIKELY(Destroyed())) {
2894 NS_WARNING(
2895 "EditorBase::InsertTextIntoTextNodeWithTransaction() caused "
2896 "destroying the editor");
2897 return Err(NS_ERROR_EDITOR_DESTROYED);
2899 if (NS_FAILED(rv)) {
2900 NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
2901 return Err(rv);
2903 return EditorDOMPoint(pointToInsert.GetContainer(), newOffset.value());
2906 if (pointToInsert.IsInTextNode()) {
2907 CheckedUint32 newOffset = aStringToInsert.Length();
2908 newOffset += pointToInsert.Offset();
2909 if (NS_WARN_IF(!newOffset.isValid())) {
2910 return Err(NS_ERROR_FAILURE);
2912 // we are inserting text into an existing text node.
2913 nsresult rv = InsertTextIntoTextNodeWithTransaction(
2914 aStringToInsert, EditorDOMPointInText(pointToInsert.ContainerAsText(),
2915 pointToInsert.Offset()));
2916 if (MOZ_UNLIKELY(Destroyed())) {
2917 NS_WARNING(
2918 "EditorBase::InsertTextIntoTextNodeWithTransaction() caused "
2919 "destroying the editor");
2920 return Err(NS_ERROR_EDITOR_DESTROYED);
2922 if (NS_FAILED(rv)) {
2923 NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
2924 return Err(rv);
2926 return EditorDOMPoint(pointToInsert.GetContainer(), newOffset.value());
2929 // we are inserting text into a non-text node. first we have to create a
2930 // textnode (this also populates it with the text)
2931 RefPtr<nsTextNode> newTextNode = CreateTextNode(aStringToInsert);
2932 if (NS_WARN_IF(!newTextNode)) {
2933 return Err(NS_ERROR_FAILURE);
2935 // then we insert it into the dom tree
2936 CreateTextResult insertTextNodeResult =
2937 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert);
2938 if (insertTextNodeResult.isErr()) {
2939 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
2940 return Err(insertTextNodeResult.unwrapErr());
2942 nsresult rv = insertTextNodeResult.SuggestCaretPointTo(
2943 *this, {SuggestCaret::OnlyIfHasSuggestion,
2944 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
2945 SuggestCaret::AndIgnoreTrivialError});
2946 if (NS_FAILED(rv)) {
2947 NS_WARNING("CreateTextResult::SuggestCaretPointTo() failed");
2948 return Err(rv);
2950 NS_WARNING_ASSERTION(
2951 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
2952 "CreateTextResult::SuggestCaretPointTo() failed, but ignored");
2953 return EditorDOMPoint(insertTextNodeResult.UnwrapNewNode(),
2954 aStringToInsert.Length());
2957 static bool TextFragmentBeginsWithStringAtOffset(
2958 const nsTextFragment& aTextFragment, const uint32_t aOffset,
2959 const nsAString& aString) {
2960 const uint32_t stringLength = aString.Length();
2962 if (aOffset + stringLength > aTextFragment.GetLength()) {
2963 return false;
2966 if (aTextFragment.Is2b()) {
2967 return aString.Equals(aTextFragment.Get2b() + aOffset);
2970 return aString.EqualsLatin1(aTextFragment.Get1b() + aOffset, stringLength);
2973 static std::tuple<EditorDOMPointInText, EditorDOMPointInText>
2974 AdjustTextInsertionRange(const EditorDOMPointInText& aInsertedPoint,
2975 const nsAString& aInsertedString) {
2976 if (TextFragmentBeginsWithStringAtOffset(
2977 aInsertedPoint.ContainerAsText()->TextFragment(),
2978 aInsertedPoint.Offset(), aInsertedString)) {
2979 return {aInsertedPoint,
2980 EditorDOMPointInText(
2981 aInsertedPoint.ContainerAsText(),
2982 aInsertedPoint.Offset() + aInsertedString.Length())};
2985 return {EditorDOMPointInText(aInsertedPoint.ContainerAsText(), 0),
2986 EditorDOMPointInText::AtEndOf(*aInsertedPoint.ContainerAsText())};
2989 std::tuple<EditorDOMPointInText, EditorDOMPointInText>
2990 EditorBase::ComputeInsertedRange(const EditorDOMPointInText& aInsertedPoint,
2991 const nsAString& aInsertedString) const {
2992 MOZ_ASSERT(aInsertedPoint.IsSet());
2994 // The DOM was potentially modified during the transaction. This is possible
2995 // through mutation event listeners. That is, the node could've been removed
2996 // from the doc or otherwise modified.
2997 if (!MayHaveMutationEventListeners(
2998 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED)) {
2999 EditorDOMPointInText endOfInsertion(
3000 aInsertedPoint.ContainerAsText(),
3001 aInsertedPoint.Offset() + aInsertedString.Length());
3002 return {aInsertedPoint, endOfInsertion};
3004 if (aInsertedPoint.ContainerAsText()->IsInComposedDoc()) {
3005 EditorDOMPointInText begin, end;
3006 return AdjustTextInsertionRange(aInsertedPoint, aInsertedString);
3008 return {EditorDOMPointInText(), EditorDOMPointInText()};
3011 nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
3012 const nsAString& aStringToInsert,
3013 const EditorDOMPointInText& aPointToInsert, bool aSuppressIME) {
3014 MOZ_ASSERT(IsEditActionDataAvailable());
3015 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
3017 EditorDOMPointInText pointToInsert(aPointToInsert);
3018 RefPtr<EditTransactionBase> transaction;
3019 bool isIMETransaction = false;
3020 // aSuppressIME is used when editor must insert text, yet this text is not
3021 // part of the current IME operation. Example: adjusting white-space around an
3022 // IME insertion.
3023 if (ShouldHandleIMEComposition() && !aSuppressIME) {
3024 transaction =
3025 CompositionTransaction::Create(*this, aStringToInsert, pointToInsert);
3026 isIMETransaction = true;
3027 // All characters of the composition string will be replaced with
3028 // aStringToInsert. So, we need to emulate to remove the composition
3029 // string.
3030 // FYI: The text node information in mComposition has been updated by
3031 // CompositionTransaction::Create().
3032 pointToInsert.Set(mComposition->GetContainerTextNode(),
3033 mComposition->XPOffsetInTextNode());
3034 } else {
3035 transaction =
3036 InsertTextTransaction::Create(*this, aStringToInsert, pointToInsert);
3039 // XXX We may not need these view batches anymore. This is handled at a
3040 // higher level now I believe.
3041 BeginUpdateViewBatch(__FUNCTION__);
3042 nsresult rv = DoTransactionInternal(transaction);
3043 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3044 "EditorBase::DoTransactionInternal() failed");
3045 EndUpdateViewBatch(__FUNCTION__);
3047 if (IsHTMLEditor() && pointToInsert.IsSet()) {
3048 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
3049 if (begin.IsSet() && end.IsSet()) {
3050 TopLevelEditSubActionDataRef().DidInsertText(
3051 *this, begin.To<EditorRawDOMPoint>(), end.To<EditorRawDOMPoint>());
3053 if (isIMETransaction) {
3054 // Let's mark the text node as "modified frequently" if it interact with
3055 // IME since non-ASCII character may be inserted into it in most cases.
3056 aPointToInsert.ContainerAsText()->MarkAsMaybeModifiedFrequently();
3060 // let listeners know what happened
3061 if (!mActionListeners.IsEmpty()) {
3062 for (auto& listener : mActionListeners.Clone()) {
3063 // TODO: might need adaptation because of mutation event listeners called
3064 // during `DoTransactionInternal`.
3065 DebugOnly<nsresult> rvIgnored =
3066 listener->DidInsertText(pointToInsert.ContainerAsText(),
3067 pointToInsert.Offset(), aStringToInsert, rv);
3068 NS_WARNING_ASSERTION(
3069 NS_SUCCEEDED(rvIgnored),
3070 "nsIEditActionListener::DidInsertText() failed, but ignored");
3074 // Added some cruft here for bug 43366. Layout was crashing because we left
3075 // an empty text node lying around in the document. So I delete empty text
3076 // nodes caused by IME. I have to mark the IME transaction as "fixed", which
3077 // means that furure IME txns won't merge with it. This is because we don't
3078 // want future IME txns trying to put their text into a node that is no
3079 // longer in the document. This does not break undo/redo, because all these
3080 // txns are wrapped in a parent PlaceHolder txn, and placeholder txns are
3081 // already savvy to having multiple ime txns inside them.
3083 // Delete empty IME text node if there is one
3084 if (IsHTMLEditor() && isIMETransaction && mComposition) {
3085 RefPtr<Text> textNode = mComposition->GetContainerTextNode();
3086 if (textNode && !textNode->Length()) {
3087 nsresult rv = DeleteNodeWithTransaction(*textNode);
3088 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
3089 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3090 return rv;
3092 NS_WARNING_ASSERTION(
3093 NS_SUCCEEDED(rv),
3094 "EditorBase::DeleteNodeWithTransaction() failed, but ignored");
3095 mComposition->OnTextNodeRemoved();
3096 static_cast<CompositionTransaction*>(transaction.get())->MarkFixed();
3100 return rv;
3103 nsresult EditorBase::NotifyDocumentListeners(
3104 TDocumentListenerNotification aNotificationType) {
3105 switch (aNotificationType) {
3106 case eDocumentCreated:
3107 if (IsTextEditor()) {
3108 return NS_OK;
3110 if (RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3111 AsHTMLEditor()->mComposerCommandsUpdater) {
3112 composerCommandsUpdate->OnHTMLEditorCreated();
3114 return NS_OK;
3116 case eDocumentToBeDestroyed: {
3117 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3118 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr;
3119 if (!mDocStateListeners.Length() && !composerCommandsUpdate) {
3120 return NS_OK;
3122 // Needs to store all listeners before notifying ComposerCommandsUpdate
3123 // since notifying it might change mDocStateListeners.
3124 const AutoDocumentStateListenerArray listeners(
3125 mDocStateListeners.Clone());
3126 if (composerCommandsUpdate) {
3127 composerCommandsUpdate->OnBeforeHTMLEditorDestroyed();
3129 for (auto& listener : listeners) {
3130 // MOZ_KnownLive because 'listeners' is guaranteed to
3131 // keep it alive.
3133 // This can go away once
3134 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3135 nsresult rv = MOZ_KnownLive(listener)->NotifyDocumentWillBeDestroyed();
3136 if (NS_FAILED(rv)) {
3137 NS_WARNING(
3138 "nsIDocumentStateListener::NotifyDocumentWillBeDestroyed() "
3139 "failed");
3140 return rv;
3143 return NS_OK;
3145 case eDocumentStateChanged: {
3146 bool docIsDirty;
3147 nsresult rv = GetDocumentModified(&docIsDirty);
3148 if (NS_FAILED(rv)) {
3149 NS_WARNING("EditorBase::GetDocumentModified() failed");
3150 return rv;
3153 if (static_cast<int8_t>(docIsDirty) == mDocDirtyState) {
3154 return NS_OK;
3157 mDocDirtyState = docIsDirty;
3159 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate =
3160 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr;
3161 if (!mDocStateListeners.Length() && !composerCommandsUpdate) {
3162 return NS_OK;
3164 // Needs to store all listeners before notifying ComposerCommandsUpdate
3165 // since notifying it might change mDocStateListeners.
3166 const AutoDocumentStateListenerArray listeners(
3167 mDocStateListeners.Clone());
3168 if (composerCommandsUpdate) {
3169 composerCommandsUpdate->OnHTMLEditorDirtyStateChanged(mDocDirtyState);
3171 for (auto& listener : listeners) {
3172 // MOZ_KnownLive because 'listeners' is guaranteed to
3173 // keep it alive.
3175 // This can go away once
3176 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed.
3177 nsresult rv =
3178 MOZ_KnownLive(listener)->NotifyDocumentStateChanged(mDocDirtyState);
3179 if (NS_FAILED(rv)) {
3180 NS_WARNING(
3181 "nsIDocumentStateListener::NotifyDocumentStateChanged() failed");
3182 return rv;
3185 return NS_OK;
3187 default:
3188 MOZ_ASSERT_UNREACHABLE("Unknown notification");
3189 return NS_ERROR_FAILURE;
3193 nsresult EditorBase::SetTextNodeWithoutTransaction(const nsAString& aString,
3194 Text& aTextNode) {
3195 MOZ_ASSERT(IsEditActionDataAvailable());
3196 MOZ_ASSERT(IsTextEditor());
3197 MOZ_ASSERT(!IsUndoRedoEnabled());
3199 const uint32_t length = aTextNode.Length();
3201 // Let listeners know what's up
3202 if (!mActionListeners.IsEmpty() && length) {
3203 for (auto& listener : mActionListeners.Clone()) {
3204 DebugOnly<nsresult> rvIgnored =
3205 listener->WillDeleteText(MOZ_KnownLive(&aTextNode), 0, length);
3206 if (NS_WARN_IF(Destroyed())) {
3207 NS_WARNING(
3208 "nsIEditActionListener::WillDeleteText() failed, but ignored");
3209 return NS_ERROR_EDITOR_DESTROYED;
3214 // We don't support undo here, so we don't really need all of the transaction
3215 // machinery, therefore we can run our transaction directly, breaking all of
3216 // the rules!
3217 IgnoredErrorResult error;
3218 DoSetText(aTextNode, aString, error);
3219 if (MOZ_UNLIKELY(error.Failed())) {
3220 NS_WARNING("EditorBase::DoSetText() failed");
3221 return error.StealNSResult();
3224 CollapseSelectionTo(EditorRawDOMPoint(&aTextNode, aString.Length()), error);
3225 if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3226 NS_WARNING("EditorBase::CollapseSelection() caused destroying the editor");
3227 return NS_ERROR_EDITOR_DESTROYED;
3229 NS_ASSERTION(!error.Failed(),
3230 "EditorBase::CollapseSelectionTo() failed, but ignored");
3232 RangeUpdaterRef().SelAdjReplaceText(aTextNode, 0, length, aString.Length());
3234 // Let listeners know what happened
3235 if (!mActionListeners.IsEmpty() && !aString.IsEmpty()) {
3236 for (auto& listener : mActionListeners.Clone()) {
3237 DebugOnly<nsresult> rvIgnored =
3238 listener->DidInsertText(&aTextNode, 0, aString, NS_OK);
3239 if (NS_WARN_IF(Destroyed())) {
3240 return NS_ERROR_EDITOR_DESTROYED;
3242 NS_WARNING_ASSERTION(
3243 NS_SUCCEEDED(rvIgnored),
3244 "nsIEditActionListener::DidInsertText() failed, but ignored");
3248 return NS_OK;
3251 nsresult EditorBase::DeleteTextWithTransaction(Text& aTextNode,
3252 uint32_t aOffset,
3253 uint32_t aLength) {
3254 MOZ_ASSERT(IsEditActionDataAvailable());
3256 RefPtr<DeleteTextTransaction> transaction =
3257 DeleteTextTransaction::MaybeCreate(*this, aTextNode, aOffset, aLength);
3258 if (!transaction) {
3259 NS_WARNING("DeleteTextTransaction::MaybeCreate() failed");
3260 return NS_ERROR_FAILURE;
3263 IgnoredErrorResult ignoredError;
3264 AutoEditSubActionNotifier startToHandleEditSubAction(
3265 *this, EditSubAction::eDeleteText, nsIEditor::ePrevious, ignoredError);
3266 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3267 return ignoredError.StealNSResult();
3269 NS_WARNING_ASSERTION(
3270 !ignoredError.Failed(),
3271 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3273 // Let listeners know what's up
3274 if (!mActionListeners.IsEmpty()) {
3275 for (auto& listener : mActionListeners.Clone()) {
3276 DebugOnly<nsresult> rvIgnored =
3277 listener->WillDeleteText(&aTextNode, aOffset, aLength);
3278 NS_WARNING_ASSERTION(
3279 NS_SUCCEEDED(rvIgnored),
3280 "nsIEditActionListener::WillDeleteText() failed, but ignored");
3284 nsresult rv = DoTransactionInternal(transaction);
3285 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3286 "EditorBase::DoTransactionInternal() failed");
3288 if (IsHTMLEditor()) {
3289 TopLevelEditSubActionDataRef().DidDeleteText(
3290 *this, EditorRawDOMPoint(&aTextNode, aOffset));
3293 return rv;
3296 bool EditorBase::IsRoot(const nsINode* inNode) const {
3297 if (NS_WARN_IF(!inNode)) {
3298 return false;
3300 nsINode* rootNode = GetRoot();
3301 return inNode == rootNode;
3304 bool EditorBase::IsDescendantOfRoot(const nsINode* inNode) const {
3305 if (NS_WARN_IF(!inNode)) {
3306 return false;
3308 nsIContent* root = GetRoot();
3309 if (NS_WARN_IF(!root)) {
3310 return false;
3313 return inNode->IsInclusiveDescendantOf(root);
3316 NS_IMETHODIMP EditorBase::IncrementModificationCount(int32_t inNumMods) {
3317 uint32_t oldModCount = mModCount;
3319 mModCount += inNumMods;
3321 if ((!oldModCount && mModCount) || (oldModCount && !mModCount)) {
3322 DebugOnly<nsresult> rvIgnored =
3323 NotifyDocumentListeners(eDocumentStateChanged);
3324 NS_WARNING_ASSERTION(
3325 NS_SUCCEEDED(rvIgnored),
3326 "EditorBase::NotifyDocumentListeners() failed, but ignored");
3328 return NS_OK;
3331 NS_IMETHODIMP EditorBase::GetModificationCount(int32_t* aOutModCount) {
3332 if (NS_WARN_IF(!aOutModCount)) {
3333 return NS_ERROR_INVALID_ARG;
3335 *aOutModCount = mModCount;
3336 return NS_OK;
3339 NS_IMETHODIMP EditorBase::ResetModificationCount() {
3340 bool doNotify = (mModCount != 0);
3342 mModCount = 0;
3344 if (!doNotify) {
3345 return NS_OK;
3348 DebugOnly<nsresult> rvIgnored =
3349 NotifyDocumentListeners(eDocumentStateChanged);
3350 NS_WARNING_ASSERTION(
3351 NS_SUCCEEDED(rvIgnored),
3352 "EditorBase::NotifyDocumentListeners() failed, but ignored");
3353 return NS_OK;
3356 template <typename EditorDOMPointType>
3357 EditorDOMPointType EditorBase::GetFirstSelectionStartPoint() const {
3358 MOZ_ASSERT(IsEditActionDataAvailable());
3359 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
3360 return EditorDOMPointType();
3363 const nsRange* range = SelectionRef().GetRangeAt(0);
3364 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) {
3365 return EditorDOMPointType();
3368 return EditorDOMPointType(range->StartRef());
3371 template <typename EditorDOMPointType>
3372 EditorDOMPointType EditorBase::GetFirstSelectionEndPoint() const {
3373 MOZ_ASSERT(IsEditActionDataAvailable());
3374 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) {
3375 return EditorDOMPointType();
3378 const nsRange* range = SelectionRef().GetRangeAt(0);
3379 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) {
3380 return EditorDOMPointType();
3383 return EditorDOMPointType(range->EndRef());
3386 // static
3387 nsresult EditorBase::GetEndChildNode(const Selection& aSelection,
3388 nsIContent** aEndNode) {
3389 MOZ_ASSERT(aEndNode);
3391 *aEndNode = nullptr;
3393 if (NS_WARN_IF(!aSelection.RangeCount())) {
3394 return NS_ERROR_FAILURE;
3397 const nsRange* range = aSelection.GetRangeAt(0);
3398 if (NS_WARN_IF(!range)) {
3399 return NS_ERROR_FAILURE;
3402 if (NS_WARN_IF(!range->IsPositioned())) {
3403 return NS_ERROR_FAILURE;
3406 NS_IF_ADDREF(*aEndNode = range->GetChildAtEndOffset());
3407 return NS_OK;
3410 nsresult EditorBase::EnsurePaddingBRElementInMultilineEditor() {
3411 MOZ_ASSERT(IsEditActionDataAvailable());
3412 MOZ_ASSERT(IsInPlaintextMode());
3413 MOZ_ASSERT(!IsSingleLineEditor());
3415 Element* anonymousDivOrBodyElement = GetRoot();
3416 if (NS_WARN_IF(!anonymousDivOrBodyElement)) {
3417 return NS_ERROR_FAILURE;
3420 // Assuming EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() has been
3421 // called first.
3422 // XXX This assumption is wrong. This method may be called alone. Actually,
3423 // we see this warning in mochitest log. So, we should fix this bug
3424 // later.
3425 if (NS_WARN_IF(!anonymousDivOrBodyElement->GetLastChild())) {
3426 return NS_ERROR_FAILURE;
3429 RefPtr<HTMLBRElement> brElement =
3430 HTMLBRElement::FromNode(anonymousDivOrBodyElement->GetLastChild());
3431 if (!brElement) {
3432 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
3433 // in normal cases. However, it may be required for nested edit
3434 // actions which may be caused by legacy mutation event listeners or
3435 // chrome script.
3436 AutoTransactionsConserveSelection dontChangeMySelection(*this);
3437 EditorDOMPoint endOfAnonymousDiv(
3438 EditorDOMPoint::AtEndOf(*anonymousDivOrBodyElement));
3439 CreateElementResult insertPaddingBRElementResult =
3440 InsertPaddingBRElementForEmptyLastLineWithTransaction(
3441 endOfAnonymousDiv);
3442 if (insertPaddingBRElementResult.isErr()) {
3443 NS_WARNING(
3444 "EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
3445 "failed");
3446 return insertPaddingBRElementResult.unwrapErr();
3448 insertPaddingBRElementResult.IgnoreCaretPointSuggestion();
3449 return NS_OK;
3452 // Check to see if the trailing BR is a former padding <br> element for empty
3453 // editor - this will have stuck around if we previously morphed a trailing
3454 // node into a padding <br> element.
3455 if (!brElement->IsPaddingForEmptyEditor()) {
3456 return NS_OK;
3459 // Morph it back to a padding <br> element for empty last line.
3460 brElement->UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR);
3461 brElement->SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE);
3463 return NS_OK;
3466 void EditorBase::BeginUpdateViewBatch(const char* aRequesterFuncName) {
3467 MOZ_ASSERT(IsEditActionDataAvailable());
3468 MOZ_ASSERT(mUpdateCount >= 0, "bad state");
3470 if (!mUpdateCount) {
3471 // Turn off selection updates and notifications.
3472 SelectionRef().StartBatchChanges(aRequesterFuncName);
3475 mUpdateCount++;
3478 void EditorBase::EndUpdateViewBatch(const char* aRequesterFuncName) {
3479 MOZ_ASSERT(IsEditActionDataAvailable());
3480 MOZ_ASSERT(mUpdateCount > 0, "bad state");
3482 if (NS_WARN_IF(mUpdateCount <= 0)) {
3483 mUpdateCount = 0;
3484 return;
3487 if (--mUpdateCount) {
3488 return;
3491 // Turn selection updating and notifications back on.
3492 SelectionRef().EndBatchChanges(aRequesterFuncName);
3495 TextComposition* EditorBase::GetComposition() const { return mComposition; }
3497 template <typename EditorDOMPointType>
3498 EditorDOMPointType EditorBase::GetFirstIMESelectionStartPoint() const {
3499 return mComposition
3500 ? EditorDOMPointType(mComposition->FirstIMESelectionStartRef())
3501 : EditorDOMPointType();
3504 template <typename EditorDOMPointType>
3505 EditorDOMPointType EditorBase::GetLastIMESelectionEndPoint() const {
3506 return mComposition
3507 ? EditorDOMPointType(mComposition->LastIMESelectionEndRef())
3508 : EditorDOMPointType();
3511 bool EditorBase::IsIMEComposing() const {
3512 return mComposition && mComposition->IsComposing();
3515 bool EditorBase::ShouldHandleIMEComposition() const {
3516 // When the editor is being reframed, the old value may be restored with
3517 // InsertText(). In this time, the text should be inserted as not a part
3518 // of the composition.
3519 return mComposition && mDidPostCreate;
3522 bool EditorBase::EnsureComposition(WidgetCompositionEvent& aCompositionEvent) {
3523 if (mComposition) {
3524 return true;
3526 // The compositionstart event must cause creating new TextComposition
3527 // instance at being dispatched by IMEStateManager.
3528 mComposition = IMEStateManager::GetTextCompositionFor(&aCompositionEvent);
3529 if (!mComposition) {
3530 // However, TextComposition may be committed before the composition
3531 // event comes here.
3532 return false;
3534 mComposition->StartHandlingComposition(this);
3535 return true;
3538 nsresult EditorBase::OnCompositionStart(
3539 WidgetCompositionEvent& aCompositionStartEvent) {
3540 if (mComposition) {
3541 NS_WARNING("There was a composition at receiving compositionstart event");
3542 return NS_OK;
3545 // "beforeinput" event shouldn't be fired before "compositionstart".
3546 AutoEditActionDataSetter editActionData(*this, EditAction::eStartComposition);
3547 if (NS_WARN_IF(!editActionData.CanHandle())) {
3548 return NS_ERROR_NOT_INITIALIZED;
3551 EnsureComposition(aCompositionStartEvent);
3552 NS_WARNING_ASSERTION(mComposition, "Failed to get TextComposition instance?");
3553 return NS_OK;
3556 nsresult EditorBase::OnCompositionChange(
3557 WidgetCompositionEvent& aCompositionChangeEvent) {
3558 MOZ_ASSERT(aCompositionChangeEvent.mMessage == eCompositionChange,
3559 "The event should be eCompositionChange");
3561 if (!mComposition) {
3562 NS_WARNING(
3563 "There is no composition, but receiving compositionchange event");
3564 return NS_ERROR_FAILURE;
3567 AutoEditActionDataSetter editActionData(*this,
3568 EditAction::eUpdateComposition);
3569 if (NS_WARN_IF(!editActionData.CanHandle())) {
3570 return NS_ERROR_NOT_INITIALIZED;
3573 // If:
3574 // - new composition string is not empty,
3575 // - there is no composition string in the DOM tree,
3576 // - and there is non-collapsed Selection,
3577 // the selected content will be removed by this composition.
3578 if (aCompositionChangeEvent.mData.IsEmpty() &&
3579 mComposition->String().IsEmpty() && !SelectionRef().IsCollapsed()) {
3580 editActionData.UpdateEditAction(EditAction::eDeleteByComposition);
3583 // If Input Events Level 2 is enabled, EditAction::eDeleteByComposition is
3584 // mapped to EditorInputType::eDeleteByComposition and it requires null
3585 // for InputEvent.data. Therefore, only otherwise, we should set data.
3586 if (ToInputType(editActionData.GetEditAction()) !=
3587 EditorInputType::eDeleteByComposition) {
3588 MOZ_ASSERT(ToInputType(editActionData.GetEditAction()) ==
3589 EditorInputType::eInsertCompositionText);
3590 MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid());
3591 editActionData.SetData(aCompositionChangeEvent.mData);
3594 // If we're an `HTMLEditor` and this is second or later composition change,
3595 // we should set target range to the range of composition string.
3596 // Otherwise, set target ranges to selection ranges (will be done by
3597 // editActionData itself before dispatching `beforeinput` event).
3598 if (IsHTMLEditor() && mComposition->GetContainerTextNode()) {
3599 RefPtr<StaticRange> targetRange = StaticRange::Create(
3600 mComposition->GetContainerTextNode(),
3601 mComposition->XPOffsetInTextNode(),
3602 mComposition->GetContainerTextNode(),
3603 mComposition->XPEndOffsetInTextNode(), IgnoreErrors());
3604 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
3605 "StaticRange::Create() failed");
3606 if (targetRange && targetRange->IsPositioned()) {
3607 editActionData.AppendTargetRange(*targetRange);
3611 // TODO: We need to use different EditAction value for beforeinput event
3612 // if the event is followed by "compositionend" because corresponding
3613 // "input" event will be fired from OnCompositionEnd() later with
3614 // different EditAction value.
3615 // TODO: If Input Events Level 2 is enabled, "beforeinput" event may be
3616 // actually canceled if edit action is eDeleteByComposition. In such
3617 // case, we might need to keep selected text, but insert composition
3618 // string before or after the selection. However, the spec is still
3619 // unstable. We should keep handling the composition since other
3620 // parts including widget may not be ready for such complicated
3621 // behavior.
3622 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
3623 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) {
3624 NS_WARNING("MaybeDispatchBeforeInputEvent() failed");
3625 return EditorBase::ToGenericNSResult(rv);
3628 if (!EnsureComposition(aCompositionChangeEvent)) {
3629 NS_WARNING("EditorBase::EnsureComposition() failed");
3630 return NS_OK;
3633 if (NS_WARN_IF(!GetPresShell())) {
3634 return NS_ERROR_NOT_INITIALIZED;
3637 // NOTE: TextComposition should receive selection change notification before
3638 // CompositionChangeEventHandlingMarker notifies TextComposition of the
3639 // end of handling compositionchange event because TextComposition may
3640 // need to ignore selection changes caused by composition. Therefore,
3641 // CompositionChangeEventHandlingMarker must be destroyed after a call
3642 // of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
3643 // NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
3644 // TextComposition of a selection change.
3645 MOZ_ASSERT(
3646 !mPlaceholderBatch,
3647 "UpdateIMEComposition() must be called without place holder batch");
3648 nsString data(aCompositionChangeEvent.mData);
3649 if (IsHTMLEditor()) {
3650 nsContentUtils::PlatformToDOMLineBreaks(data);
3654 // This needs to be destroyed before dispatching "input" event from
3655 // the following call of `NotifyEditorObservers`. Therefore, we need to
3656 // put this in this block rather than outside of this.
3657 const bool wasComposing = mComposition->IsComposing();
3658 TextComposition::CompositionChangeEventHandlingMarker
3659 compositionChangeEventHandlingMarker(mComposition,
3660 &aCompositionChangeEvent);
3661 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::IMETxnName,
3662 ScrollSelectionIntoView::Yes,
3663 __FUNCTION__);
3665 // XXX Why don't we get caret after the DOM mutation?
3666 RefPtr<nsCaret> caret = GetCaret();
3668 MOZ_ASSERT(
3669 mIsInEditSubAction,
3670 "AutoPlaceholderBatch should've notified the observes of before-edit");
3671 // If we're updating composition, we need to ignore normal selection
3672 // which may be updated by the web content.
3673 rv = InsertTextAsSubAction(data, wasComposing ? SelectionHandling::Ignore
3674 : SelectionHandling::Delete);
3675 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3676 "EditorBase::InsertTextAsSubAction() failed");
3678 if (caret) {
3679 caret->SetSelection(&SelectionRef());
3683 // If still composing, we should fire input event via observer.
3684 // Note that if the composition will be committed by the following
3685 // compositionend event, we don't need to notify editor observes of this
3686 // change.
3687 // NOTE: We must notify after the auto batch will be gone.
3688 if (!aCompositionChangeEvent.IsFollowedByCompositionEnd()) {
3689 // If we're a TextEditor, we'll be initialized with a new anonymous subtree,
3690 // which can be caused by reframing from a "input" event listener. At that
3691 // time, we'll move composition from current text node to the new text node
3692 // with using mComposition's data. Therefore, it's important that
3693 // mComposition already has the latest information here.
3694 MOZ_ASSERT_IF(mComposition, mComposition->String() == data);
3695 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
3698 return EditorBase::ToGenericNSResult(rv);
3701 void EditorBase::OnCompositionEnd(
3702 WidgetCompositionEvent& aCompositionEndEvent) {
3703 if (!mComposition) {
3704 NS_WARNING("There is no composition, but receiving compositionend event");
3705 return;
3708 EditAction editAction = aCompositionEndEvent.mData.IsEmpty()
3709 ? EditAction::eCancelComposition
3710 : EditAction::eCommitComposition;
3711 AutoEditActionDataSetter editActionData(*this, editAction);
3712 // If Input Events Level 2 is enabled, EditAction::eCancelComposition is
3713 // mapped to EditorInputType::eDeleteCompositionText and it requires null
3714 // for InputEvent.data. Therefore, only otherwise, we should set data.
3715 if (ToInputType(editAction) != EditorInputType::eDeleteCompositionText) {
3716 MOZ_ASSERT(
3717 ToInputType(editAction) == EditorInputType::eInsertCompositionText ||
3718 ToInputType(editAction) == EditorInputType::eInsertFromComposition);
3719 MOZ_ASSERT(!aCompositionEndEvent.mData.IsVoid());
3720 editActionData.SetData(aCompositionEndEvent.mData);
3723 // commit the IME transaction..we can get at it via the transaction mgr.
3724 // Note that this means IME won't work without an undo stack!
3725 if (mTransactionManager) {
3726 if (nsCOMPtr<nsITransaction> transaction =
3727 mTransactionManager->PeekUndoStack()) {
3728 if (RefPtr<EditTransactionBase> transactionBase =
3729 transaction->GetAsEditTransactionBase()) {
3730 if (PlaceholderTransaction* placeholderTransaction =
3731 transactionBase->GetAsPlaceholderTransaction()) {
3732 placeholderTransaction->Commit();
3738 // Note that this just marks as that we've already handled "beforeinput" for
3739 // preventing assertions in FireInputEvent(). Note that corresponding
3740 // "beforeinput" event for the following "input" event should've already
3741 // been dispatched from `OnCompositionChange()`.
3742 DebugOnly<nsresult> rvIgnored =
3743 editActionData.MaybeDispatchBeforeInputEvent();
3744 MOZ_ASSERT(rvIgnored != NS_ERROR_EDITOR_ACTION_CANCELED,
3745 "Why beforeinput event was canceled in this case?");
3746 MOZ_ASSERT(NS_SUCCEEDED(rvIgnored),
3747 "MaybeDispatchBeforeInputEvent() should just mark the instance as "
3748 "handled it");
3750 // Composition string may have hidden the caret. Therefore, we need to
3751 // cancel it here.
3752 HideCaret(false);
3754 // FYI: mComposition still keeps storing container text node of committed
3755 // string, its offset and length. However, they will be invalidated
3756 // soon since its Destroy() will be called by IMEStateManager.
3757 mComposition->EndHandlingComposition(this);
3758 mComposition = nullptr;
3760 // notify editor observers of action
3761 // FYI: With current draft, "input" event should be fired from
3762 // OnCompositionChange(), however, it requires a lot of our UI code
3763 // change and does not make sense. See spec issue:
3764 // https://github.com/w3c/uievents/issues/202
3765 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
3768 void EditorBase::DoAfterDoTransaction(nsITransaction* aTransaction) {
3769 bool isTransientTransaction;
3770 MOZ_ALWAYS_SUCCEEDS(aTransaction->GetIsTransient(&isTransientTransaction));
3772 if (!isTransientTransaction) {
3773 // we need to deal here with the case where the user saved after some
3774 // edits, then undid one or more times. Then, the undo count is -ve,
3775 // but we can't let a do take it back to zero. So we flip it up to
3776 // a +ve number.
3777 int32_t modCount;
3778 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount);
3779 NS_WARNING_ASSERTION(
3780 NS_SUCCEEDED(rvIgnored),
3781 "EditorBase::GetModificationCount() failed, but ignored");
3782 if (modCount < 0) {
3783 modCount = -modCount;
3786 // don't count transient transactions
3787 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1));
3791 void EditorBase::DoAfterUndoTransaction() {
3792 // all undoable transactions are non-transient
3793 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(-1));
3796 void EditorBase::DoAfterRedoTransaction() {
3797 // all redoable transactions are non-transient
3798 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1));
3801 already_AddRefed<EditAggregateTransaction>
3802 EditorBase::CreateTransactionForDeleteSelection(
3803 HowToHandleCollapsedRange aHowToHandleCollapsedRange,
3804 const AutoRangeArray& aRangesToDelete) {
3805 MOZ_ASSERT(IsEditActionDataAvailable());
3806 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
3808 // Check whether the selection is collapsed and we should do nothing:
3809 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
3810 aHowToHandleCollapsedRange ==
3811 HowToHandleCollapsedRange::Ignore)) {
3812 return nullptr;
3815 // allocate the out-param transaction
3816 RefPtr<EditAggregateTransaction> aggregateTransaction =
3817 EditAggregateTransaction::Create();
3818 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
3819 // Same with range as with selection; if it is collapsed and action
3820 // is eNone, do nothing.
3821 if (!range->Collapsed()) {
3822 RefPtr<DeleteRangeTransaction> deleteRangeTransaction =
3823 DeleteRangeTransaction::Create(*this, range);
3824 // XXX Oh, not checking if deleteRangeTransaction can modify the range...
3825 DebugOnly<nsresult> rvIgnored =
3826 aggregateTransaction->AppendChild(deleteRangeTransaction);
3827 NS_WARNING_ASSERTION(
3828 NS_SUCCEEDED(rvIgnored),
3829 "EditAggregationTransaction::AppendChild() failed, but ignored");
3830 continue;
3833 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::Ignore) {
3834 continue;
3837 // Let's extend the collapsed range to delete content around it.
3838 RefPtr<EditTransactionBase> deleteNodeOrTextTransaction =
3839 CreateTransactionForCollapsedRange(range, aHowToHandleCollapsedRange);
3840 // XXX When there are two or more ranges and at least one of them is
3841 // not editable, deleteNodeOrTextTransaction may be nullptr.
3842 // In such case, should we stop removing other ranges too?
3843 if (!deleteNodeOrTextTransaction) {
3844 NS_WARNING("EditorBase::CreateTransactionForCollapsedRange() failed");
3845 return nullptr;
3847 DebugOnly<nsresult> rvIgnored =
3848 aggregateTransaction->AppendChild(deleteNodeOrTextTransaction);
3849 NS_WARNING_ASSERTION(
3850 NS_SUCCEEDED(rvIgnored),
3851 "EditAggregationTransaction::AppendChild() failed, but ignored");
3854 return aggregateTransaction.forget();
3857 // XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior
3858 // are not implemented
3859 already_AddRefed<EditTransactionBase>
3860 EditorBase::CreateTransactionForCollapsedRange(
3861 const nsRange& aCollapsedRange,
3862 HowToHandleCollapsedRange aHowToHandleCollapsedRange) {
3863 MOZ_ASSERT(aCollapsedRange.Collapsed());
3864 MOZ_ASSERT(
3865 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward ||
3866 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward);
3868 EditorRawDOMPoint point(aCollapsedRange.StartRef());
3869 if (NS_WARN_IF(!point.IsSet())) {
3870 return nullptr;
3872 if (IsTextEditor()) {
3873 // There should be only one text node in the anonymous `<div>` (but may
3874 // be followed by a padding `<br>`). We should adjust the point into
3875 // the text node (or return nullptr if there is no text to delete) for
3876 // avoiding finding the text node with complicated API.
3877 if (!point.IsInTextNode()) {
3878 const Element* anonymousDiv = GetRoot();
3879 if (NS_WARN_IF(!anonymousDiv)) {
3880 return nullptr;
3882 if (!anonymousDiv->GetFirstChild() ||
3883 !anonymousDiv->GetFirstChild()->IsText()) {
3884 return nullptr; // The value is empty.
3886 if (point.GetContainer() == anonymousDiv) {
3887 if (point.IsStartOfContainer()) {
3888 point.Set(anonymousDiv->GetFirstChild(), 0);
3889 } else {
3890 point.SetToEndOf(anonymousDiv->GetFirstChild());
3892 } else {
3893 // Must be referring a padding `<br>` element or after the text node.
3894 point.SetToEndOf(anonymousDiv->GetFirstChild());
3897 MOZ_ASSERT(!point.ContainerAsText()->GetPreviousSibling());
3898 MOZ_ASSERT(!point.ContainerAsText()->GetNextSibling() ||
3899 !point.ContainerAsText()->GetNextSibling()->IsText());
3900 if (aHowToHandleCollapsedRange ==
3901 HowToHandleCollapsedRange::ExtendBackward &&
3902 point.IsStartOfContainer()) {
3903 return nullptr;
3905 if (aHowToHandleCollapsedRange ==
3906 HowToHandleCollapsedRange::ExtendForward &&
3907 point.IsEndOfContainer()) {
3908 return nullptr;
3912 // XXX: if the container of point is empty, then we'll need to delete the node
3913 // as well as the 1 child
3915 // build a transaction for deleting the appropriate data
3916 // XXX: this has to come from rule section
3917 const Element* const anonymousDivOrEditingHost =
3918 IsTextEditor() ? GetRoot() : AsHTMLEditor()->ComputeEditingHost();
3919 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward &&
3920 point.IsStartOfContainer()) {
3921 MOZ_ASSERT(IsHTMLEditor());
3922 // We're backspacing from the beginning of a node. Delete the last thing
3923 // of previous editable content.
3924 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
3925 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
3926 anonymousDivOrEditingHost);
3927 if (!previousEditableContent) {
3928 NS_WARNING("There was no editable content before the collapsed range");
3929 return nullptr;
3932 // There is an editable content, so delete its last child (if a text node,
3933 // delete the last char). If it has no children, delete it.
3934 if (previousEditableContent->IsText()) {
3935 uint32_t length = previousEditableContent->Length();
3936 // Bail out for empty text node.
3937 // XXX Do we want to do something else?
3938 // XXX If other browsers delete empty text node, we should follow it.
3939 if (NS_WARN_IF(!length)) {
3940 NS_WARNING("Previous editable content was an empty text node");
3941 return nullptr;
3943 RefPtr<DeleteTextTransaction> deleteTextTransaction =
3944 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
3945 *this, *previousEditableContent->AsText(), length);
3946 if (!deleteTextTransaction) {
3947 NS_WARNING(
3948 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
3949 return nullptr;
3951 return deleteTextTransaction.forget();
3954 if (IsHTMLEditor() &&
3955 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*previousEditableContent))) {
3956 return nullptr;
3958 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
3959 DeleteNodeTransaction::MaybeCreate(*this, *previousEditableContent);
3960 if (!deleteNodeTransaction) {
3961 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed");
3962 return nullptr;
3964 return deleteNodeTransaction.forget();
3967 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward &&
3968 point.IsEndOfContainer()) {
3969 MOZ_ASSERT(IsHTMLEditor());
3970 // We're deleting from the end of a node. Delete the first thing of
3971 // next editable content.
3972 nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
3973 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
3974 anonymousDivOrEditingHost);
3975 if (!nextEditableContent) {
3976 NS_WARNING("There was no editable content after the collapsed range");
3977 return nullptr;
3980 // There is an editable content, so delete its first child (if a text node,
3981 // delete the first char). If it has no children, delete it.
3982 if (nextEditableContent->IsText()) {
3983 uint32_t length = nextEditableContent->Length();
3984 // Bail out for empty text node.
3985 // XXX Do we want to do something else?
3986 // XXX If other browsers delete empty text node, we should follow it.
3987 if (!length) {
3988 NS_WARNING("Next editable content was an empty text node");
3989 return nullptr;
3991 RefPtr<DeleteTextTransaction> deleteTextTransaction =
3992 DeleteTextTransaction::MaybeCreateForNextCharacter(
3993 *this, *nextEditableContent->AsText(), 0);
3994 if (!deleteTextTransaction) {
3995 NS_WARNING(
3996 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
3997 return nullptr;
3999 return deleteTextTransaction.forget();
4002 if (IsHTMLEditor() &&
4003 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*nextEditableContent))) {
4004 return nullptr;
4006 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
4007 DeleteNodeTransaction::MaybeCreate(*this, *nextEditableContent);
4008 if (!deleteNodeTransaction) {
4009 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed");
4010 return nullptr;
4012 return deleteNodeTransaction.forget();
4015 if (point.IsInTextNode()) {
4016 if (aHowToHandleCollapsedRange ==
4017 HowToHandleCollapsedRange::ExtendBackward) {
4018 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4019 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
4020 *this, *point.ContainerAsText(), point.Offset());
4021 NS_WARNING_ASSERTION(
4022 deleteTextTransaction,
4023 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
4024 return deleteTextTransaction.forget();
4026 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4027 DeleteTextTransaction::MaybeCreateForNextCharacter(
4028 *this, *point.ContainerAsText(), point.Offset());
4029 NS_WARNING_ASSERTION(
4030 deleteTextTransaction,
4031 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
4032 return deleteTextTransaction.forget();
4035 nsIContent* editableContent = nullptr;
4036 if (IsHTMLEditor()) {
4037 editableContent =
4038 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward
4039 ? HTMLEditUtils::GetPreviousContent(
4040 point, {WalkTreeOption::IgnoreNonEditableNode},
4041 anonymousDivOrEditingHost)
4042 : HTMLEditUtils::GetNextContent(
4043 point, {WalkTreeOption::IgnoreNonEditableNode},
4044 anonymousDivOrEditingHost);
4045 if (!editableContent) {
4046 NS_WARNING("There was no editable content around the collapsed range");
4047 return nullptr;
4049 while (editableContent && editableContent->IsCharacterData() &&
4050 !editableContent->Length()) {
4051 // Can't delete an empty text node (bug 762183)
4052 editableContent =
4053 aHowToHandleCollapsedRange ==
4054 HowToHandleCollapsedRange::ExtendBackward
4055 ? HTMLEditUtils::GetPreviousContent(
4056 *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
4057 anonymousDivOrEditingHost)
4058 : HTMLEditUtils::GetNextContent(
4059 *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
4060 anonymousDivOrEditingHost);
4062 if (!editableContent) {
4063 NS_WARNING(
4064 "There was no editable content which is not empty around the "
4065 "collapsed range");
4066 return nullptr;
4068 } else {
4069 MOZ_ASSERT(point.IsInTextNode());
4070 editableContent = point.GetContainerAsContent();
4071 if (!editableContent) {
4072 NS_WARNING("If there was no text node, should've been handled first");
4073 return nullptr;
4077 if (editableContent->IsText()) {
4078 if (aHowToHandleCollapsedRange ==
4079 HowToHandleCollapsedRange::ExtendBackward) {
4080 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4081 DeleteTextTransaction::MaybeCreateForPreviousCharacter(
4082 *this, *editableContent->AsText(), editableContent->Length());
4083 NS_WARNING_ASSERTION(
4084 deleteTextTransaction,
4085 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed");
4086 return deleteTextTransaction.forget();
4089 RefPtr<DeleteTextTransaction> deleteTextTransaction =
4090 DeleteTextTransaction::MaybeCreateForNextCharacter(
4091 *this, *editableContent->AsText(), 0);
4092 NS_WARNING_ASSERTION(
4093 deleteTextTransaction,
4094 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed");
4095 return deleteTextTransaction.forget();
4098 MOZ_ASSERT(IsHTMLEditor());
4099 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*editableContent))) {
4100 return nullptr;
4102 RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
4103 DeleteNodeTransaction::MaybeCreate(*this, *editableContent);
4104 NS_WARNING_ASSERTION(deleteNodeTransaction,
4105 "DeleteNodeTransaction::MaybeCreate() failed");
4106 return deleteNodeTransaction.forget();
4109 bool EditorBase::FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
4110 nsIEditor::EDirection aDirectionAndAmount) const {
4111 MOZ_ASSERT(IsEditActionDataAvailable());
4113 if (NS_WARN_IF(Destroyed())) {
4114 return false;
4116 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
4117 aDirectionAndAmount, SelectionRef())) {
4118 return true;
4120 // Although AutoRangeArray::ExtendAnchorFocusRangeFor() will use
4121 // nsFrameSelection, if it still has dirty frame, nsFrameSelection doesn't
4122 // extend selection since we block script.
4123 if (RefPtr<PresShell> presShell = GetPresShell()) {
4124 presShell->FlushPendingNotifications(FlushType::Layout);
4125 if (NS_WARN_IF(Destroyed())) {
4126 return false;
4129 return true;
4132 nsresult EditorBase::DeleteSelectionAsAction(
4133 nsIEditor::EDirection aDirectionAndAmount,
4134 nsIEditor::EStripWrappers aStripWrappers, nsIPrincipal* aPrincipal) {
4135 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4136 // Showing this assertion is fine if this method is called by outside via
4137 // mutation event listener or something. Otherwise, this is called by
4138 // wrong method.
4139 NS_ASSERTION(
4140 !mPlaceholderBatch,
4141 "Should be called only when this is the only edit action of the "
4142 "operation unless mutation event listener nests some operations");
4144 // If we're a TextEditor instance, we don't need to treat parent elements
4145 // so that we can ignore aStripWrappers for skipping unnecessary cost.
4146 if (IsTextEditor()) {
4147 aStripWrappers = nsIEditor::eNoStrip;
4150 EditAction editAction = EditAction::eDeleteSelection;
4151 switch (aDirectionAndAmount) {
4152 case nsIEditor::ePrevious:
4153 editAction = EditAction::eDeleteBackward;
4154 break;
4155 case nsIEditor::eNext:
4156 editAction = EditAction::eDeleteForward;
4157 break;
4158 case nsIEditor::ePreviousWord:
4159 editAction = EditAction::eDeleteWordBackward;
4160 break;
4161 case nsIEditor::eNextWord:
4162 editAction = EditAction::eDeleteWordForward;
4163 break;
4164 case nsIEditor::eToBeginningOfLine:
4165 editAction = EditAction::eDeleteToBeginningOfSoftLine;
4166 break;
4167 case nsIEditor::eToEndOfLine:
4168 editAction = EditAction::eDeleteToEndOfSoftLine;
4169 break;
4172 AutoEditActionDataSetter editActionData(*this, editAction, aPrincipal);
4173 if (NS_WARN_IF(!editActionData.CanHandle())) {
4174 return NS_ERROR_NOT_INITIALIZED;
4177 // If there is an existing selection when an extended delete is requested,
4178 // platforms that use "caret-style" caret positioning collapse the
4179 // selection to the start and then create a new selection.
4180 // Platforms that use "selection-style" caret positioning just delete the
4181 // existing selection without extending it.
4182 if (!SelectionRef().IsCollapsed()) {
4183 switch (aDirectionAndAmount) {
4184 case eNextWord:
4185 case ePreviousWord:
4186 case eToBeginningOfLine:
4187 case eToEndOfLine: {
4188 if (mCaretStyle != 1) {
4189 aDirectionAndAmount = eNone;
4190 break;
4192 ErrorResult error;
4193 SelectionRef().CollapseToStart(error);
4194 if (NS_WARN_IF(Destroyed())) {
4195 error.SuppressException();
4196 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
4198 if (error.Failed()) {
4199 NS_WARNING("Selection::CollapseToStart() failed");
4200 editActionData.Abort();
4201 return EditorBase::ToGenericNSResult(error.StealNSResult());
4203 break;
4205 default:
4206 break;
4210 // If Selection is still NOT collapsed, it does not important removing
4211 // range of the operation since we'll remove the selected content. However,
4212 // information of direction (backward or forward) may be important for
4213 // web apps. E.g., web apps may want to mark selected range as "deleted"
4214 // and move caret before or after the range. Therefore, we should forget
4215 // only the range information but keep range information. See discussion
4216 // of the spec issue for the detail:
4217 // https://github.com/w3c/input-events/issues/82
4218 if (!SelectionRef().IsCollapsed()) {
4219 switch (editAction) {
4220 case EditAction::eDeleteWordBackward:
4221 case EditAction::eDeleteToBeginningOfSoftLine:
4222 editActionData.UpdateEditAction(EditAction::eDeleteBackward);
4223 break;
4224 case EditAction::eDeleteWordForward:
4225 case EditAction::eDeleteToEndOfSoftLine:
4226 editActionData.UpdateEditAction(EditAction::eDeleteForward);
4227 break;
4228 default:
4229 break;
4233 if (!FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
4234 aDirectionAndAmount)) {
4235 NS_WARNING("Flusing pending notifications caused destroying the editor");
4236 editActionData.Abort();
4237 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
4240 nsresult rv =
4241 editActionData.MaybeDispatchBeforeInputEvent(aDirectionAndAmount);
4242 if (NS_FAILED(rv)) {
4243 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4244 "MaybeDispatchBeforeInputEvent() failed");
4245 return EditorBase::ToGenericNSResult(rv);
4248 // delete placeholder txns merge.
4249 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName,
4250 ScrollSelectionIntoView::Yes,
4251 __FUNCTION__);
4252 rv = DeleteSelectionAsSubAction(aDirectionAndAmount, aStripWrappers);
4253 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4254 "EditorBase::DeleteSelectionAsSubAction() failed");
4255 return EditorBase::ToGenericNSResult(rv);
4258 nsresult EditorBase::DeleteSelectionAsSubAction(
4259 nsIEditor::EDirection aDirectionAndAmount,
4260 nsIEditor::EStripWrappers aStripWrappers) {
4261 MOZ_ASSERT(IsEditActionDataAvailable());
4262 // If handling edit action is for table editing, this may be called with
4263 // selecting an any table element by the caller, but it's not usual work of
4264 // this so that `MayEditActionDeleteSelection()` returns false.
4265 MOZ_ASSERT(MayEditActionDeleteSelection(GetEditAction()) ||
4266 IsEditActionTableEditing(GetEditAction()));
4267 MOZ_ASSERT(mPlaceholderBatch);
4268 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4269 NS_ASSERTION(IsHTMLEditor() || aStripWrappers == nsIEditor::eNoStrip,
4270 "TextEditor does not support strip wrappers");
4272 if (NS_WARN_IF(!mInitSucceeded)) {
4273 return NS_ERROR_NOT_INITIALIZED;
4276 IgnoredErrorResult ignoredError;
4277 AutoEditSubActionNotifier startToHandleEditSubAction(
4278 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount,
4279 ignoredError);
4280 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4281 return ignoredError.StealNSResult();
4283 NS_WARNING_ASSERTION(
4284 !ignoredError.Failed(),
4285 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4287 EditActionResult result =
4288 HandleDeleteSelection(aDirectionAndAmount, aStripWrappers);
4289 if (result.Failed() || result.Canceled()) {
4290 NS_WARNING_ASSERTION(result.Succeeded(),
4291 "TextEditor::HandleDeleteSelection() failed");
4292 return result.Rv();
4295 // XXX This is odd. We just tries to remove empty text node here but we
4296 // refer `Selection`. It may be modified by mutation event listeners
4297 // so that we should remove the empty text node when we make it empty.
4298 const auto atNewStartOfSelection =
4299 GetFirstSelectionStartPoint<EditorDOMPoint>();
4300 if (NS_WARN_IF(!atNewStartOfSelection.IsSet())) {
4301 // XXX And also it seems that we don't need to return error here.
4302 // Why don't we just ignore? `Selection::RemoveAllRanges()` may
4303 // have been called by mutation event listeners.
4304 return NS_ERROR_FAILURE;
4306 if (IsHTMLEditor() && atNewStartOfSelection.IsInTextNode() &&
4307 !atNewStartOfSelection.GetContainer()->Length()) {
4308 nsresult rv = DeleteNodeWithTransaction(
4309 MOZ_KnownLive(*atNewStartOfSelection.ContainerAsText()));
4310 if (NS_FAILED(rv)) {
4311 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4312 return rv;
4316 // XXX I don't think that this is necessary in anonymous `<div>` element of
4317 // TextEditor since there should be at most one text node and at most
4318 // one padding `<br>` element so that `<br>` element won't be before
4319 // caret.
4320 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine) {
4321 // We prevent the caret from sticking on the left of previous `<br>`
4322 // element (i.e. the end of previous line) after this deletion. Bug 92124.
4323 if (MOZ_UNLIKELY(NS_FAILED(SelectionRef().SetInterlinePosition(
4324 InterlinePosition::StartOfNextLine)))) {
4325 NS_WARNING(
4326 "Selection::SetInterlinePosition(InterlinePosition::StartOfNextLine) "
4327 "failed");
4328 return NS_ERROR_FAILURE; // Don't need to return NS_ERROR_NOT_INITIALIZED
4332 return NS_OK;
4335 nsresult EditorBase::HandleDropEvent(DragEvent* aDropEvent) {
4336 if (NS_WARN_IF(!aDropEvent)) {
4337 return NS_ERROR_INVALID_ARG;
4340 DebugOnly<nsresult> rvIgnored = CommitComposition();
4341 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4342 "EditorBase::CommitComposition() failed, but ignored");
4344 AutoEditActionDataSetter editActionData(*this, EditAction::eDrop);
4345 // We need to initialize data or dataTransfer later. Therefore, we cannot
4346 // dispatch "beforeinput" event until then.
4347 if (NS_WARN_IF(!editActionData.CanHandle())) {
4348 return NS_ERROR_NOT_INITIALIZED;
4351 RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer();
4352 if (NS_WARN_IF(!dataTransfer)) {
4353 return NS_ERROR_FAILURE;
4356 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
4357 if (NS_WARN_IF(!dragSession)) {
4358 return NS_ERROR_FAILURE;
4361 nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode();
4363 // If there is no source document, then the drag was from another application
4364 // or another process (such as an out of process subframe). The latter case is
4365 // not currently handled below when checking for a move/copy and deleting the
4366 // existing text.
4367 RefPtr<Document> srcdoc;
4368 if (sourceNode) {
4369 srcdoc = sourceNode->OwnerDoc();
4372 nsCOMPtr<nsIPrincipal> sourcePrincipal;
4373 dragSession->GetTriggeringPrincipal(getter_AddRefs(sourcePrincipal));
4375 if (nsContentUtils::CheckForSubFrameDrop(
4376 dragSession, aDropEvent->WidgetEventPtr()->AsDragEvent())) {
4377 // Don't allow drags from subframe documents with different origins than
4378 // the drop destination.
4379 if (!IsSafeToInsertData(sourcePrincipal)) {
4380 return NS_OK;
4384 // Current doc is destination
4385 RefPtr<Document> document = GetDocument();
4386 if (NS_WARN_IF(!document)) {
4387 return NS_ERROR_NOT_INITIALIZED;
4390 const uint32_t numItems = dataTransfer->MozItemCount();
4391 if (NS_WARN_IF(!numItems)) {
4392 return NS_ERROR_FAILURE; // Nothing to drop?
4395 // We have to figure out whether to delete and relocate caret only once
4396 // Parent and offset are under the mouse cursor.
4397 int32_t dropOffset = -1;
4398 nsCOMPtr<nsIContent> dropParentContent =
4399 aDropEvent->GetRangeParentContentAndOffset(&dropOffset);
4400 if (dropOffset < 0) {
4401 NS_WARNING(
4402 "DropEvent::GetRangeParentContentAndOffset() returned negative offset");
4403 return NS_ERROR_FAILURE;
4405 EditorDOMPoint droppedAt(dropParentContent,
4406 AssertedCast<uint32_t>(dropOffset));
4407 if (NS_WARN_IF(!droppedAt.IsSet()) ||
4408 NS_WARN_IF(!droppedAt.GetContainerAsContent())) {
4409 return NS_ERROR_FAILURE;
4412 // Check if dropping into a selected range. If so and the source comes from
4413 // same document, jump through some hoops to determine if mouse is over
4414 // selection (bail) and whether user wants to copy selection or delete it.
4415 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) {
4416 bool isPointInSelection = EditorUtils::IsPointInSelection(
4417 SelectionRef(), *droppedAt.GetContainer(), droppedAt.Offset());
4418 if (isPointInSelection) {
4419 // If source document and destination document is same and dropping
4420 // into one of selected ranges, we don't need to do nothing.
4421 // XXX If the source comes from outside of this editor, this check
4422 // means that we don't allow to drop the item in the selected
4423 // range. However, the selection is hidden until the <input> or
4424 // <textarea> gets focus, therefore, this looks odd.
4425 return NS_OK;
4429 // Delete if user doesn't want to copy when user moves selected content
4430 // to different place in same editor.
4431 // XXX Do we need the check whether it's in same document or not?
4432 RefPtr<EditorBase> editorToDeleteSelection;
4433 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) {
4434 if ((dataTransfer->DropEffectInt() &
4435 nsIDragService::DRAGDROP_ACTION_MOVE) &&
4436 !(dataTransfer->DropEffectInt() &
4437 nsIDragService::DRAGDROP_ACTION_COPY)) {
4438 // If the source node is in native anonymous tree, it must be in
4439 // <input> or <textarea> element. If so, its TextEditor can remove it.
4440 if (sourceNode->IsInNativeAnonymousSubtree()) {
4441 if (RefPtr<TextControlElement> textControlElement =
4442 TextControlElement::FromNodeOrNull(
4443 sourceNode->GetClosestNativeAnonymousSubtreeRootParent())) {
4444 editorToDeleteSelection = textControlElement->GetTextEditor();
4447 // Otherwise, must be the content is in HTMLEditor.
4448 else if (IsHTMLEditor()) {
4449 editorToDeleteSelection = this;
4450 } else {
4451 editorToDeleteSelection =
4452 nsContentUtils::GetHTMLEditor(srcdoc->GetPresContext());
4455 // If the found editor isn't modifiable, we should not try to delete
4456 // selection.
4457 if (editorToDeleteSelection && !editorToDeleteSelection->IsModifiable()) {
4458 editorToDeleteSelection = nullptr;
4460 // If the found editor has collapsed selection, we need to delete nothing
4461 // in the editor.
4462 if (editorToDeleteSelection) {
4463 if (Selection* selection = editorToDeleteSelection->GetSelection()) {
4464 if (selection->IsCollapsed()) {
4465 editorToDeleteSelection = nullptr;
4471 if (IsInPlaintextMode()) {
4472 for (nsIContent* content = droppedAt.GetContainerAsContent(); content;
4473 content = content->GetParent()) {
4474 nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
4475 if (formControl && !formControl->AllowDrop()) {
4476 // Don't allow dropping into a form control that doesn't allow being
4477 // dropped into.
4478 return NS_OK;
4483 // Combine any deletion and drop insertion into one transaction.
4484 AutoPlaceholderBatch treatAsOneTransaction(
4485 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
4487 // Don't dispatch "selectionchange" event until inserting all contents.
4488 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
4490 // Track dropped point with nsRange because we shouldn't insert the
4491 // dropped content into different position even if some event listeners
4492 // modify selection. Note that Chrome's behavior is really odd. So,
4493 // we don't need to worry about web-compat about this.
4494 IgnoredErrorResult ignoredError;
4495 RefPtr<nsRange> rangeAtDropPoint =
4496 nsRange::Create(droppedAt.ToRawRangeBoundary(),
4497 droppedAt.ToRawRangeBoundary(), ignoredError);
4498 if (NS_WARN_IF(ignoredError.Failed()) ||
4499 NS_WARN_IF(!rangeAtDropPoint->IsPositioned())) {
4500 editActionData.Abort();
4501 return NS_ERROR_FAILURE;
4504 // Remove selected contents first here because we need to fire a pair of
4505 // "beforeinput" and "input" for deletion and web apps can cancel only
4506 // this deletion. Note that callee may handle insertion asynchronously.
4507 // Therefore, it is the best to remove selected content here.
4508 if (editorToDeleteSelection) {
4509 nsresult rv = editorToDeleteSelection->DeleteSelectionByDragAsAction(
4510 mDispatchInputEvent);
4511 if (NS_WARN_IF(Destroyed())) {
4512 editActionData.Abort();
4513 return NS_OK;
4515 // Ignore the editor instance specific error if it's another editor.
4516 if (this != editorToDeleteSelection &&
4517 (rv == NS_ERROR_NOT_INITIALIZED || rv == NS_ERROR_EDITOR_DESTROYED)) {
4518 rv = NS_OK;
4520 // Don't cancel "insertFromDrop" even if "deleteByDrag" is canceled.
4521 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) {
4522 NS_WARNING("EditorBase::DeleteSelectionByDragAsAction() failed");
4523 editActionData.Abort();
4524 return EditorBase::ToGenericNSResult(rv);
4526 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
4527 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
4528 editActionData.Abort();
4529 return NS_ERROR_FAILURE;
4531 droppedAt = rangeAtDropPoint->StartRef();
4532 MOZ_ASSERT(droppedAt.IsSetAndValid());
4535 // Before inserting dropping content, we need to move focus for compatibility
4536 // with Chrome and firing "beforeinput" event on new editing host.
4537 RefPtr<Element> focusedElement, newFocusedElement;
4538 if (IsTextEditor()) {
4539 newFocusedElement = GetExposedRoot();
4540 focusedElement = IsActiveInDOMWindow() ? newFocusedElement : nullptr;
4542 // TODO: We need to add automated tests when dropping something into an
4543 // editing host for contenteditable which is in a shadow DOM tree
4544 // and its host which is in design mode.
4545 else if (!AsHTMLEditor()->IsInDesignMode()) {
4546 focusedElement = AsHTMLEditor()->ComputeEditingHost();
4547 if (focusedElement &&
4548 droppedAt.GetContainerAsContent()->IsInclusiveDescendantOf(
4549 focusedElement)) {
4550 newFocusedElement = focusedElement;
4551 } else {
4552 newFocusedElement = droppedAt.GetContainerAsContent()->GetEditingHost();
4555 // Move selection right now. Note that this does not move focus because
4556 // `Selection` moves focus with selection change only when the API caller is
4557 // JS. And also this does not notify selection listeners (nor
4558 // "selectionchange") since we created SelectionBatcher above.
4559 ErrorResult error;
4560 SelectionRef().SetStartAndEnd(droppedAt.ToRawRangeBoundary(),
4561 droppedAt.ToRawRangeBoundary(), error);
4562 if (error.Failed()) {
4563 NS_WARNING("Selection::SetStartAndEnd() failed");
4564 editActionData.Abort();
4565 return error.StealNSResult();
4567 if (NS_WARN_IF(Destroyed())) {
4568 editActionData.Abort();
4569 return NS_OK;
4571 // Then, move focus if necessary. This must cause dispatching "blur" event
4572 // and "focus" event.
4573 if (newFocusedElement && focusedElement != newFocusedElement) {
4574 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
4575 DebugOnly<nsresult> rvIgnored = fm->SetFocus(newFocusedElement, 0);
4576 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4577 "nsFocusManager::SetFocus() failed to set focus "
4578 "to the element, but ignored");
4579 if (NS_WARN_IF(Destroyed())) {
4580 editActionData.Abort();
4581 return NS_OK;
4583 // "blur" or "focus" event listener may have changed the value.
4584 // Let's keep using the original point.
4585 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) ||
4586 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) {
4587 return NS_ERROR_FAILURE;
4589 droppedAt = rangeAtDropPoint->StartRef();
4590 MOZ_ASSERT(droppedAt.IsSetAndValid());
4592 // If focus is changed to different element and we're handling drop in
4593 // contenteditable, we cannot handle it without focus. So, we should give
4594 // it up.
4595 if (IsHTMLEditor() && !AsHTMLEditor()->IsInDesignMode() &&
4596 NS_WARN_IF(newFocusedElement != AsHTMLEditor()->ComputeEditingHost())) {
4597 editActionData.Abort();
4598 return NS_OK;
4602 nsresult rv = InsertDroppedDataTransferAsAction(editActionData, *dataTransfer,
4603 droppedAt, sourcePrincipal);
4604 if (rv == NS_ERROR_EDITOR_DESTROYED ||
4605 rv == NS_ERROR_EDITOR_ACTION_CANCELED) {
4606 return EditorBase::ToGenericNSResult(rv);
4608 NS_WARNING_ASSERTION(
4609 NS_SUCCEEDED(rv),
4610 "EditorBase::InsertDroppedDataTransferAsAction() failed, but ignored");
4612 rv = ScrollSelectionFocusIntoView();
4613 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4614 "EditorBase::ScrollSelectionFocusIntoView() failed");
4615 return rv;
4618 nsresult EditorBase::DeleteSelectionByDragAsAction(bool aDispatchInputEvent) {
4619 // TODO: Move this method to `EditorBase`.
4620 AutoRestore<bool> saveDispatchInputEvent(mDispatchInputEvent);
4621 mDispatchInputEvent = aDispatchInputEvent;
4622 // Even if we're handling "deleteByDrag" in same editor as "insertFromDrop",
4623 // we need to recreate edit action data here because
4624 // `AutoEditActionDataSetter` needs to manage event state separately.
4625 bool requestedByAnotherEditor = GetEditAction() != EditAction::eDrop;
4626 AutoEditActionDataSetter editActionData(*this, EditAction::eDeleteByDrag);
4627 MOZ_ASSERT(!SelectionRef().IsCollapsed());
4628 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
4629 if (NS_FAILED(rv)) {
4630 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4631 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
4632 return rv;
4634 // But keep using placeholder transaction for "insertFromDrop" if there is.
4635 Maybe<AutoPlaceholderBatch> treatAsOneTransaction;
4636 if (requestedByAnotherEditor) {
4637 treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes,
4638 __FUNCTION__);
4641 rv = DeleteSelectionAsSubAction(nsIEditor::eNone, IsTextEditor()
4642 ? nsIEditor::eNoStrip
4643 : nsIEditor::eStrip);
4644 if (NS_FAILED(rv)) {
4645 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed");
4646 return rv;
4649 if (!mDispatchInputEvent) {
4650 return NS_OK;
4653 if (treatAsOneTransaction.isNothing()) {
4654 DispatchInputEvent();
4656 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
4659 nsresult EditorBase::DeleteSelectionWithTransaction(
4660 nsIEditor::EDirection aDirectionAndAmount,
4661 nsIEditor::EStripWrappers aStripWrappers) {
4662 MOZ_ASSERT(IsEditActionDataAvailable());
4663 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4664 if (NS_WARN_IF(Destroyed())) {
4665 return NS_ERROR_EDITOR_DESTROYED;
4668 AutoRangeArray rangesToDelete(SelectionRef());
4669 if (NS_WARN_IF(rangesToDelete.Ranges().IsEmpty())) {
4670 NS_ASSERTION(
4671 false,
4672 "For avoiding to throw incompatible exception for `execCommand`, fix "
4673 "the caller");
4674 return NS_ERROR_FAILURE;
4677 if (IsTextEditor()) {
4678 if (const Text* theTextNode = AsTextEditor()->GetTextNode()) {
4679 rangesToDelete.EnsureRangesInTextNode(*theTextNode);
4683 nsresult rv = DeleteRangesWithTransaction(aDirectionAndAmount, aStripWrappers,
4684 rangesToDelete);
4685 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4686 "EditorBase::DeleteRangesWithTransaction() failed");
4687 return rv;
4690 nsresult EditorBase::DeleteRangesWithTransaction(
4691 nsIEditor::EDirection aDirectionAndAmount,
4692 nsIEditor::EStripWrappers aStripWrappers,
4693 const AutoRangeArray& aRangesToDelete) {
4694 MOZ_ASSERT(IsEditActionDataAvailable());
4695 MOZ_ASSERT(!Destroyed());
4696 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4697 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
4699 HowToHandleCollapsedRange howToHandleCollapsedRange =
4700 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
4701 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
4702 howToHandleCollapsedRange ==
4703 HowToHandleCollapsedRange::Ignore)) {
4704 NS_ASSERTION(
4705 false,
4706 "For avoiding to throw incompatible exception for `execCommand`, fix "
4707 "the caller");
4708 return NS_ERROR_FAILURE;
4711 RefPtr<EditAggregateTransaction> deleteSelectionTransaction =
4712 CreateTransactionForDeleteSelection(howToHandleCollapsedRange,
4713 aRangesToDelete);
4714 if (!deleteSelectionTransaction) {
4715 NS_WARNING("EditorBase::CreateTransactionForDeleteSelection() failed");
4716 return NS_ERROR_FAILURE;
4719 // XXX This is odd, this assumes that there are no multiple collapsed
4720 // ranges in `Selection`, but it's possible scenario.
4721 // XXX This loop looks slow, but it's rarely so because of multiple
4722 // selection is not used so many times.
4723 nsCOMPtr<nsIContent> deleteContent;
4724 uint32_t deleteCharOffset = 0;
4725 for (const OwningNonNull<EditTransactionBase>& transactionBase :
4726 Reversed(deleteSelectionTransaction->ChildTransactions())) {
4727 if (DeleteTextTransaction* deleteTextTransaction =
4728 transactionBase->GetAsDeleteTextTransaction()) {
4729 deleteContent = deleteTextTransaction->GetText();
4730 deleteCharOffset = deleteTextTransaction->Offset();
4731 break;
4733 if (DeleteNodeTransaction* deleteNodeTransaction =
4734 transactionBase->GetAsDeleteNodeTransaction()) {
4735 deleteContent = deleteNodeTransaction->GetContent();
4736 break;
4740 RefPtr<CharacterData> deleteCharData =
4741 CharacterData::FromNodeOrNull(deleteContent);
4742 IgnoredErrorResult ignoredError;
4743 AutoEditSubActionNotifier startToHandleEditSubAction(
4744 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount,
4745 ignoredError);
4746 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4747 return ignoredError.StealNSResult();
4749 NS_WARNING_ASSERTION(
4750 !ignoredError.Failed(),
4751 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4753 if (IsHTMLEditor()) {
4754 if (!deleteContent) {
4755 // XXX We may remove multiple ranges in the following. Therefore,
4756 // this must have a bug since we only add the first range into
4757 // the changed range.
4758 TopLevelEditSubActionDataRef().WillDeleteRange(
4759 *this, aRangesToDelete.GetFirstRangeStartPoint<EditorRawDOMPoint>(),
4760 aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>());
4761 } else if (!deleteCharData) {
4762 TopLevelEditSubActionDataRef().WillDeleteContent(*this, *deleteContent);
4766 // Notify nsIEditActionListener::WillDelete[Selection|Text]
4767 if (!mActionListeners.IsEmpty()) {
4768 if (!deleteContent) {
4769 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
4770 AutoTArray<RefPtr<nsRange>, 8> rangesToDelete(
4771 aRangesToDelete.CloneRanges<RefPtr>());
4772 AutoActionListenerArray listeners(mActionListeners.Clone());
4773 for (auto& listener : listeners) {
4774 DebugOnly<nsresult> rvIgnored =
4775 listener->WillDeleteRanges(rangesToDelete);
4776 NS_WARNING_ASSERTION(
4777 NS_SUCCEEDED(rvIgnored),
4778 "nsIEditActionListener::WillDeleteRanges() failed, but ignored");
4779 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(),
4780 "nsIEditActionListener::WillDeleteRanges() "
4781 "must not destroy the editor");
4783 } else if (deleteCharData) {
4784 AutoActionListenerArray listeners(mActionListeners.Clone());
4785 for (auto& listener : listeners) {
4786 // XXX Why don't we notify listeners of actual length?
4787 DebugOnly<nsresult> rvIgnored =
4788 listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
4789 NS_WARNING_ASSERTION(
4790 NS_SUCCEEDED(rvIgnored),
4791 "nsIEditActionListener::WillDeleteText() failed, but ignored");
4792 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(),
4793 "nsIEditActionListener::WillDeleteText() must "
4794 "not destroy the editor");
4799 // Delete the specified amount
4800 nsresult rv = DoTransactionInternal(deleteSelectionTransaction);
4801 // I'm not sure whether we should keep notifying edit action listeners or
4802 // stop doing it. For now, just keep traditional behavior.
4803 bool destroyedByTransaction = Destroyed();
4804 NS_WARNING_ASSERTION(destroyedByTransaction || NS_SUCCEEDED(rv),
4805 "EditorBase::DoTransactionInternal() failed");
4807 if (IsHTMLEditor() && deleteCharData) {
4808 MOZ_ASSERT(deleteContent);
4809 TopLevelEditSubActionDataRef().DidDeleteText(
4810 *this, EditorRawDOMPoint(deleteContent));
4813 if (mTextServicesDocument && NS_SUCCEEDED(rv) && deleteContent &&
4814 !deleteCharData) {
4815 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
4816 textServicesDocument->DidDeleteContent(*deleteContent);
4817 MOZ_ASSERT(
4818 destroyedByTransaction || !Destroyed(),
4819 "TextServicesDocument::DidDeleteContent() must not destroy the editor");
4822 if (!mActionListeners.IsEmpty() && deleteContent && !deleteCharData) {
4823 for (auto& listener : mActionListeners.Clone()) {
4824 DebugOnly<nsresult> rvIgnored =
4825 listener->DidDeleteNode(deleteContent, rv);
4826 NS_WARNING_ASSERTION(
4827 NS_SUCCEEDED(rvIgnored),
4828 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
4829 MOZ_DIAGNOSTIC_ASSERT(
4830 destroyedByTransaction || !Destroyed(),
4831 "nsIEditActionListener::DidDeleteNode() must not destroy the editor");
4835 if (NS_WARN_IF(destroyedByTransaction)) {
4836 return NS_ERROR_EDITOR_DESTROYED;
4838 if (NS_FAILED(rv)) {
4839 return rv;
4842 if (IsTextEditor() || aStripWrappers == nsIEditor::eNoStrip) {
4843 return NS_OK;
4846 if (!SelectionRef().IsCollapsed()) {
4847 NS_WARNING("Selection was changed by mutation event listeners");
4848 return NS_OK;
4851 nsINode* anchorNode = SelectionRef().GetAnchorNode();
4852 if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!anchorNode->IsContent()) ||
4853 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*anchorNode)) ||
4854 anchorNode->Length() > 0) {
4855 return NS_OK;
4858 OwningNonNull<nsIContent> anchorContent = *anchorNode->AsContent();
4859 rv = MOZ_KnownLive(AsHTMLEditor())
4860 ->RemoveEmptyInclusiveAncestorInlineElements(anchorContent);
4861 NS_WARNING_ASSERTION(
4862 NS_SUCCEEDED(rv),
4863 "HTMLEditor::RemoveEmptyInclusiveAncestorInlineElements() failed");
4864 return rv;
4867 already_AddRefed<Element> EditorBase::CreateHTMLContent(
4868 const nsAtom* aTag) const {
4869 MOZ_ASSERT(aTag);
4871 RefPtr<Document> document = GetDocument();
4872 if (NS_WARN_IF(!document)) {
4873 return nullptr;
4876 // XXX Wallpaper over editor bug (editor tries to create elements with an
4877 // empty nodename).
4878 if (aTag == nsGkAtoms::_empty) {
4879 NS_ERROR(
4880 "Don't pass an empty tag to EditorBase::CreateHTMLContent, "
4881 "check caller.");
4882 return nullptr;
4885 return document->CreateElem(nsDependentAtomString(aTag), nullptr,
4886 kNameSpaceID_XHTML);
4889 already_AddRefed<nsTextNode> EditorBase::CreateTextNode(
4890 const nsAString& aData) const {
4891 MOZ_ASSERT(IsEditActionDataAvailable());
4893 Document* document = GetDocument();
4894 if (NS_WARN_IF(!document)) {
4895 return nullptr;
4897 RefPtr<nsTextNode> text = document->CreateEmptyTextNode();
4898 text->MarkAsMaybeModifiedFrequently();
4899 if (IsPasswordEditor()) {
4900 text->MarkAsMaybeMasked();
4902 // Don't notify; this node is still being created.
4903 DebugOnly<nsresult> rvIgnored = text->SetText(aData, false);
4904 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4905 "Text::SetText() failed, but ignored");
4906 return text.forget();
4909 NS_IMETHODIMP EditorBase::SetAttributeOrEquivalent(Element* aElement,
4910 const nsAString& aAttribute,
4911 const nsAString& aValue,
4912 bool aSuppressTransaction) {
4913 if (NS_WARN_IF(!aElement)) {
4914 return NS_ERROR_NULL_POINTER;
4917 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute);
4918 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
4919 if (NS_FAILED(rv)) {
4920 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4921 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
4922 return EditorBase::ToGenericNSResult(rv);
4925 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
4926 rv = SetAttributeOrEquivalent(aElement, attribute, aValue,
4927 aSuppressTransaction);
4928 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4929 "EditorBase::SetAttributeOrEquivalent() failed");
4930 return EditorBase::ToGenericNSResult(rv);
4933 NS_IMETHODIMP EditorBase::RemoveAttributeOrEquivalent(
4934 Element* aElement, const nsAString& aAttribute, bool aSuppressTransaction) {
4935 if (NS_WARN_IF(!aElement)) {
4936 return NS_ERROR_NULL_POINTER;
4939 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute);
4940 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
4941 if (NS_FAILED(rv)) {
4942 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
4943 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
4944 return EditorBase::ToGenericNSResult(rv);
4947 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute);
4948 rv = RemoveAttributeOrEquivalent(aElement, attribute, aSuppressTransaction);
4949 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4950 "EditorBase::RemoveAttributeOrEquivalent() failed");
4951 return EditorBase::ToGenericNSResult(rv);
4954 void EditorBase::HandleKeyPressEventInReadOnlyMode(
4955 WidgetKeyboardEvent& aKeyboardEvent) const {
4956 MOZ_ASSERT(IsReadonly());
4957 MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress);
4959 switch (aKeyboardEvent.mKeyCode) {
4960 case NS_VK_BACK:
4961 // If it's a `Backspace` key, let's consume it because it may be mapped
4962 // to "Back" of the history navigation. So, it's possible that user
4963 // tries to delete a character with `Backspace` even in the read-only
4964 // editor.
4965 aKeyboardEvent.PreventDefault();
4966 break;
4968 // XXX How about space key (page up and page down in browser navigation)?
4971 nsresult EditorBase::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
4972 MOZ_ASSERT(!IsReadonly());
4973 MOZ_ASSERT(aKeyboardEvent);
4974 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress);
4976 // NOTE: When you change this method, you should also change:
4977 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
4978 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
4980 // And also when you add new key handling, you need to change the subclass's
4981 // HandleKeyPressEvent()'s switch statement.
4983 switch (aKeyboardEvent->mKeyCode) {
4984 case NS_VK_META:
4985 case NS_VK_WIN:
4986 case NS_VK_SHIFT:
4987 case NS_VK_CONTROL:
4988 case NS_VK_ALT:
4989 MOZ_ASSERT_UNREACHABLE(
4990 "eKeyPress event shouldn't be fired for modifier keys");
4991 return NS_ERROR_UNEXPECTED;
4993 case NS_VK_BACK: {
4994 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
4995 aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) {
4996 return NS_OK;
4998 DebugOnly<nsresult> rvIgnored =
4999 DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip);
5000 aKeyboardEvent->PreventDefault();
5001 NS_WARNING_ASSERTION(
5002 NS_SUCCEEDED(rvIgnored),
5003 "EditorBase::DeleteSelectionAsAction() failed, but ignored");
5004 return NS_OK;
5006 case NS_VK_DELETE: {
5007 // on certain platforms (such as windows) the shift key
5008 // modifies what delete does (cmd_cut in this case).
5009 // bailing here to allow the keybindings to do the cut.
5010 if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
5011 aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
5012 aKeyboardEvent->IsOS()) {
5013 return NS_OK;
5015 DebugOnly<nsresult> rvIgnored =
5016 DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip);
5017 aKeyboardEvent->PreventDefault();
5018 NS_WARNING_ASSERTION(
5019 NS_SUCCEEDED(rvIgnored),
5020 "EditorBase::DeleteSelectionAsAction() failed, but ignored");
5021 return NS_OK;
5024 return NS_OK;
5027 nsresult EditorBase::OnInputText(const nsAString& aStringToInsert) {
5028 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
5029 MOZ_ASSERT(!aStringToInsert.IsVoid());
5030 editActionData.SetData(aStringToInsert);
5031 // FYI: For conforming to current UI Events spec, we should dispatch
5032 // "beforeinput" event before "keypress" event, but here is in a
5033 // "keypress" event listener. However, the other browsers dispatch
5034 // "beforeinput" event after "keypress" event. Therefore, it makes
5035 // sense to follow the other browsers. Spec issue:
5036 // https://github.com/w3c/uievents/issues/220
5037 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
5038 if (NS_FAILED(rv)) {
5039 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5040 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
5041 return EditorBase::ToGenericNSResult(rv);
5044 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
5045 ScrollSelectionIntoView::Yes,
5046 __FUNCTION__);
5047 rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
5048 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5049 "EditorBase::InsertTextAsSubAction() failed");
5050 return EditorBase::ToGenericNSResult(rv);
5053 nsresult EditorBase::ReplaceTextAsAction(
5054 const nsAString& aString, nsRange* aReplaceRange,
5055 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
5056 nsIPrincipal* aPrincipal) {
5057 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound);
5058 MOZ_ASSERT_IF(!aReplaceRange, IsTextEditor());
5060 AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText,
5061 aPrincipal);
5062 if (NS_WARN_IF(!editActionData.CanHandle())) {
5063 return NS_ERROR_NOT_INITIALIZED;
5065 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) {
5066 editActionData.MakeBeforeInputEventNonCancelable();
5069 if (IsTextEditor()) {
5070 editActionData.SetData(aString);
5071 } else {
5072 editActionData.InitializeDataTransfer(aString);
5073 RefPtr<StaticRange> targetRange;
5074 if (aReplaceRange) {
5075 // Compute offset of the range before dispatching `beforeinput` event
5076 // because it may be referred after the DOM tree is changed and the
5077 // range may have not computed the offset yet.
5078 targetRange = StaticRange::Create(
5079 aReplaceRange->GetStartContainer(), aReplaceRange->StartOffset(),
5080 aReplaceRange->GetEndContainer(), aReplaceRange->EndOffset(),
5081 IgnoreErrors());
5082 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
5083 "StaticRange::Create() failed");
5084 } else {
5085 Element* editingHost = AsHTMLEditor()->ComputeEditingHost();
5086 NS_WARNING_ASSERTION(editingHost,
5087 "No active editing host, no target ranges");
5088 if (editingHost) {
5089 targetRange = StaticRange::Create(
5090 editingHost, 0, editingHost, editingHost->Length(), IgnoreErrors());
5091 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(),
5092 "StaticRange::Create() failed");
5095 if (targetRange && targetRange->IsPositioned()) {
5096 editActionData.AppendTargetRange(*targetRange);
5100 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
5101 if (NS_FAILED(rv)) {
5102 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5103 "MaybeDispatchBeforeInputEvent() failed");
5104 return EditorBase::ToGenericNSResult(rv);
5107 AutoPlaceholderBatch treatAsOneTransaction(
5108 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
5110 // This should emulates inserting text for better undo/redo behavior.
5111 IgnoredErrorResult ignoredError;
5112 AutoEditSubActionNotifier startToHandleEditSubAction(
5113 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
5114 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5115 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
5117 NS_WARNING_ASSERTION(
5118 !ignoredError.Failed(),
5119 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5121 if (!aReplaceRange) {
5122 // Use fast path if we're `TextEditor` because it may be in a hot path.
5123 if (IsTextEditor()) {
5124 nsresult rv = MOZ_KnownLive(AsTextEditor())->SetTextAsSubAction(aString);
5125 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5126 "TextEditor::SetTextAsSubAction() failed");
5127 return EditorBase::ToGenericNSResult(rv);
5130 MOZ_ASSERT_UNREACHABLE("Setting value of `HTMLEditor` isn't supported");
5131 return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE);
5134 if (aString.IsEmpty() && aReplaceRange->Collapsed()) {
5135 NS_WARNING("Setting value was empty and replaced range was empty");
5136 return NS_OK;
5139 // Note that do not notify selectionchange caused by selecting all text
5140 // because it's preparation of our delete implementation so web apps
5141 // shouldn't receive such selectionchange before the first mutation.
5142 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
5144 // Select the range but as far as possible, we should not create new range
5145 // even if it's part of special Selection.
5146 ErrorResult error;
5147 SelectionRef().RemoveAllRanges(error);
5148 if (error.Failed()) {
5149 NS_WARNING("Selection::RemoveAllRanges() failed");
5150 return error.StealNSResult();
5152 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*aReplaceRange,
5153 error);
5154 if (error.Failed()) {
5155 NS_WARNING("Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
5156 return error.StealNSResult();
5159 rv = ReplaceSelectionAsSubAction(aString);
5160 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5161 "EditorBase::ReplaceSelectionAsSubAction() failed");
5162 return EditorBase::ToGenericNSResult(rv);
5165 nsresult EditorBase::ReplaceSelectionAsSubAction(const nsAString& aString) {
5166 if (aString.IsEmpty()) {
5167 nsresult rv = DeleteSelectionAsSubAction(
5168 nsIEditor::eNone,
5169 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
5170 NS_WARNING_ASSERTION(
5171 NS_SUCCEEDED(rv),
5172 "EditorBase::DeleteSelectionAsSubAction(eNone) failed");
5173 return rv;
5176 nsresult rv = InsertTextAsSubAction(aString, SelectionHandling::Delete);
5177 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5178 "EditorBase::InsertTextAsSubAction() failed");
5179 return rv;
5182 nsresult EditorBase::HandleInlineSpellCheck(
5183 const EditorDOMPoint& aPreviouslySelectedStart,
5184 const AbstractRange* aRange) {
5185 MOZ_ASSERT(IsEditActionDataAvailable());
5187 if (!mInlineSpellChecker) {
5188 return NS_OK;
5190 nsresult rv = mInlineSpellChecker->SpellCheckAfterEditorChange(
5191 GetTopLevelEditSubAction(), SelectionRef(),
5192 aPreviouslySelectedStart.GetContainer(),
5193 aPreviouslySelectedStart.Offset(),
5194 aRange ? aRange->GetStartContainer() : nullptr,
5195 aRange ? aRange->StartOffset() : 0,
5196 aRange ? aRange->GetEndContainer() : nullptr,
5197 aRange ? aRange->EndOffset() : 0);
5198 NS_WARNING_ASSERTION(
5199 NS_SUCCEEDED(rv),
5200 "mozInlineSpellChecker::SpellCheckAfterEditorChange() failed");
5201 return rv;
5204 Element* EditorBase::FindSelectionRoot(const nsINode& aNode) const {
5205 return GetRoot();
5208 void EditorBase::InitializeSelectionAncestorLimit(
5209 nsIContent& aAncestorLimit) const {
5210 MOZ_ASSERT(IsEditActionDataAvailable());
5212 SelectionRef().SetAncestorLimiter(&aAncestorLimit);
5215 nsresult EditorBase::InitializeSelection(
5216 const nsINode& aOriginalEventTargetNode) {
5217 MOZ_ASSERT(IsEditActionDataAvailable());
5219 nsCOMPtr<nsIContent> selectionRootContent =
5220 FindSelectionRoot(aOriginalEventTargetNode);
5221 if (!selectionRootContent) {
5222 return NS_OK;
5225 nsCOMPtr<nsISelectionController> selectionController =
5226 GetSelectionController();
5227 if (NS_WARN_IF(!selectionController)) {
5228 return NS_ERROR_FAILURE;
5231 // Init the caret
5232 RefPtr<nsCaret> caret = GetCaret();
5233 if (NS_WARN_IF(!caret)) {
5234 return NS_ERROR_FAILURE;
5236 caret->SetSelection(&SelectionRef());
5237 DebugOnly<nsresult> rvIgnored =
5238 selectionController->SetCaretReadOnly(IsReadonly());
5239 NS_WARNING_ASSERTION(
5240 NS_SUCCEEDED(rvIgnored),
5241 "nsISelectionController::SetCaretReadOnly() failed, but ignored");
5242 rvIgnored = selectionController->SetCaretEnabled(true);
5243 NS_WARNING_ASSERTION(
5244 NS_SUCCEEDED(rvIgnored),
5245 "nsISelectionController::SetCaretEnabled() failed, but ignored");
5246 // NOTE(emilio): It's important for this call to be after
5247 // SetCaretEnabled(true), since that would override mIgnoreUserModify to true.
5249 // Also, make sure to always ignore it for designMode, since that effectively
5250 // overrides everything and we allow to edit stuff with
5251 // contenteditable="false" subtrees in such a document.
5252 caret->SetIgnoreUserModify(aOriginalEventTargetNode.IsInDesignMode());
5254 // Init selection
5255 rvIgnored =
5256 selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
5257 NS_WARNING_ASSERTION(
5258 NS_SUCCEEDED(rvIgnored),
5259 "nsISelectionController::SetSelectionFlags() failed, but ignored");
5261 selectionController->SelectionWillTakeFocus();
5263 // If the computed selection root isn't root content, we should set it
5264 // as selection ancestor limit. However, if that is root element, it means
5265 // there is not limitation of the selection, then, we must set nullptr.
5266 // NOTE: If we set a root element to the ancestor limit, some selection
5267 // methods don't work fine.
5268 if (selectionRootContent->GetParent()) {
5269 InitializeSelectionAncestorLimit(*selectionRootContent);
5270 } else {
5271 SelectionRef().SetAncestorLimiter(nullptr);
5274 // If there is composition when this is called, we may need to restore IME
5275 // selection because if the editor is reframed, this already forgot IME
5276 // selection and the transaction.
5277 if (mComposition && mComposition->IsMovingToNewTextNode()) {
5278 // We need to look for the new text node from current selection.
5279 // XXX If selection is changed during reframe, this doesn't work well!
5280 const nsRange* firstRange = SelectionRef().GetRangeAt(0);
5281 if (NS_WARN_IF(!firstRange)) {
5282 return NS_ERROR_FAILURE;
5284 EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
5285 EditorRawDOMPoint betterInsertionPoint =
5286 FindBetterInsertionPoint(atStartOfFirstRange);
5287 RefPtr<Text> textNode = betterInsertionPoint.GetContainerAsText();
5288 MOZ_ASSERT(textNode,
5289 "There must be text node if composition string is not empty");
5290 if (textNode) {
5291 MOZ_ASSERT(textNode->Length() >= mComposition->XPEndOffsetInTextNode(),
5292 "The text node must be different from the old text node");
5293 RefPtr<TextRangeArray> ranges = mComposition->GetRanges();
5294 DebugOnly<nsresult> rvIgnored = CompositionTransaction::SetIMESelection(
5295 *this, textNode, mComposition->XPOffsetInTextNode(),
5296 mComposition->XPLengthInTextNode(), ranges);
5297 NS_WARNING_ASSERTION(
5298 NS_SUCCEEDED(rvIgnored),
5299 "CompositionTransaction::SetIMESelection() failed, but ignored");
5300 mComposition->OnUpdateCompositionInEditor(
5301 mComposition->String(), *textNode,
5302 mComposition->XPOffsetInTextNode());
5306 return NS_OK;
5309 nsresult EditorBase::FinalizeSelection() {
5310 nsCOMPtr<nsISelectionController> selectionController =
5311 GetSelectionController();
5312 if (NS_WARN_IF(!selectionController)) {
5313 return NS_ERROR_FAILURE;
5316 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
5317 if (NS_WARN_IF(!editActionData.CanHandle())) {
5318 return NS_ERROR_NOT_INITIALIZED;
5321 SelectionRef().SetAncestorLimiter(nullptr);
5323 if (NS_WARN_IF(!GetPresShell())) {
5324 return NS_ERROR_NOT_INITIALIZED;
5327 if (RefPtr<nsCaret> caret = GetCaret()) {
5328 caret->SetIgnoreUserModify(true);
5329 DebugOnly<nsresult> rvIgnored = selectionController->SetCaretEnabled(false);
5330 NS_WARNING_ASSERTION(
5331 NS_SUCCEEDED(rvIgnored),
5332 "nsISelectionController::SetCaretEnabled(false) failed, but ignored");
5335 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager();
5336 if (NS_WARN_IF(!focusManager)) {
5337 return NS_ERROR_NOT_INITIALIZED;
5339 // TODO: Running script from here makes harder to handle blur events. We
5340 // should do this asynchronously.
5341 focusManager->UpdateCaretForCaretBrowsingMode();
5342 if (nsCOMPtr<nsINode> node = do_QueryInterface(GetDOMEventTarget())) {
5343 if (node->OwnerDoc()->GetUnretargetedFocusedContent() != node) {
5344 selectionController->SelectionWillLoseFocus();
5347 return NS_OK;
5350 Element* EditorBase::GetExposedRoot() const {
5351 Element* rootElement = GetRoot();
5352 if (!rootElement || !rootElement->IsInNativeAnonymousSubtree()) {
5353 return rootElement;
5355 return Element::FromNodeOrNull(
5356 rootElement->GetClosestNativeAnonymousSubtreeRootParent());
5359 nsresult EditorBase::DetermineCurrentDirection() {
5360 // Get the current root direction from its frame
5361 Element* rootElement = GetExposedRoot();
5362 if (NS_WARN_IF(!rootElement)) {
5363 return NS_ERROR_FAILURE;
5366 // If we don't have an explicit direction, determine our direction
5367 // from the content's direction
5368 if (!IsRightToLeft() && !IsLeftToRight()) {
5369 nsIFrame* frameForRootElement = rootElement->GetPrimaryFrame();
5370 if (NS_WARN_IF(!frameForRootElement)) {
5371 return NS_ERROR_FAILURE;
5374 // Set the flag here, to enable us to use the same code path below.
5375 // It will be flipped before returning from the function.
5376 if (frameForRootElement->StyleVisibility()->mDirection ==
5377 StyleDirection::Rtl) {
5378 mFlags |= nsIEditor::eEditorRightToLeft;
5379 } else {
5380 mFlags |= nsIEditor::eEditorLeftToRight;
5384 return NS_OK;
5387 nsresult EditorBase::ToggleTextDirectionAsAction(nsIPrincipal* aPrincipal) {
5388 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection,
5389 aPrincipal);
5390 if (NS_WARN_IF(!editActionData.CanHandle())) {
5391 return NS_ERROR_NOT_INITIALIZED;
5394 nsresult rv = DetermineCurrentDirection();
5395 if (NS_FAILED(rv)) {
5396 NS_WARNING("EditorBase::DetermineCurrentDirection() failed");
5397 return EditorBase::ToGenericNSResult(rv);
5400 MOZ_ASSERT(IsRightToLeft() || IsLeftToRight());
5401 // Note that we need to consider new direction before dispatching
5402 // "beforeinput" event since "beforeinput" event listener may change it
5403 // but not canceled.
5404 TextDirection newDirection =
5405 IsRightToLeft() ? TextDirection::eLTR : TextDirection::eRTL;
5406 editActionData.SetData(IsRightToLeft() ? u"ltr"_ns : u"rtl"_ns);
5408 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but
5409 // dispatches input event.
5410 rv = editActionData.MaybeDispatchBeforeInputEvent();
5411 if (NS_FAILED(rv)) {
5412 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5413 "MaybeDispatchBeforeInputEvent() failed");
5414 return EditorBase::ToGenericNSResult(rv);
5417 rv = SetTextDirectionTo(newDirection);
5418 if (NS_FAILED(rv)) {
5419 NS_WARNING("EditorBase::SetTextDirectionTo() failed");
5420 return EditorBase::ToGenericNSResult(rv);
5423 editActionData.MarkAsHandled();
5425 // XXX When we don't change the text direction, do we really need to
5426 // dispatch input event?
5427 DispatchInputEvent();
5429 return NS_OK;
5432 void EditorBase::SwitchTextDirectionTo(TextDirection aTextDirection) {
5433 MOZ_ASSERT(aTextDirection == TextDirection::eLTR ||
5434 aTextDirection == TextDirection::eRTL);
5436 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection);
5437 if (NS_WARN_IF(!editActionData.CanHandle())) {
5438 return;
5441 nsresult rv = DetermineCurrentDirection();
5442 if (NS_WARN_IF(NS_FAILED(rv))) {
5443 return;
5446 editActionData.SetData(aTextDirection == TextDirection::eLTR ? u"ltr"_ns
5447 : u"rtl"_ns);
5449 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but
5450 // dispatches input event.
5451 rv = editActionData.MaybeDispatchBeforeInputEvent();
5452 if (NS_FAILED(rv)) {
5453 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5454 "MaybeDispatchBeforeInputEvent() failed");
5455 return;
5458 if ((aTextDirection == TextDirection::eLTR && IsRightToLeft()) ||
5459 (aTextDirection == TextDirection::eRTL && IsLeftToRight())) {
5460 // Do it only when the direction is still different from the original
5461 // new direction. Note that "beforeinput" event listener may have already
5462 // changed the direction here, but they may not cancel the event.
5463 nsresult rv = SetTextDirectionTo(aTextDirection);
5464 if (NS_FAILED(rv)) {
5465 NS_WARNING("EditorBase::SetTextDirectionTo() failed");
5466 return;
5470 editActionData.MarkAsHandled();
5472 // XXX When we don't change the text direction, do we really need to
5473 // dispatch input event?
5474 DispatchInputEvent();
5477 nsresult EditorBase::SetTextDirectionTo(TextDirection aTextDirection) {
5478 Element* rootElement = GetExposedRoot();
5480 if (aTextDirection == TextDirection::eLTR) {
5481 NS_ASSERTION(!IsLeftToRight(), "Unexpected mutually exclusive flag");
5482 mFlags &= ~nsIEditor::eEditorRightToLeft;
5483 mFlags |= nsIEditor::eEditorLeftToRight;
5484 nsresult rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
5485 u"ltr"_ns, true);
5486 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5487 "Element::SetAttr(nsGkAtoms::dir, ltr) failed");
5488 return rv;
5491 if (aTextDirection == TextDirection::eRTL) {
5492 NS_ASSERTION(!IsRightToLeft(), "Unexpected mutually exclusive flag");
5493 mFlags |= nsIEditor::eEditorRightToLeft;
5494 mFlags &= ~nsIEditor::eEditorLeftToRight;
5495 nsresult rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
5496 u"rtl"_ns, true);
5497 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5498 "Element::SetAttr(nsGkAtoms::dir, rtl) failed");
5499 return rv;
5502 return NS_OK;
5505 Element* EditorBase::GetFocusedElement() const {
5506 EventTarget* eventTarget = GetDOMEventTarget();
5507 if (!eventTarget) {
5508 return nullptr;
5511 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5512 if (NS_WARN_IF(!focusManager)) {
5513 return nullptr;
5516 Element* focusedElement = focusManager->GetFocusedElement();
5517 MOZ_ASSERT((focusedElement == eventTarget) ==
5518 SameCOMIdentity(focusedElement, eventTarget));
5520 return (focusedElement == eventTarget) ? focusedElement : nullptr;
5523 bool EditorBase::IsActiveInDOMWindow() const {
5524 EventTarget* piTarget = GetDOMEventTarget();
5525 if (!piTarget) {
5526 return false;
5529 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5530 if (NS_WARN_IF(!focusManager)) {
5531 return false; // Do we need to check the singleton instance??
5534 Document* document = GetDocument();
5535 if (NS_WARN_IF(!document)) {
5536 return false;
5538 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
5539 nsCOMPtr<nsPIDOMWindowOuter> win;
5540 nsIContent* content = nsFocusManager::GetFocusedDescendant(
5541 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win));
5542 return SameCOMIdentity(content, piTarget);
5545 bool EditorBase::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const {
5546 // If the event is trusted, the event should always cause input.
5547 if (NS_WARN_IF(!aGUIEvent)) {
5548 return false;
5551 // If this is dispatched by using cordinates but this editor doesn't have
5552 // focus, we shouldn't handle it.
5553 if (aGUIEvent->IsUsingCoordinates() && !GetFocusedElement()) {
5554 return false;
5557 // If a composition event isn't dispatched via widget, we need to ignore them
5558 // since they cannot be managed by TextComposition. E.g., the event was
5559 // created by chrome JS.
5560 // Note that if we allow to handle such events, editor may be confused by
5561 // strange event order.
5562 bool needsWidget = false;
5563 switch (aGUIEvent->mMessage) {
5564 case eUnidentifiedEvent:
5565 // If events are not created with proper event interface, their message
5566 // are initialized with eUnidentifiedEvent. Let's ignore such event.
5567 return false;
5568 case eCompositionStart:
5569 case eCompositionEnd:
5570 case eCompositionUpdate:
5571 case eCompositionChange:
5572 case eCompositionCommitAsIs:
5573 // Don't allow composition events whose internal event are not
5574 // WidgetCompositionEvent.
5575 if (!aGUIEvent->AsCompositionEvent()) {
5576 return false;
5578 needsWidget = true;
5579 break;
5580 default:
5581 break;
5583 if (needsWidget && !aGUIEvent->mWidget) {
5584 return false;
5587 // Accept all trusted events.
5588 if (aGUIEvent->IsTrusted()) {
5589 return true;
5592 // Ignore untrusted mouse event.
5593 // XXX Why are we handling other untrusted input events?
5594 if (aGUIEvent->AsMouseEventBase()) {
5595 return false;
5598 // Otherwise, we shouldn't handle any input events when we're not an active
5599 // element of the DOM window.
5600 return IsActiveInDOMWindow();
5603 nsresult EditorBase::FlushPendingSpellCheck() {
5604 // If the spell check skip flag is still enabled from creation time,
5605 // disable it because focused editors are allowed to spell check.
5606 if (!ShouldSkipSpellCheck()) {
5607 return NS_OK;
5609 MOZ_ASSERT(!IsHTMLEditor(), "HTMLEditor should not has pending spell checks");
5610 nsresult rv = RemoveFlags(nsIEditor::eEditorSkipSpellCheck);
5611 if (NS_WARN_IF(Destroyed())) {
5612 return NS_ERROR_EDITOR_DESTROYED;
5614 NS_WARNING_ASSERTION(
5615 NS_SUCCEEDED(rv),
5616 "EditorBase::RemoveFlags(nsIEditor::eEditorSkipSpellCheck) failed");
5617 return rv;
5620 bool EditorBase::CanKeepHandlingFocusEvent(
5621 const nsINode& aOriginalEventTargetNode) const {
5622 if (MOZ_UNLIKELY(!IsListeningToEvents() || Destroyed())) {
5623 return false;
5626 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5627 if (MOZ_UNLIKELY(!focusManager)) {
5628 return false;
5631 // If the event target is document mode, we only need to handle the focus
5632 // event when the document is still in designMode. Otherwise, the
5633 // mode has been disabled by somebody while we're handling the focus event.
5634 if (aOriginalEventTargetNode.IsDocument()) {
5635 return IsHTMLEditor() && aOriginalEventTargetNode.IsInDesignMode();
5637 MOZ_ASSERT(aOriginalEventTargetNode.IsContent());
5639 // If nobody has focus, the focus event target has been blurred by somebody
5640 // else. So the editor shouldn't initialize itself to start to handle
5641 // anything.
5642 if (!focusManager->GetFocusedElement()) {
5643 return false;
5645 const nsIContent* exposedTargetContent =
5646 aOriginalEventTargetNode.AsContent()
5647 ->FindFirstNonChromeOnlyAccessContent();
5648 const nsIContent* exposedFocusedContent =
5649 focusManager->GetFocusedElement()->FindFirstNonChromeOnlyAccessContent();
5650 return exposedTargetContent && exposedFocusedContent &&
5651 exposedTargetContent == exposedFocusedContent;
5654 nsresult EditorBase::OnFocus(const nsINode& aOriginalEventTargetNode) {
5655 MOZ_ASSERT(IsEditActionDataAvailable());
5657 InitializeSelection(aOriginalEventTargetNode);
5658 mSpellCheckerDictionaryUpdated = false;
5659 if (mInlineSpellChecker && CanEnableSpellCheck()) {
5660 DebugOnly<nsresult> rvIgnored =
5661 mInlineSpellChecker->UpdateCurrentDictionary();
5662 NS_WARNING_ASSERTION(
5663 NS_SUCCEEDED(rvIgnored),
5664 "mozInlineSpellCHecker::UpdateCurrentDictionary() failed, but ignored");
5665 mSpellCheckerDictionaryUpdated = true;
5667 // XXX Why don't we stop handling focus with the spell checker immediately
5668 // after calling InitializeSelection?
5669 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) {
5670 return NS_ERROR_EDITOR_DESTROYED;
5673 RefPtr<nsPresContext> presContext = GetPresContext();
5674 if (NS_WARN_IF(!presContext)) {
5675 return NS_ERROR_FAILURE;
5677 RefPtr<Element> focusedElement = GetFocusedElement();
5678 IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this);
5680 return NS_OK;
5683 void EditorBase::HideCaret(bool aHide) {
5684 if (mHidingCaret == aHide) {
5685 return;
5688 RefPtr<nsCaret> caret = GetCaret();
5689 if (NS_WARN_IF(!caret)) {
5690 return;
5693 mHidingCaret = aHide;
5694 if (aHide) {
5695 caret->AddForceHide();
5696 } else {
5697 caret->RemoveForceHide();
5701 NS_IMETHODIMP EditorBase::Unmask(uint32_t aStart, int64_t aEnd,
5702 uint32_t aTimeout, uint8_t aArgc) {
5703 if (NS_WARN_IF(!IsPasswordEditor())) {
5704 return NS_ERROR_NOT_AVAILABLE;
5706 if (NS_WARN_IF(aArgc >= 1 && aStart == UINT32_MAX) ||
5707 NS_WARN_IF(aArgc >= 2 && aEnd == 0) ||
5708 NS_WARN_IF(aArgc >= 2 && aEnd > 0 && aStart >= aEnd)) {
5709 return NS_ERROR_INVALID_ARG;
5712 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
5713 if (NS_WARN_IF(!editActionData.CanHandle())) {
5714 return NS_ERROR_NOT_INITIALIZED;
5717 uint32_t start = aArgc < 1 ? 0 : aStart;
5718 uint32_t length = aArgc < 2 || aEnd < 0 ? UINT32_MAX : aEnd - start;
5719 uint32_t timeout = aArgc < 3 ? 0 : aTimeout;
5720 nsresult rv = MOZ_KnownLive(AsTextEditor())
5721 ->SetUnmaskRangeAndNotify(start, length, timeout);
5722 if (NS_FAILED(rv)) {
5723 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed");
5724 return EditorBase::ToGenericNSResult(rv);
5727 // Flush pending layout right now since the caller may access us before
5728 // doing it.
5729 if (RefPtr<PresShell> presShell = GetPresShell()) {
5730 presShell->FlushPendingNotifications(FlushType::Layout);
5733 return NS_OK;
5736 NS_IMETHODIMP EditorBase::Mask() {
5737 if (NS_WARN_IF(!IsPasswordEditor())) {
5738 return NS_ERROR_NOT_AVAILABLE;
5741 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword);
5742 if (NS_WARN_IF(!editActionData.CanHandle())) {
5743 return NS_ERROR_NOT_INITIALIZED;
5746 nsresult rv = MOZ_KnownLive(AsTextEditor())->MaskAllCharactersAndNotify();
5747 if (NS_FAILED(rv)) {
5748 NS_WARNING("TextEditor::MaskAllCharactersAndNotify() failed");
5749 return EditorBase::ToGenericNSResult(rv);
5752 // Flush pending layout right now since the caller may access us before
5753 // doing it.
5754 if (RefPtr<PresShell> presShell = GetPresShell()) {
5755 presShell->FlushPendingNotifications(FlushType::Layout);
5758 return NS_OK;
5761 NS_IMETHODIMP EditorBase::GetUnmaskedStart(uint32_t* aResult) {
5762 if (NS_WARN_IF(!IsPasswordEditor())) {
5763 *aResult = 0;
5764 return NS_ERROR_NOT_AVAILABLE;
5766 *aResult =
5767 AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedStart();
5768 return NS_OK;
5771 NS_IMETHODIMP EditorBase::GetUnmaskedEnd(uint32_t* aResult) {
5772 if (NS_WARN_IF(!IsPasswordEditor())) {
5773 *aResult = 0;
5774 return NS_ERROR_NOT_AVAILABLE;
5776 *aResult = AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedEnd();
5777 return NS_OK;
5780 NS_IMETHODIMP EditorBase::GetAutoMaskingEnabled(bool* aResult) {
5781 if (NS_WARN_IF(!IsPasswordEditor())) {
5782 *aResult = false;
5783 return NS_ERROR_NOT_AVAILABLE;
5785 *aResult = AsTextEditor()->IsMaskingPassword();
5786 return NS_OK;
5789 NS_IMETHODIMP EditorBase::GetPasswordMask(nsAString& aPasswordMask) {
5790 aPasswordMask.Assign(TextEditor::PasswordMask());
5791 return NS_OK;
5794 template <typename PT, typename CT>
5795 EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager(
5796 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount,
5797 const EditorDOMPointBase<PT, CT>& aPointAtCaret) {
5798 MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable());
5800 nsPresContext* presContext = aEditorBase.GetPresContext();
5801 if (NS_WARN_IF(!presContext)) {
5802 mFailed = true;
5803 return;
5806 if (!presContext->BidiEnabled()) {
5807 return; // Perform the deletion
5810 if (!aPointAtCaret.GetContainerAsContent()) {
5811 mFailed = true;
5812 return;
5815 // XXX Not sure whether this requires strong reference here.
5816 RefPtr<nsFrameSelection> frameSelection =
5817 aEditorBase.SelectionRef().GetFrameSelection();
5818 if (NS_WARN_IF(!frameSelection)) {
5819 mFailed = true;
5820 return;
5823 nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels(
5824 aPointAtCaret.GetContainerAsContent(), aPointAtCaret.Offset(), true);
5826 mozilla::intl::BidiEmbeddingLevel levelBefore = levels.mLevelBefore;
5827 mozilla::intl::BidiEmbeddingLevel levelAfter = levels.mLevelAfter;
5829 mozilla::intl::BidiEmbeddingLevel currentCaretLevel =
5830 frameSelection->GetCaretBidiLevel();
5832 mozilla::intl::BidiEmbeddingLevel levelOfDeletion;
5833 levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount ||
5834 nsIEditor::eNextWord == aDirectionAndAmount)
5835 ? levelAfter
5836 : levelBefore;
5838 if (currentCaretLevel == levelOfDeletion) {
5839 return; // Perform the deletion
5842 // Set the bidi level of the caret to that of the
5843 // character that will be (or would have been) deleted
5844 mNewCaretBidiLevel = Some(levelOfDeletion);
5845 mCanceled =
5846 !StaticPrefs::bidi_edit_delete_immediately() && levelBefore != levelAfter;
5849 void EditorBase::AutoCaretBidiLevelManager::MaybeUpdateCaretBidiLevel(
5850 const EditorBase& aEditorBase) const {
5851 MOZ_ASSERT(!mFailed);
5852 if (mNewCaretBidiLevel.isNothing()) {
5853 return;
5855 RefPtr<nsFrameSelection> frameSelection =
5856 aEditorBase.SelectionRef().GetFrameSelection();
5857 MOZ_ASSERT(frameSelection);
5858 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint(
5859 mNewCaretBidiLevel.value());
5862 void EditorBase::UndefineCaretBidiLevel() const {
5863 MOZ_ASSERT(IsEditActionDataAvailable());
5866 * After inserting text the caret Bidi level must be set to the level of the
5867 * inserted text.This is difficult, because we cannot know what the level is
5868 * until after the Bidi algorithm is applied to the whole paragraph.
5870 * So we set the caret Bidi level to UNDEFINED here, and the caret code will
5871 * set it correctly later
5873 nsFrameSelection* frameSelection = SelectionRef().GetFrameSelection();
5874 if (frameSelection) {
5875 frameSelection->UndefineCaretBidiLevel();
5879 NS_IMETHODIMP EditorBase::GetTextLength(uint32_t* aCount) {
5880 return NS_ERROR_NOT_IMPLEMENTED;
5883 NS_IMETHODIMP EditorBase::GetWrapWidth(int32_t* aWrapColumn) {
5884 if (NS_WARN_IF(!aWrapColumn)) {
5885 return NS_ERROR_INVALID_ARG;
5887 *aWrapColumn = WrapWidth();
5888 return NS_OK;
5892 // See if the style value includes this attribute, and if it does,
5893 // cut out everything from the attribute to the next semicolon.
5895 static void CutStyle(const char* stylename, nsString& styleValue) {
5896 // Find the current wrapping type:
5897 int32_t styleStart = styleValue.Find(stylename, true);
5898 if (styleStart >= 0) {
5899 int32_t styleEnd = styleValue.Find(";", false, styleStart);
5900 if (styleEnd > styleStart) {
5901 styleValue.Cut(styleStart, styleEnd - styleStart + 1);
5902 } else {
5903 styleValue.Cut(styleStart, styleValue.Length() - styleStart);
5908 NS_IMETHODIMP EditorBase::SetWrapWidth(int32_t aWrapColumn) {
5909 AutoEditActionDataSetter editActionData(*this, EditAction::eSetWrapWidth);
5910 if (NS_WARN_IF(!editActionData.CanHandle())) {
5911 return NS_ERROR_NOT_INITIALIZED;
5914 SetWrapColumn(aWrapColumn);
5916 // Make sure we're a plaintext editor, otherwise we shouldn't
5917 // do the rest of this.
5918 if (!IsInPlaintextMode()) {
5919 return NS_OK;
5922 // Ought to set a style sheet here ...
5923 // Probably should keep around an mPlaintextStyleSheet for this purpose.
5924 RefPtr<Element> rootElement = GetRoot();
5925 if (NS_WARN_IF(!rootElement)) {
5926 return NS_ERROR_NOT_INITIALIZED;
5929 // Get the current style for this root element:
5930 nsAutoString styleValue;
5931 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue);
5933 // We'll replace styles for these values:
5934 CutStyle("white-space", styleValue);
5935 CutStyle("width", styleValue);
5936 CutStyle("font-family", styleValue);
5938 // If we have other style left, trim off any existing semicolons
5939 // or white-space, then add a known semicolon-space:
5940 if (!styleValue.IsEmpty()) {
5941 styleValue.Trim("; \t", false, true);
5942 styleValue.AppendLiteral("; ");
5945 // Make sure we have fixed-width font. This should be done for us,
5946 // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
5947 // Only do this if we're wrapping.
5948 if (IsWrapHackEnabled() && aWrapColumn >= 0) {
5949 styleValue.AppendLiteral("font-family: -moz-fixed; ");
5952 // and now we're ready to set the new white-space/wrapping style.
5953 if (aWrapColumn > 0) {
5954 // Wrap to a fixed column.
5955 styleValue.AppendLiteral("white-space: pre-wrap; width: ");
5956 styleValue.AppendInt(aWrapColumn);
5957 styleValue.AppendLiteral("ch;");
5958 } else if (!aWrapColumn) {
5959 styleValue.AppendLiteral("white-space: pre-wrap;");
5960 } else {
5961 styleValue.AppendLiteral("white-space: pre;");
5964 nsresult rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
5965 styleValue, true);
5966 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5967 "Element::SetAttr(nsGkAtoms::style) failed");
5968 return rv;
5971 NS_IMETHODIMP EditorBase::GetNewlineHandling(int32_t* aNewlineHandling) {
5972 if (NS_WARN_IF(!aNewlineHandling)) {
5973 return NS_ERROR_INVALID_ARG;
5975 *aNewlineHandling = mNewlineHandling;
5976 return NS_OK;
5979 NS_IMETHODIMP EditorBase::SetNewlineHandling(int32_t aNewlineHandling) {
5980 switch (aNewlineHandling) {
5981 case nsIEditor::eNewlinesPasteIntact:
5982 case nsIEditor::eNewlinesPasteToFirst:
5983 case nsIEditor::eNewlinesReplaceWithSpaces:
5984 case nsIEditor::eNewlinesStrip:
5985 case nsIEditor::eNewlinesReplaceWithCommas:
5986 case nsIEditor::eNewlinesStripSurroundingWhitespace:
5987 mNewlineHandling = aNewlineHandling;
5988 return NS_OK;
5989 default:
5990 NS_ERROR("SetNewlineHandling() is called with wrong value");
5991 return NS_ERROR_INVALID_ARG;
5995 bool EditorBase::IsSelectionRangeContainerNotContent() const {
5996 MOZ_ASSERT(IsEditActionDataAvailable());
5998 const uint32_t rangeCount = SelectionRef().RangeCount();
5999 for (const uint32_t i : IntegerRange(rangeCount)) {
6000 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
6001 const nsRange* range = SelectionRef().GetRangeAt(i);
6002 MOZ_ASSERT(range);
6003 if (MOZ_UNLIKELY(!range) || MOZ_UNLIKELY(!range->GetStartContainer()) ||
6004 MOZ_UNLIKELY(!range->GetStartContainer()->IsContent()) ||
6005 MOZ_UNLIKELY(!range->GetEndContainer()) ||
6006 MOZ_UNLIKELY(!range->GetEndContainer()->IsContent())) {
6007 return true;
6010 return false;
6013 NS_IMETHODIMP EditorBase::InsertText(const nsAString& aStringToInsert) {
6014 nsresult rv = InsertTextAsAction(aStringToInsert);
6015 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6016 "EditorBase::InsertTextAsAction() failed");
6017 return rv;
6020 nsresult EditorBase::InsertTextAsAction(const nsAString& aStringToInsert,
6021 nsIPrincipal* aPrincipal) {
6022 // Showing this assertion is fine if this method is called by outside via
6023 // mutation event listener or something. Otherwise, this is called by
6024 // wrong method.
6025 NS_ASSERTION(!mPlaceholderBatch,
6026 "Should be called only when this is the only edit action of the "
6027 "operation "
6028 "unless mutation event listener nests some operations");
6030 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText,
6031 aPrincipal);
6032 // Note that we don't need to replace native line breaks with XP line breaks
6033 // here because Chrome does not do it.
6034 MOZ_ASSERT(!aStringToInsert.IsVoid());
6035 editActionData.SetData(aStringToInsert);
6036 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
6037 if (NS_FAILED(rv)) {
6038 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
6039 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
6040 return EditorBase::ToGenericNSResult(rv);
6043 nsString stringToInsert(aStringToInsert);
6044 if (IsTextEditor()) {
6045 nsContentUtils::PlatformToDOMLineBreaks(stringToInsert);
6047 AutoPlaceholderBatch treatAsOneTransaction(
6048 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6049 rv = InsertTextAsSubAction(stringToInsert, SelectionHandling::Delete);
6050 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6051 "EditorBase::InsertTextAsSubAction() failed");
6052 return EditorBase::ToGenericNSResult(rv);
6055 nsresult EditorBase::InsertTextAsSubAction(
6056 const nsAString& aStringToInsert, SelectionHandling aSelectionHandling) {
6057 MOZ_ASSERT(IsEditActionDataAvailable());
6058 MOZ_ASSERT(mPlaceholderBatch);
6059 MOZ_ASSERT(IsHTMLEditor() ||
6060 aStringToInsert.FindChar(nsCRT::CR) == kNotFound);
6061 MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore, mComposition);
6063 if (NS_WARN_IF(!mInitSucceeded)) {
6064 return NS_ERROR_NOT_INITIALIZED;
6067 if (NS_WARN_IF(Destroyed())) {
6068 return NS_ERROR_EDITOR_DESTROYED;
6071 EditSubAction editSubAction = ShouldHandleIMEComposition()
6072 ? EditSubAction::eInsertTextComingFromIME
6073 : EditSubAction::eInsertText;
6075 IgnoredErrorResult ignoredError;
6076 AutoEditSubActionNotifier startToHandleEditSubAction(
6077 *this, editSubAction, nsIEditor::eNext, ignoredError);
6078 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6079 return ignoredError.StealNSResult();
6081 NS_WARNING_ASSERTION(
6082 !ignoredError.Failed(),
6083 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
6085 EditActionResult result =
6086 HandleInsertText(editSubAction, aStringToInsert, aSelectionHandling);
6087 NS_WARNING_ASSERTION(result.Succeeded(),
6088 "EditorBase::HandleInsertText() failed");
6089 return result.Rv();
6092 NS_IMETHODIMP EditorBase::InsertLineBreak() { return NS_ERROR_NOT_IMPLEMENTED; }
6094 /*****************************************************************************
6095 * mozilla::EditorBase::AutoEditActionDataSetter
6096 *****************************************************************************/
6098 EditorBase::AutoEditActionDataSetter::AutoEditActionDataSetter(
6099 const EditorBase& aEditorBase, EditAction aEditAction,
6100 nsIPrincipal* aPrincipal /* = nullptr */)
6101 : mEditorBase(const_cast<EditorBase&>(aEditorBase)),
6102 mPrincipal(aPrincipal),
6103 mParentData(aEditorBase.mEditActionData),
6104 mData(VoidString()),
6105 mRawEditAction(aEditAction),
6106 mTopLevelEditSubAction(EditSubAction::eNone),
6107 mAborted(false),
6108 mHasTriedToDispatchBeforeInputEvent(false),
6109 mBeforeInputEventCanceled(false),
6110 mMakeBeforeInputEventNonCancelable(false),
6111 mHasTriedToDispatchClipboardEvent(false),
6112 mEditorWasDestroyedDuringHandlingEditAction(
6113 mParentData &&
6114 mParentData->mEditorWasDestroyedDuringHandlingEditAction),
6115 mHandled(false) {
6116 // If we're nested edit action, copies necessary data from the parent.
6117 if (mParentData) {
6118 mSelection = mParentData->mSelection;
6119 MOZ_ASSERT(!mSelection ||
6120 (mSelection->GetType() == SelectionType::eNormal));
6122 // If we're not editing something, we should inherit the parent's edit
6123 // action. This may occur if creator or its callee use public methods which
6124 // just returns something.
6125 if (IsEditActionInOrderToEditSomething(aEditAction)) {
6126 mEditAction = aEditAction;
6127 } else {
6128 mEditAction = mParentData->mEditAction;
6129 // If we inherit an edit action whose handler needs to dispatch a
6130 // clipboard event, we should inherit the clipboard dispatching state
6131 // too because this nest occurs by a clipboard event listener or
6132 // a beforeinput/mutation event listener is important for checking
6133 // whether we've already called `MaybeDispatchBeforeInputEvent()`
6134 // property in some points. If the former case, not yet dispatching
6135 // beforeinput event is okay (not fine).
6136 mHasTriedToDispatchClipboardEvent =
6137 mParentData->mHasTriedToDispatchClipboardEvent;
6139 mTopLevelEditSubAction = mParentData->mTopLevelEditSubAction;
6141 // Parent's mTopLevelEditSubActionData should be referred instead so that
6142 // we don't need to set mTopLevelEditSubActionData.mSelectedRange nor
6143 // mTopLevelEditActionData.mChangedRange here.
6145 mDirectionOfTopLevelEditSubAction =
6146 mParentData->mDirectionOfTopLevelEditSubAction;
6147 } else {
6148 mSelection = mEditorBase.GetSelection();
6149 if (NS_WARN_IF(!mSelection)) {
6150 return;
6153 MOZ_ASSERT(mSelection->GetType() == SelectionType::eNormal);
6155 mEditAction = aEditAction;
6156 mDirectionOfTopLevelEditSubAction = eNone;
6157 if (mEditorBase.IsHTMLEditor()) {
6158 mTopLevelEditSubActionData.mSelectedRange =
6159 mEditorBase.AsHTMLEditor()
6160 ->GetSelectedRangeItemForTopLevelEditSubAction();
6161 mTopLevelEditSubActionData.mChangedRange =
6162 mEditorBase.AsHTMLEditor()->GetChangedRangeForTopLevelEditSubAction();
6163 mTopLevelEditSubActionData.mCachedInlineStyles.emplace();
6166 mEditorBase.mEditActionData = this;
6169 EditorBase::AutoEditActionDataSetter::~AutoEditActionDataSetter() {
6170 MOZ_ASSERT(mHasCanHandleChecked);
6172 if (!mSelection || NS_WARN_IF(mEditorBase.mEditActionData != this)) {
6173 return;
6175 mEditorBase.mEditActionData = mParentData;
6177 MOZ_ASSERT(
6178 !mTopLevelEditSubActionData.mSelectedRange ||
6179 (!mTopLevelEditSubActionData.mSelectedRange->mStartContainer &&
6180 !mTopLevelEditSubActionData.mSelectedRange->mEndContainer),
6181 "mTopLevelEditSubActionData.mSelectedRange should've been cleared");
6184 void EditorBase::AutoEditActionDataSetter::UpdateSelectionCache(
6185 Selection& aSelection) {
6186 MOZ_ASSERT(aSelection.GetType() == SelectionType::eNormal);
6188 if (mSelection == &aSelection) {
6189 return;
6192 AutoEditActionDataSetter& topLevelEditActionData =
6193 [&]() -> AutoEditActionDataSetter& {
6194 for (AutoEditActionDataSetter* editActionData = this;;
6195 editActionData = editActionData->mParentData) {
6196 if (!editActionData->mParentData) {
6197 return *editActionData;
6200 MOZ_ASSERT_UNREACHABLE("You do something wrong");
6201 }();
6203 // Keep grabbing the old selection in the top level edit action data until the
6204 // all owners end handling it.
6205 if (mSelection) {
6206 topLevelEditActionData.mRetiredSelections.AppendElement(*mSelection);
6209 // If the old selection is in batch, we should end the batch which
6210 // `EditorBase::BeginUpdateViewBatch` started.
6211 if (mEditorBase.mUpdateCount && mSelection) {
6212 mSelection->EndBatchChanges(__FUNCTION__);
6215 Selection* previousSelection = mSelection;
6216 mSelection = &aSelection;
6217 for (AutoEditActionDataSetter* parentActionData = mParentData;
6218 parentActionData; parentActionData = parentActionData->mParentData) {
6219 if (!parentActionData->mSelection) {
6220 continue;
6222 // Skip scanning mRetiredSelections if we've already handled the selection
6223 // previous time.
6224 if (parentActionData->mSelection != previousSelection) {
6225 if (!topLevelEditActionData.mRetiredSelections.Contains(
6226 OwningNonNull<Selection>(*parentActionData->mSelection))) {
6227 topLevelEditActionData.mRetiredSelections.AppendElement(
6228 *parentActionData->mSelection);
6230 previousSelection = parentActionData->mSelection;
6232 parentActionData->mSelection = &aSelection;
6235 // Restart the batching in the new selection.
6236 if (mEditorBase.mUpdateCount) {
6237 aSelection.StartBatchChanges(__FUNCTION__);
6241 void EditorBase::AutoEditActionDataSetter::SetColorData(
6242 const nsAString& aData) {
6243 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6244 "It's too late to set data since this may have already dispatched "
6245 "a beforeinput event");
6247 if (aData.IsEmpty()) {
6248 // When removing color/background-color, let's use empty string.
6249 mData.Truncate();
6250 MOZ_ASSERT(!mData.IsVoid());
6251 return;
6254 bool wasCurrentColor = false;
6255 nscolor color = NS_RGB(0, 0, 0);
6256 if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0),
6257 NS_ConvertUTF16toUTF8(aData), &color,
6258 &wasCurrentColor)) {
6259 // If we cannot parse aData, let's set original value as-is. It could be
6260 // new format defined by newer spec.
6261 MOZ_ASSERT(!aData.IsVoid());
6262 mData = aData;
6263 return;
6266 // If it's current color, we cannot resolve actual current color here.
6267 // So, let's return "currentcolor" keyword, but let's use it as-is because
6268 // there is no agreement between browser vendors.
6269 if (wasCurrentColor) {
6270 MOZ_ASSERT(!aData.IsVoid());
6271 mData = aData;
6272 return;
6275 // Get serialized color value (i.e., "rgb()" or "rgba()").
6276 nsStyleUtil::GetSerializedColorValue(color, mData);
6277 MOZ_ASSERT(!mData.IsVoid());
6280 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6281 DataTransfer* aDataTransfer) {
6282 MOZ_ASSERT(aDataTransfer);
6283 MOZ_ASSERT(aDataTransfer->IsReadOnly());
6284 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6285 "It's too late to set dataTransfer since this may have already "
6286 "dispatched a beforeinput event");
6288 mDataTransfer = aDataTransfer;
6291 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6292 nsITransferable* aTransferable) {
6293 MOZ_ASSERT(aTransferable);
6294 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6295 "It's too late to set dataTransfer since this may have already "
6296 "dispatched a beforeinput event");
6298 Document* document = mEditorBase.GetDocument();
6299 nsIGlobalObject* scopeObject =
6300 document ? document->GetScopeObject() : nullptr;
6301 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aTransferable);
6304 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer(
6305 const nsAString& aString) {
6306 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6307 "It's too late to set dataTransfer since this may have already "
6308 "dispatched a beforeinput event");
6309 Document* document = mEditorBase.GetDocument();
6310 nsIGlobalObject* scopeObject =
6311 document ? document->GetScopeObject() : nullptr;
6312 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aString);
6315 void EditorBase::AutoEditActionDataSetter::InitializeDataTransferWithClipboard(
6316 SettingDataTransfer aSettingDataTransfer, int32_t aClipboardType) {
6317 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6318 "It's too late to set dataTransfer since this may have already "
6319 "dispatched a beforeinput event");
6321 Document* document = mEditorBase.GetDocument();
6322 nsIGlobalObject* scopeObject =
6323 document ? document->GetScopeObject() : nullptr;
6324 // mDataTransfer will be used for eEditorInput event, but we can keep
6325 // using ePaste and ePasteNoFormatting here. If we need to use eEditorInput,
6326 // we need to create eEditorInputNoFormatting or something...
6327 mDataTransfer =
6328 new DataTransfer(scopeObject,
6329 aSettingDataTransfer == SettingDataTransfer::eWithFormat
6330 ? ePaste
6331 : ePasteNoFormatting,
6332 true /* is external */, aClipboardType);
6335 void EditorBase::AutoEditActionDataSetter::AppendTargetRange(
6336 StaticRange& aTargetRange) {
6337 mTargetRanges.AppendElement(aTargetRange);
6340 bool EditorBase::AutoEditActionDataSetter::IsBeforeInputEventEnabled() const {
6341 if (!StaticPrefs::dom_input_events_beforeinput_enabled()) {
6342 return false;
6345 // Don't dispatch "beforeinput" event when the editor user makes us stop
6346 // dispatching input event.
6347 if (mEditorBase.IsSuppressingDispatchingInputEvent()) {
6348 return false;
6351 // If mPrincipal has set, it means that we're handling an edit action
6352 // which is requested by JS. If it's not chrome script, we shouldn't
6353 // dispatch "beforeinput" event.
6354 if (mPrincipal && !mPrincipal->IsSystemPrincipal()) {
6355 // But if it's content script of an addon, `execCommand` calls are a
6356 // part of browser's default action from point of view of web apps.
6357 // Therefore, we should dispatch `beforeinput` event.
6358 // https://github.com/w3c/input-events/issues/91
6359 if (!mPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
6360 return false;
6364 return true;
6367 nsresult EditorBase::AutoEditActionDataSetter::MaybeFlushPendingNotifications()
6368 const {
6369 MOZ_ASSERT(CanHandle());
6370 if (!MayEditActionRequireLayout(mRawEditAction)) {
6371 return NS_SUCCESS_DOM_NO_OPERATION;
6373 OwningNonNull<EditorBase> editorBase = mEditorBase;
6374 RefPtr<PresShell> presShell = editorBase->GetPresShell();
6375 if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) {
6376 return NS_ERROR_NOT_AVAILABLE;
6378 presShell->FlushPendingNotifications(FlushType::Layout);
6379 if (MOZ_UNLIKELY(NS_WARN_IF(editorBase->Destroyed()))) {
6380 return NS_ERROR_EDITOR_DESTROYED;
6382 return NS_OK;
6385 nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent(
6386 nsIEditor::EDirection aDeleteDirectionAndAmount /* = nsIEditor::eNone */) {
6387 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(),
6388 "We've already handled beforeinput event");
6389 MOZ_ASSERT(CanHandle());
6390 MOZ_ASSERT_IF(IsBeforeInputEventEnabled(),
6391 ShouldAlreadyHaveHandledBeforeInputEventDispatching());
6392 MOZ_ASSERT_IF(!MayEditActionDeleteAroundCollapsedSelection(mEditAction),
6393 aDeleteDirectionAndAmount == nsIEditor::eNone);
6395 mHasTriedToDispatchBeforeInputEvent = true;
6397 if (!IsBeforeInputEventEnabled()) {
6398 return NS_OK;
6401 // If we're called from OnCompositionEnd(), we shouldn't dispatch
6402 // "beforeinput" event since the preceding OnCompositionChange() call has
6403 // already dispatched "beforeinput" event for this.
6404 if (mEditAction == EditAction::eCommitComposition ||
6405 mEditAction == EditAction::eCancelComposition) {
6406 return NS_OK;
6409 RefPtr<Element> targetElement = mEditorBase.GetInputEventTargetElement();
6410 if (!targetElement) {
6411 // If selection is not in editable element and it is outside of any
6412 // editing hosts, there may be no target element to dispatch `beforeinput`
6413 // event. In this case, the caller shouldn't keep handling the edit
6414 // action since web apps cannot override it with `beforeinput` event
6415 // listener, but for backward compatibility, we should return a special
6416 // success code instead of error.
6417 return NS_OK;
6419 OwningNonNull<EditorBase> editorBase = mEditorBase;
6420 EditorInputType inputType = ToInputType(mEditAction);
6421 if (editorBase->IsHTMLEditor() && mTargetRanges.IsEmpty()) {
6422 // If the edit action will delete selected ranges, compute the range
6423 // strictly.
6424 if (MayEditActionDeleteAroundCollapsedSelection(mEditAction) ||
6425 (!editorBase->SelectionRef().IsCollapsed() &&
6426 MayEditActionDeleteSelection(mEditAction))) {
6427 if (!editorBase
6428 ->FlushPendingNotificationsIfToHandleDeletionWithFrameSelection(
6429 aDeleteDirectionAndAmount)) {
6430 NS_WARNING(
6431 "Flusing pending notifications caused destroying the editor");
6432 return NS_ERROR_EDITOR_DESTROYED;
6435 AutoRangeArray rangesToDelete(editorBase->SelectionRef());
6436 if (!rangesToDelete.Ranges().IsEmpty()) {
6437 nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor())
6438 ->ComputeTargetRanges(aDeleteDirectionAndAmount,
6439 rangesToDelete);
6440 if (rv == NS_ERROR_EDITOR_DESTROYED) {
6441 NS_WARNING("HTMLEditor::ComputeTargetRanges() destroyed the editor");
6442 return NS_ERROR_EDITOR_DESTROYED;
6444 if (rv == NS_ERROR_EDITOR_NO_EDITABLE_RANGE) {
6445 // For now, keep dispatching `beforeinput` event even if no selection
6446 // range can be editable.
6447 rv = NS_OK;
6449 NS_WARNING_ASSERTION(
6450 NS_SUCCEEDED(rv),
6451 "HTMLEditor::ComputeTargetRanges() failed, but ignored");
6452 for (auto& range : rangesToDelete.Ranges()) {
6453 RefPtr<StaticRange> staticRange =
6454 StaticRange::Create(range, IgnoreErrors());
6455 if (NS_WARN_IF(!staticRange)) {
6456 continue;
6458 AppendTargetRange(*staticRange);
6462 // Otherwise, just set target ranges to selection ranges.
6463 else if (MayHaveTargetRangesOnHTMLEditor(inputType)) {
6464 if (uint32_t rangeCount = editorBase->SelectionRef().RangeCount()) {
6465 mTargetRanges.SetCapacity(rangeCount);
6466 for (const uint32_t i : IntegerRange(rangeCount)) {
6467 MOZ_ASSERT(editorBase->SelectionRef().RangeCount() == rangeCount);
6468 const nsRange* range = editorBase->SelectionRef().GetRangeAt(i);
6469 MOZ_ASSERT(range);
6470 MOZ_ASSERT(range->IsPositioned());
6471 if (MOZ_UNLIKELY(NS_WARN_IF(!range)) ||
6472 MOZ_UNLIKELY(NS_WARN_IF(!range->IsPositioned()))) {
6473 continue;
6475 // Now, we need to fix the offset of target range because it may
6476 // be referred after modifying the DOM tree and range boundaries
6477 // of `range` may have not computed offset yet.
6478 RefPtr<StaticRange> targetRange = StaticRange::Create(
6479 range->GetStartContainer(), range->StartOffset(),
6480 range->GetEndContainer(), range->EndOffset(), IgnoreErrors());
6481 if (NS_WARN_IF(!targetRange) ||
6482 NS_WARN_IF(!targetRange->IsPositioned())) {
6483 continue;
6485 mTargetRanges.AppendElement(std::move(targetRange));
6490 nsEventStatus status = nsEventStatus_eIgnore;
6491 InputEventOptions::NeverCancelable neverCancelable =
6492 mMakeBeforeInputEventNonCancelable
6493 ? InputEventOptions::NeverCancelable::Yes
6494 : InputEventOptions::NeverCancelable::No;
6495 nsresult rv = nsContentUtils::DispatchInputEvent(
6496 targetElement, eEditorBeforeInput, inputType, editorBase,
6497 mDataTransfer
6498 ? InputEventOptions(mDataTransfer, std::move(mTargetRanges),
6499 neverCancelable)
6500 : InputEventOptions(mData, std::move(mTargetRanges), neverCancelable),
6501 &status);
6502 if (NS_WARN_IF(mEditorBase.Destroyed())) {
6503 return NS_ERROR_EDITOR_DESTROYED;
6505 if (NS_FAILED(rv)) {
6506 NS_WARNING("nsContentUtils::DispatchInputEvent() failed");
6507 return rv;
6509 mBeforeInputEventCanceled = status == nsEventStatus_eConsumeNoDefault;
6510 if (mBeforeInputEventCanceled && mEditorBase.IsHTMLEditor()) {
6511 mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true;
6513 return mBeforeInputEventCanceled ? NS_ERROR_EDITOR_ACTION_CANCELED : NS_OK;
6516 /*****************************************************************************
6517 * mozilla::EditorBase::TopLevelEditSubActionData
6518 *****************************************************************************/
6520 nsresult EditorBase::TopLevelEditSubActionData::AddNodeToChangedRange(
6521 const HTMLEditor& aHTMLEditor, nsINode& aNode) {
6522 EditorRawDOMPoint startPoint(&aNode);
6523 EditorRawDOMPoint endPoint(&aNode);
6524 DebugOnly<bool> advanced = endPoint.AdvanceOffset();
6525 NS_WARNING_ASSERTION(advanced, "Failed to set endPoint to next to aNode");
6526 nsresult rv = AddRangeToChangedRange(aHTMLEditor, startPoint, endPoint);
6527 NS_WARNING_ASSERTION(
6528 NS_SUCCEEDED(rv),
6529 "TopLevelEditSubActionData::AddRangeToChangedRange() failed");
6530 return rv;
6533 nsresult EditorBase::TopLevelEditSubActionData::AddPointToChangedRange(
6534 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aPoint) {
6535 nsresult rv = AddRangeToChangedRange(aHTMLEditor, aPoint, aPoint);
6536 NS_WARNING_ASSERTION(
6537 NS_SUCCEEDED(rv),
6538 "TopLevelEditSubActionData::AddRangeToChangedRange() failed");
6539 return rv;
6542 nsresult EditorBase::TopLevelEditSubActionData::AddRangeToChangedRange(
6543 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aStart,
6544 const EditorRawDOMPoint& aEnd) {
6545 if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet())) {
6546 return NS_ERROR_INVALID_ARG;
6549 if (!aHTMLEditor.IsDescendantOfRoot(aStart.GetContainer()) ||
6550 (aStart.GetContainer() != aEnd.GetContainer() &&
6551 !aHTMLEditor.IsDescendantOfRoot(aEnd.GetContainer()))) {
6552 return NS_OK;
6555 // If mChangedRange hasn't been set, we can just set it to `aStart` and
6556 // `aEnd`.
6557 if (!mChangedRange->IsPositioned()) {
6558 nsresult rv = mChangedRange->SetStartAndEnd(aStart.ToRawRangeBoundary(),
6559 aEnd.ToRawRangeBoundary());
6560 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
6561 return rv;
6564 Maybe<int32_t> relation =
6565 mChangedRange->StartRef().IsSet()
6566 ? nsContentUtils::ComparePoints(mChangedRange->StartRef(),
6567 aStart.ToRawRangeBoundary())
6568 : Some(1);
6569 if (NS_WARN_IF(!relation)) {
6570 return NS_ERROR_FAILURE;
6573 // If aStart is before start of mChangedRange, reset the start.
6574 if (*relation > 0) {
6575 ErrorResult error;
6576 mChangedRange->SetStart(aStart.ToRawRangeBoundary(), error);
6577 if (error.Failed()) {
6578 NS_WARNING("nsRange::SetStart() failed");
6579 return error.StealNSResult();
6583 relation = mChangedRange->EndRef().IsSet()
6584 ? nsContentUtils::ComparePoints(mChangedRange->EndRef(),
6585 aEnd.ToRawRangeBoundary())
6586 : Some(1);
6587 if (NS_WARN_IF(!relation)) {
6588 return NS_ERROR_FAILURE;
6591 // If aEnd is after end of mChangedRange, reset the end.
6592 if (*relation < 0) {
6593 ErrorResult error;
6594 mChangedRange->SetEnd(aEnd.ToRawRangeBoundary(), error);
6595 if (error.Failed()) {
6596 NS_WARNING("nsRange::SetEnd() failed");
6597 return error.StealNSResult();
6601 return NS_OK;
6604 void EditorBase::TopLevelEditSubActionData::DidCreateElement(
6605 EditorBase& aEditorBase, Element& aNewElement) {
6606 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6608 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6609 return; // We have not been initialized yet or already been destroyed.
6612 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6613 return; // Temporarily disabled by edit sub-action handler.
6616 DebugOnly<nsresult> rvIgnored =
6617 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewElement);
6618 NS_WARNING_ASSERTION(
6619 NS_SUCCEEDED(rvIgnored),
6620 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6623 void EditorBase::TopLevelEditSubActionData::DidInsertContent(
6624 EditorBase& aEditorBase, nsIContent& aNewContent) {
6625 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6627 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6628 return; // We have not been initialized yet or already been destroyed.
6631 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6632 return; // Temporarily disabled by edit sub-action handler.
6635 DebugOnly<nsresult> rvIgnored =
6636 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewContent);
6637 NS_WARNING_ASSERTION(
6638 NS_SUCCEEDED(rvIgnored),
6639 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6642 void EditorBase::TopLevelEditSubActionData::WillDeleteContent(
6643 EditorBase& aEditorBase, nsIContent& aRemovingContent) {
6644 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6646 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6647 return; // We have not been initialized yet or already been destroyed.
6650 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6651 return; // Temporarily disabled by edit sub-action handler.
6654 DebugOnly<nsresult> rvIgnored =
6655 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aRemovingContent);
6656 NS_WARNING_ASSERTION(
6657 NS_SUCCEEDED(rvIgnored),
6658 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored");
6661 void EditorBase::TopLevelEditSubActionData::DidSplitContent(
6662 EditorBase& aEditorBase, nsIContent& aSplitContent, nsIContent& aNewContent,
6663 SplitNodeDirection aSplitNodeDirection) {
6664 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6666 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6667 return; // We have not been initialized yet or already been destroyed.
6670 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6671 return; // Temporarily disabled by edit sub-action handler.
6674 DebugOnly<nsresult> rvIgnored =
6675 aSplitNodeDirection == SplitNodeDirection::LeftNodeIsNewOne
6676 ? AddRangeToChangedRange(*aEditorBase.AsHTMLEditor(),
6677 EditorRawDOMPoint(&aNewContent, 0),
6678 EditorRawDOMPoint(&aSplitContent, 0))
6679 : AddRangeToChangedRange(*aEditorBase.AsHTMLEditor(),
6680 EditorRawDOMPoint::AtEndOf(aSplitContent),
6681 EditorRawDOMPoint::AtEndOf(aNewContent));
6682 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6683 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6684 "failed, but ignored");
6687 void EditorBase::TopLevelEditSubActionData::DidJoinContents(
6688 EditorBase& aEditorBase, const EditorRawDOMPoint& aJoinedPoint) {
6689 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6691 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6692 return; // We have not been initialized yet or already been destroyed.
6695 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6696 return; // Temporarily disabled by edit sub-action handler.
6699 DebugOnly<nsresult> rvIgnored =
6700 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aJoinedPoint);
6701 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6702 "TopLevelEditSubActionData::AddPointToChangedRange() "
6703 "failed, but ignored");
6706 void EditorBase::TopLevelEditSubActionData::DidInsertText(
6707 EditorBase& aEditorBase, const EditorRawDOMPoint& aInsertionBegin,
6708 const EditorRawDOMPoint& aInsertionEnd) {
6709 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6711 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6712 return; // We have not been initialized yet or already been destroyed.
6715 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6716 return; // Temporarily disabled by edit sub-action handler.
6719 DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange(
6720 *aEditorBase.AsHTMLEditor(), aInsertionBegin, aInsertionEnd);
6721 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6722 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6723 "failed, but ignored");
6726 void EditorBase::TopLevelEditSubActionData::DidDeleteText(
6727 EditorBase& aEditorBase, const EditorRawDOMPoint& aStartInTextNode) {
6728 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6730 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6731 return; // We have not been initialized yet or already been destroyed.
6734 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6735 return; // Temporarily disabled by edit sub-action handler.
6738 DebugOnly<nsresult> rvIgnored =
6739 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aStartInTextNode);
6740 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6741 "TopLevelEditSubActionData::AddPointToChangedRange() "
6742 "failed, but ignored");
6745 void EditorBase::TopLevelEditSubActionData::WillDeleteRange(
6746 EditorBase& aEditorBase, const EditorRawDOMPoint& aStart,
6747 const EditorRawDOMPoint& aEnd) {
6748 MOZ_ASSERT(aEditorBase.AsHTMLEditor());
6749 MOZ_ASSERT(aStart.IsSet());
6750 MOZ_ASSERT(aEnd.IsSet());
6752 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
6753 return; // We have not been initialized yet or already been destroyed.
6756 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
6757 return; // Temporarily disabled by edit sub-action handler.
6760 // XXX Looks like that this is wrong. We delete multiple selection ranges
6761 // once, but this adds only first range into the changed range.
6762 // Anyway, we should take the range as an argument.
6763 DebugOnly<nsresult> rvIgnored =
6764 AddRangeToChangedRange(*aEditorBase.AsHTMLEditor(), aStart, aEnd);
6765 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
6766 "TopLevelEditSubActionData::AddRangeToChangedRange() "
6767 "failed, but ignored");
6770 nsPIDOMWindowOuter* EditorBase::GetWindow() const {
6771 return mDocument ? mDocument->GetWindow() : nullptr;
6774 nsPIDOMWindowInner* EditorBase::GetInnerWindow() const {
6775 return mDocument ? mDocument->GetInnerWindow() : nullptr;
6778 PresShell* EditorBase::GetPresShell() const {
6779 return mDocument ? mDocument->GetPresShell() : nullptr;
6782 nsPresContext* EditorBase::GetPresContext() const {
6783 PresShell* presShell = GetPresShell();
6784 return presShell ? presShell->GetPresContext() : nullptr;
6787 already_AddRefed<nsCaret> EditorBase::GetCaret() const {
6788 PresShell* presShell = GetPresShell();
6789 if (NS_WARN_IF(!presShell)) {
6790 return nullptr;
6792 return presShell->GetCaret();
6795 nsISelectionController* EditorBase::GetSelectionController() const {
6796 if (mSelectionController) {
6797 return mSelectionController;
6799 if (!mDocument) {
6800 return nullptr;
6802 return mDocument->GetPresShell();
6805 } // namespace mozilla