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