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