Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / HTMLEditor.cpp
blobfc88c7947751eb5018477fb212f9ae75d362808a
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 "HTMLEditor.h"
7 #include "HTMLEditHelpers.h"
8 #include "HTMLEditorInlines.h"
10 #include "AutoRangeArray.h"
11 #include "CSSEditUtils.h"
12 #include "EditAction.h"
13 #include "EditorBase.h"
14 #include "EditorDOMPoint.h"
15 #include "EditorUtils.h"
16 #include "ErrorList.h"
17 #include "HTMLEditorEventListener.h"
18 #include "HTMLEditUtils.h"
19 #include "InsertNodeTransaction.h"
20 #include "JoinNodesTransaction.h"
21 #include "MoveNodeTransaction.h"
22 #include "PendingStyles.h"
23 #include "ReplaceTextTransaction.h"
24 #include "SplitNodeTransaction.h"
25 #include "WSRunObject.h"
27 #include "mozilla/ComposerCommandsUpdater.h"
28 #include "mozilla/ContentIterator.h"
29 #include "mozilla/DebugOnly.h"
30 #include "mozilla/EditorForwards.h"
31 #include "mozilla/Encoding.h" // for Encoding
32 #include "mozilla/FlushType.h"
33 #include "mozilla/IMEStateManager.h"
34 #include "mozilla/IntegerRange.h" // for IntegerRange
35 #include "mozilla/InternalMutationEvent.h"
36 #include "mozilla/mozInlineSpellChecker.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/PresShell.h"
39 #include "mozilla/StaticPrefs_editor.h"
40 #include "mozilla/StyleSheet.h"
41 #include "mozilla/StyleSheetInlines.h"
42 #include "mozilla/Telemetry.h"
43 #include "mozilla/TextEvents.h"
44 #include "mozilla/TextServicesDocument.h"
45 #include "mozilla/ToString.h"
46 #include "mozilla/css/Loader.h"
47 #include "mozilla/dom/AncestorIterator.h"
48 #include "mozilla/dom/Attr.h"
49 #include "mozilla/dom/DocumentFragment.h"
50 #include "mozilla/dom/DocumentInlines.h"
51 #include "mozilla/dom/Element.h"
52 #include "mozilla/dom/Event.h"
53 #include "mozilla/dom/EventTarget.h"
54 #include "mozilla/dom/HTMLAnchorElement.h"
55 #include "mozilla/dom/HTMLBodyElement.h"
56 #include "mozilla/dom/HTMLBRElement.h"
57 #include "mozilla/dom/HTMLButtonElement.h"
58 #include "mozilla/dom/HTMLSummaryElement.h"
59 #include "mozilla/dom/NameSpaceConstants.h"
60 #include "mozilla/dom/Selection.h"
62 #include "nsContentList.h"
63 #include "nsContentUtils.h"
64 #include "nsCRT.h"
65 #include "nsDebug.h"
66 #include "nsDOMAttributeMap.h"
67 #include "nsElementTable.h"
68 #include "nsFocusManager.h"
69 #include "nsGenericHTMLElement.h"
70 #include "nsGkAtoms.h"
71 #include "nsHTMLDocument.h"
72 #include "nsIContent.h"
73 #include "nsIContentInlines.h"
74 #include "nsIEditActionListener.h"
75 #include "nsIFrame.h"
76 #include "nsIPrincipal.h"
77 #include "nsISelectionController.h"
78 #include "nsIURI.h"
79 #include "nsIWidget.h"
80 #include "nsNetUtil.h"
81 #include "nsPresContext.h"
82 #include "nsPrintfCString.h"
83 #include "nsPIDOMWindow.h"
84 #include "nsStyledElement.h"
85 #include "nsTextFragment.h"
86 #include "nsUnicharUtils.h"
88 namespace mozilla {
90 using namespace dom;
91 using namespace widget;
93 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
94 using LeafNodeType = HTMLEditUtils::LeafNodeType;
95 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
96 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
98 // Some utilities to handle overloading of "A" tag for link and named anchor.
99 static bool IsLinkTag(const nsAtom& aTagName) {
100 return &aTagName == nsGkAtoms::href;
103 static bool IsNamedAnchorTag(const nsAtom& aTagName) {
104 return &aTagName == nsGkAtoms::anchor;
107 // Helper struct for DoJoinNodes() and DoSplitNode().
108 struct MOZ_STACK_CLASS SavedRange final {
109 RefPtr<Selection> mSelection;
110 nsCOMPtr<nsINode> mStartContainer;
111 nsCOMPtr<nsINode> mEndContainer;
112 uint32_t mStartOffset = 0;
113 uint32_t mEndOffset = 0;
116 /******************************************************************************
117 * HTMLEditor::AutoSelectionRestorer
118 *****************************************************************************/
120 HTMLEditor::AutoSelectionRestorer::AutoSelectionRestorer(
121 HTMLEditor& aHTMLEditor)
122 : mHTMLEditor(nullptr) {
123 if (aHTMLEditor.ArePreservingSelection()) {
124 // We already have initialized mParentData::mSavedSelection, so this must
125 // be nested call.
126 return;
128 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
129 mHTMLEditor = &aHTMLEditor;
130 mHTMLEditor->PreserveSelectionAcrossActions();
133 HTMLEditor::AutoSelectionRestorer::~AutoSelectionRestorer() {
134 if (!mHTMLEditor || !mHTMLEditor->ArePreservingSelection()) {
135 return;
137 DebugOnly<nsresult> rvIgnored = mHTMLEditor->RestorePreservedSelection();
138 NS_WARNING_ASSERTION(
139 NS_SUCCEEDED(rvIgnored),
140 "EditorBase::RestorePreservedSelection() failed, but ignored");
143 void HTMLEditor::AutoSelectionRestorer::Abort() {
144 if (mHTMLEditor) {
145 mHTMLEditor->StopPreservingSelection();
149 /******************************************************************************
150 * HTMLEditor
151 *****************************************************************************/
153 template Result<CreateContentResult, nsresult>
154 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(
155 nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert,
156 SplitAtEdges aSplitAtEdges);
157 template Result<CreateElementResult, nsresult>
158 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(
159 Element& aContentToInsert, const EditorDOMPoint& aPointToInsert,
160 SplitAtEdges aSplitAtEdges);
161 template Result<CreateTextResult, nsresult>
162 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(
163 Text& aContentToInsert, const EditorDOMPoint& aPointToInsert,
164 SplitAtEdges aSplitAtEdges);
166 HTMLEditor::InitializeInsertingElement HTMLEditor::DoNothingForNewElement =
167 [](HTMLEditor&, Element&, const EditorDOMPoint&) { return NS_OK; };
169 HTMLEditor::InitializeInsertingElement HTMLEditor::InsertNewBRElement =
170 [](HTMLEditor& aHTMLEditor, Element& aNewElement, const EditorDOMPoint&)
171 MOZ_CAN_RUN_SCRIPT_BOUNDARY {
172 MOZ_ASSERT(!aNewElement.IsInComposedDoc());
173 Result<CreateElementResult, nsresult> createBRElementResult =
174 aHTMLEditor.InsertBRElement(WithTransaction::No,
175 EditorDOMPoint(&aNewElement, 0u));
176 if (MOZ_UNLIKELY(createBRElementResult.isErr())) {
177 NS_WARNING_ASSERTION(
178 createBRElementResult.isOk(),
179 "HTMLEditor::InsertBRElement(WithTransaction::No) failed");
180 return createBRElementResult.unwrapErr();
182 createBRElementResult.unwrap().IgnoreCaretPointSuggestion();
183 return NS_OK;
186 // static
187 Result<CreateElementResult, nsresult>
188 HTMLEditor::AppendNewElementToInsertingElement(
189 HTMLEditor& aHTMLEditor, const nsStaticAtom& aTagName, Element& aNewElement,
190 const InitializeInsertingElement& aInitializer) {
191 MOZ_ASSERT(!aNewElement.IsInComposedDoc());
192 Result<CreateElementResult, nsresult> createNewElementResult =
193 aHTMLEditor.CreateAndInsertElement(
194 WithTransaction::No, const_cast<nsStaticAtom&>(aTagName),
195 EditorDOMPoint(&aNewElement, 0u), aInitializer);
196 NS_WARNING_ASSERTION(
197 createNewElementResult.isOk(),
198 "HTMLEditor::CreateAndInsertElement(WithTransaction::No) failed");
199 return createNewElementResult;
202 // static
203 Result<CreateElementResult, nsresult>
204 HTMLEditor::AppendNewElementWithBRToInsertingElement(
205 HTMLEditor& aHTMLEditor, const nsStaticAtom& aTagName,
206 Element& aNewElement) {
207 MOZ_ASSERT(!aNewElement.IsInComposedDoc());
208 Result<CreateElementResult, nsresult> createNewElementWithBRResult =
209 HTMLEditor::AppendNewElementToInsertingElement(
210 aHTMLEditor, aTagName, aNewElement, HTMLEditor::InsertNewBRElement);
211 NS_WARNING_ASSERTION(
212 createNewElementWithBRResult.isOk(),
213 "HTMLEditor::AppendNewElementToInsertingElement() failed");
214 return createNewElementWithBRResult;
217 HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributes =
218 [](HTMLEditor&, const Element&, const Element&, const Attr&, nsString&) {
219 return true;
221 HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptId =
222 [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
223 nsString&) {
224 return aAttr.NodeInfo()->NamespaceID() != kNameSpaceID_None ||
225 aAttr.NodeInfo()->NameAtom() != nsGkAtoms::id;
227 HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptDir =
228 [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
229 nsString&) {
230 return aAttr.NodeInfo()->NamespaceID() != kNameSpaceID_None ||
231 aAttr.NodeInfo()->NameAtom() != nsGkAtoms::dir;
233 HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptIdAndDir =
234 [](HTMLEditor&, const Element&, const Element&, const Attr& aAttr,
235 nsString&) {
236 return !(aAttr.NodeInfo()->NamespaceID() == kNameSpaceID_None &&
237 (aAttr.NodeInfo()->NameAtom() == nsGkAtoms::id ||
238 aAttr.NodeInfo()->NameAtom() == nsGkAtoms::dir));
241 HTMLEditor::HTMLEditor(const Document& aDocument)
242 : EditorBase(EditorBase::EditorType::HTML),
243 mCRInParagraphCreatesParagraph(false),
244 mIsObjectResizingEnabled(
245 StaticPrefs::editor_resizing_enabled_by_default()),
246 mIsResizing(false),
247 mPreserveRatio(false),
248 mResizedObjectIsAnImage(false),
249 mIsAbsolutelyPositioningEnabled(
250 StaticPrefs::editor_positioning_enabled_by_default()),
251 mResizedObjectIsAbsolutelyPositioned(false),
252 mGrabberClicked(false),
253 mIsMoving(false),
254 mSnapToGridEnabled(false),
255 mIsInlineTableEditingEnabled(
256 StaticPrefs::editor_inline_table_editing_enabled_by_default()),
257 mIsCSSPrefChecked(StaticPrefs::editor_use_css()),
258 mOriginalX(0),
259 mOriginalY(0),
260 mResizedObjectX(0),
261 mResizedObjectY(0),
262 mResizedObjectWidth(0),
263 mResizedObjectHeight(0),
264 mResizedObjectMarginLeft(0),
265 mResizedObjectMarginTop(0),
266 mResizedObjectBorderLeft(0),
267 mResizedObjectBorderTop(0),
268 mXIncrementFactor(0),
269 mYIncrementFactor(0),
270 mWidthIncrementFactor(0),
271 mHeightIncrementFactor(0),
272 mInfoXIncrement(20),
273 mInfoYIncrement(20),
274 mPositionedObjectX(0),
275 mPositionedObjectY(0),
276 mPositionedObjectWidth(0),
277 mPositionedObjectHeight(0),
278 mPositionedObjectMarginLeft(0),
279 mPositionedObjectMarginTop(0),
280 mPositionedObjectBorderLeft(0),
281 mPositionedObjectBorderTop(0),
282 mGridSize(0),
283 mDefaultParagraphSeparator(ParagraphSeparator::div) {}
285 HTMLEditor::~HTMLEditor() {
286 Telemetry::Accumulate(Telemetry::HTMLEDITORS_WITH_BEFOREINPUT_LISTENERS,
287 MayHaveBeforeInputEventListenersForTelemetry() ? 1 : 0);
288 Telemetry::Accumulate(
289 Telemetry::HTMLEDITORS_OVERRIDDEN_BY_BEFOREINPUT_LISTENERS,
290 mHasBeforeInputBeenCanceled ? 1 : 0);
291 Telemetry::Accumulate(
292 Telemetry::
293 HTMLEDITORS_WITH_MUTATION_LISTENERS_WITHOUT_BEFOREINPUT_LISTENERS,
294 !MayHaveBeforeInputEventListenersForTelemetry() &&
295 MayHaveMutationEventListeners()
297 : 0);
298 Telemetry::Accumulate(
299 Telemetry::
300 HTMLEDITORS_WITH_MUTATION_OBSERVERS_WITHOUT_BEFOREINPUT_LISTENERS,
301 !MayHaveBeforeInputEventListenersForTelemetry() &&
302 MutationObserverHasObservedNodeForTelemetry()
304 : 0);
306 mPendingStylesToApplyToNewContent = nullptr;
308 if (mDisabledLinkHandling) {
309 if (Document* doc = GetDocument()) {
310 doc->SetLinkHandlingEnabled(mOldLinkHandlingEnabled);
314 RemoveEventListeners();
316 HideAnonymousEditingUIs();
319 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor)
321 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLEditor, EditorBase)
322 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStylesToApplyToNewContent)
323 NS_IMPL_CYCLE_COLLECTION_UNLINK(mComposerCommandsUpdater)
324 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChangedRangeForTopLevelEditSubAction)
325 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaddingBRElementForEmptyEditor)
326 tmp->HideAnonymousEditingUIs();
327 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
329 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor, EditorBase)
330 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStylesToApplyToNewContent)
331 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mComposerCommandsUpdater)
332 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChangedRangeForTopLevelEditSubAction)
333 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaddingBRElementForEmptyEditor)
335 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle)
336 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle)
337 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle)
338 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle)
339 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle)
340 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle)
341 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle)
342 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle)
343 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle)
344 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow)
345 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo)
346 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject)
348 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject)
349 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber)
350 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow)
352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell)
353 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton)
354 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton)
355 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton)
356 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton)
357 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton)
358 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton)
359 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
361 NS_IMPL_ADDREF_INHERITED(HTMLEditor, EditorBase)
362 NS_IMPL_RELEASE_INHERITED(HTMLEditor, EditorBase)
364 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLEditor)
365 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor)
366 NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer)
367 NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor)
368 NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor)
369 NS_INTERFACE_MAP_ENTRY(nsITableEditor)
370 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
371 NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
372 NS_INTERFACE_MAP_END_INHERITING(EditorBase)
374 nsresult HTMLEditor::Init(Document& aDocument,
375 ComposerCommandsUpdater& aComposerCommandsUpdater,
376 uint32_t aFlags) {
377 MOZ_ASSERT(!mInitSucceeded,
378 "HTMLEditor::Init() called again without calling PreDestroy()?");
380 MOZ_DIAGNOSTIC_ASSERT(!mComposerCommandsUpdater ||
381 mComposerCommandsUpdater == &aComposerCommandsUpdater);
382 mComposerCommandsUpdater = &aComposerCommandsUpdater;
384 RefPtr<PresShell> presShell = aDocument.GetPresShell();
385 if (NS_WARN_IF(!presShell)) {
386 return NS_ERROR_FAILURE;
388 nsresult rv = InitInternal(aDocument, nullptr, *presShell, aFlags);
389 if (NS_FAILED(rv)) {
390 NS_WARNING("EditorBase::InitInternal() failed");
391 return rv;
394 // Init mutation observer
395 aDocument.AddMutationObserverUnlessExists(this);
397 if (!mRootElement) {
398 UpdateRootElement();
401 // disable Composer-only features
402 if (IsMailEditor()) {
403 DebugOnly<nsresult> rvIgnored = SetAbsolutePositioningEnabled(false);
404 NS_WARNING_ASSERTION(
405 NS_SUCCEEDED(rvIgnored),
406 "HTMLEditor::SetAbsolutePositioningEnabled(false) failed, but ignored");
407 rvIgnored = SetSnapToGridEnabled(false);
408 NS_WARNING_ASSERTION(
409 NS_SUCCEEDED(rvIgnored),
410 "HTMLEditor::SetSnapToGridEnabled(false) failed, but ignored");
413 // disable links
414 Document* document = GetDocument();
415 if (NS_WARN_IF(!document)) {
416 return NS_ERROR_FAILURE;
418 if (!IsPlaintextMailComposer() && !IsInteractionAllowed()) {
419 mDisabledLinkHandling = true;
420 mOldLinkHandlingEnabled = document->LinkHandlingEnabled();
421 document->SetLinkHandlingEnabled(false);
424 // init the type-in state
425 mPendingStylesToApplyToNewContent = new PendingStyles();
427 if (!IsInteractionAllowed()) {
428 nsCOMPtr<nsIURI> uaURI;
429 rv = NS_NewURI(getter_AddRefs(uaURI),
430 "resource://gre/res/EditorOverride.css");
431 NS_ENSURE_SUCCESS(rv, rv);
433 rv = document->LoadAdditionalStyleSheet(Document::eAgentSheet, uaURI);
434 NS_ENSURE_SUCCESS(rv, rv);
437 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing);
438 if (NS_WARN_IF(!editActionData.CanHandle())) {
439 return NS_ERROR_FAILURE;
442 rv = InitEditorContentAndSelection();
443 if (NS_FAILED(rv)) {
444 NS_WARNING("HTMLEditor::InitEditorContentAndSelection() failed");
445 // XXX Sholdn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
446 // is a public method?
447 return EditorBase::ToGenericNSResult(rv);
450 // Throw away the old transaction manager if this is not the first time that
451 // we're initializing the editor.
452 ClearUndoRedo();
453 EnableUndoRedo(); // FYI: Creating mTransactionManager in this call
455 if (mTransactionManager) {
456 mTransactionManager->Attach(*this);
459 MOZ_ASSERT(!mInitSucceeded, "HTMLEditor::Init() shouldn't be nested");
460 mInitSucceeded = true;
461 return NS_OK;
464 nsresult HTMLEditor::PostCreate() {
465 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
466 if (NS_WARN_IF(!editActionData.CanHandle())) {
467 return NS_ERROR_NOT_INITIALIZED;
470 nsresult rv = PostCreateInternal();
471 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
472 "EditorBase::PostCreatInternal() failed");
473 return rv;
476 void HTMLEditor::PreDestroy() {
477 if (mDidPreDestroy) {
478 return;
481 mInitSucceeded = false;
483 // FYI: Cannot create AutoEditActionDataSetter here. However, it does not
484 // necessary for the methods called by the following code.
486 RefPtr<Document> document = GetDocument();
487 if (document) {
488 document->RemoveMutationObserver(this);
490 if (!IsInteractionAllowed()) {
491 nsCOMPtr<nsIURI> uaURI;
492 nsresult rv = NS_NewURI(getter_AddRefs(uaURI),
493 "resource://gre/res/EditorOverride.css");
494 if (NS_SUCCEEDED(rv)) {
495 document->RemoveAdditionalStyleSheet(Document::eAgentSheet, uaURI);
500 // Clean up after our anonymous content -- we don't want these nodes to
501 // stay around (which they would, since the frames have an owning reference).
502 PresShell* presShell = GetPresShell();
503 if (presShell && presShell->IsDestroying()) {
504 // Just destroying PresShell now.
505 // We have to keep UI elements of anonymous content until PresShell
506 // is destroyed.
507 RefPtr<HTMLEditor> self = this;
508 nsContentUtils::AddScriptRunner(
509 NS_NewRunnableFunction("HTMLEditor::PreDestroy",
510 [self]() { self->HideAnonymousEditingUIs(); }));
511 } else {
512 // PresShell is alive or already gone.
513 HideAnonymousEditingUIs();
516 mPaddingBRElementForEmptyEditor = nullptr;
518 PreDestroyInternal();
521 NS_IMETHODIMP HTMLEditor::GetDocumentCharacterSet(nsACString& aCharacterSet) {
522 nsresult rv = GetDocumentCharsetInternal(aCharacterSet);
523 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
524 "HTMLEditor::GetDocumentCharsetInternal() failed");
525 return rv;
528 NS_IMETHODIMP HTMLEditor::SetDocumentCharacterSet(
529 const nsACString& aCharacterSet) {
530 AutoEditActionDataSetter editActionData(*this, EditAction::eSetCharacterSet);
531 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
532 if (NS_FAILED(rv)) {
533 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
534 "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
535 return EditorBase::ToGenericNSResult(rv);
538 RefPtr<Document> document = GetDocument();
539 if (NS_WARN_IF(!document)) {
540 return EditorBase::ToGenericNSResult(NS_ERROR_NOT_INITIALIZED);
542 // This method is scriptable, so add-ons could pass in something other
543 // than a canonical name.
544 const Encoding* encoding = Encoding::ForLabelNoReplacement(aCharacterSet);
545 if (!encoding) {
546 NS_WARNING("Encoding::ForLabelNoReplacement() failed");
547 return EditorBase::ToGenericNSResult(NS_ERROR_INVALID_ARG);
549 document->SetDocumentCharacterSet(WrapNotNull(encoding));
551 // Update META charset element.
552 if (UpdateMetaCharsetWithTransaction(*document, aCharacterSet)) {
553 return NS_OK;
556 // Set attributes to the created element
557 if (aCharacterSet.IsEmpty()) {
558 return NS_OK;
561 RefPtr<nsContentList> headElementList =
562 document->GetElementsByTagName(u"head"_ns);
563 if (NS_WARN_IF(!headElementList)) {
564 return NS_OK;
567 nsCOMPtr<nsIContent> primaryHeadElement = headElementList->Item(0);
568 if (NS_WARN_IF(!primaryHeadElement)) {
569 return NS_OK;
572 // Create a new meta charset tag
573 Result<CreateElementResult, nsresult> createNewMetaElementResult =
574 CreateAndInsertElement(
575 WithTransaction::Yes, *nsGkAtoms::meta,
576 EditorDOMPoint(primaryHeadElement, 0),
577 [&aCharacterSet](HTMLEditor&, Element& aMetaElement,
578 const EditorDOMPoint&) {
579 MOZ_ASSERT(!aMetaElement.IsInComposedDoc());
580 DebugOnly<nsresult> rvIgnored =
581 aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
582 u"Content-Type"_ns, false);
583 NS_WARNING_ASSERTION(
584 NS_SUCCEEDED(rvIgnored),
585 "Element::SetAttr(nsGkAtoms::httpEquiv, \"Content-Type\", "
586 "false) failed, but ignored");
587 rvIgnored =
588 aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::content,
589 u"text/html;charset="_ns +
590 NS_ConvertASCIItoUTF16(aCharacterSet),
591 false);
592 NS_WARNING_ASSERTION(
593 NS_SUCCEEDED(rvIgnored),
594 nsPrintfCString(
595 "Element::SetAttr(nsGkAtoms::content, "
596 "\"text/html;charset=%s\", false) failed, but ignored",
597 nsPromiseFlatCString(aCharacterSet).get())
598 .get());
599 return NS_OK;
601 NS_WARNING_ASSERTION(createNewMetaElementResult.isOk(),
602 "HTMLEditor::CreateAndInsertElement(WithTransaction::"
603 "Yes, nsGkAtoms::meta) failed, but ignored");
604 // Probably, we don't need to update selection in this case since we should
605 // not put selection into <head> element.
606 createNewMetaElementResult.inspect().IgnoreCaretPointSuggestion();
607 return NS_OK;
610 bool HTMLEditor::UpdateMetaCharsetWithTransaction(
611 Document& aDocument, const nsACString& aCharacterSet) {
612 // get a list of META tags
613 RefPtr<nsContentList> metaElementList =
614 aDocument.GetElementsByTagName(u"meta"_ns);
615 if (NS_WARN_IF(!metaElementList)) {
616 return false;
619 for (uint32_t i = 0; i < metaElementList->Length(true); ++i) {
620 RefPtr<Element> metaElement = metaElementList->Item(i)->AsElement();
621 MOZ_ASSERT(metaElement);
623 nsAutoString currentValue;
624 metaElement->GetAttr(nsGkAtoms::httpEquiv, currentValue);
626 if (!FindInReadable(u"content-type"_ns, currentValue,
627 nsCaseInsensitiveStringComparator)) {
628 continue;
631 metaElement->GetAttr(nsGkAtoms::content, currentValue);
633 constexpr auto charsetEquals = u"charset="_ns;
634 nsAString::const_iterator originalStart, start, end;
635 originalStart = currentValue.BeginReading(start);
636 currentValue.EndReading(end);
637 if (!FindInReadable(charsetEquals, start, end,
638 nsCaseInsensitiveStringComparator)) {
639 continue;
642 // set attribute to <original prefix> charset=text/html
643 nsresult rv = SetAttributeWithTransaction(
644 *metaElement, *nsGkAtoms::content,
645 Substring(originalStart, start) + charsetEquals +
646 NS_ConvertASCIItoUTF16(aCharacterSet));
647 NS_WARNING_ASSERTION(
648 NS_SUCCEEDED(rv),
649 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::content) failed");
650 return NS_SUCCEEDED(rv);
652 return false;
655 NS_IMETHODIMP HTMLEditor::NotifySelectionChanged(Document* aDocument,
656 Selection* aSelection,
657 int16_t aReason,
658 int32_t aAmount) {
659 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
660 return NS_ERROR_INVALID_ARG;
663 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
664 if (NS_WARN_IF(!editActionData.CanHandle())) {
665 return NS_ERROR_NOT_INITIALIZED;
668 if (mPendingStylesToApplyToNewContent) {
669 RefPtr<PendingStyles> pendingStyles = mPendingStylesToApplyToNewContent;
670 pendingStyles->OnSelectionChange(*this, aReason);
672 // We used a class which derived from nsISelectionListener to call
673 // HTMLEditor::RefreshEditingUI(). The lifetime of the class was
674 // exactly same as mPendingStylesToApplyToNewContent. So, call it only when
675 // mPendingStylesToApplyToNewContent is not nullptr.
676 if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
677 nsISelectionListener::KEYPRESS_REASON |
678 nsISelectionListener::SELECTALL_REASON)) &&
679 aSelection) {
680 // the selection changed and we need to check if we have to
681 // hide and/or redisplay resizing handles
682 // FYI: This is an XPCOM method. So, the caller, Selection, guarantees
683 // the lifetime of this instance. So, don't need to grab this with
684 // local variable.
685 DebugOnly<nsresult> rv = RefreshEditingUI();
686 NS_WARNING_ASSERTION(
687 NS_SUCCEEDED(rv),
688 "HTMLEditor::RefreshEditingUI() failed, but ignored");
692 if (mComposerCommandsUpdater) {
693 RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater;
694 updater->OnSelectionChange();
697 nsresult rv = EditorBase::NotifySelectionChanged(aDocument, aSelection,
698 aReason, aAmount);
699 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
700 "EditorBase::NotifySelectionChanged() failed");
701 return rv;
704 void HTMLEditor::UpdateRootElement() {
705 // Use the HTML documents body element as the editor root if we didn't
706 // get a root element during initialization.
708 mRootElement = GetBodyElement();
709 if (!mRootElement) {
710 RefPtr<Document> doc = GetDocument();
711 if (doc) {
712 // If there is no HTML body element,
713 // we should use the document root element instead.
714 mRootElement = doc->GetDocumentElement();
716 // else leave it null, for lack of anything better.
720 nsresult HTMLEditor::FocusedElementOrDocumentBecomesEditable(
721 Document& aDocument, Element* aElement) {
722 const bool isInDesignMode =
723 (IsInDesignMode() && (!aElement || aElement->IsInDesignMode()));
725 // If we should've already handled focus event, selection limiter should not
726 // be set. However, IMEStateManager is not notified the pseudo focus change
727 // in this case. Therefore, we need to notify IMEStateManager of this.
728 if (GetSelectionAncestorLimiter()) {
729 if (isInDesignMode) {
730 return NS_OK;
732 // Although editor is already initialized due to re-used, ISM may not
733 // create IME content observer yet. So we have to create it.
734 IMEState newState;
735 nsresult rv = GetPreferredIMEState(&newState);
736 if (NS_FAILED(rv)) {
737 NS_WARNING("EditorBase::GetPreferredIMEState() failed");
738 return NS_OK;
740 if (const RefPtr<Element> focusedElement = GetFocusedElement()) {
741 MOZ_ASSERT(focusedElement == aElement);
742 IMEStateManager::UpdateIMEState(newState, focusedElement, *this);
744 return NS_OK;
746 // If we should be in the design mode, we want to handle focus event fired
747 // on the document node. Therefore, we should emulate it here.
748 if (isInDesignMode) {
749 MOZ_ASSERT(&aDocument == GetDocument());
750 nsresult rv = OnFocus(aDocument);
751 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed");
752 return rv;
755 if (NS_WARN_IF(!aElement)) {
756 return NS_ERROR_INVALID_ARG;
759 // Otherwise, we should've already handled focus event on the element,
760 // therefore, we need to emulate it here.
761 MOZ_ASSERT(nsFocusManager::GetFocusManager()->GetFocusedElement() ==
762 aElement);
763 nsresult rv = OnFocus(*aElement);
764 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed");
766 // Note that we don't need to call
767 // IMEStateManager::MaybeOnEditableStateDisabled here because
768 // EditorBase::OnFocus must have already been called IMEStateManager::OnFocus
769 // if succeeded. And perhaps, it's okay that IME is not enabled when
770 // HTMLEditor fails to start handling since nobody can handle composition
771 // events anyway...
773 return rv;
776 nsresult HTMLEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
777 // Before doing anything, we should check whether the original target is still
778 // valid focus event target because it may have already lost focus.
779 if (!CanKeepHandlingFocusEvent(aOriginalEventTargetNode)) {
780 return NS_OK;
783 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
784 if (NS_WARN_IF(!editActionData.CanHandle())) {
785 return NS_ERROR_FAILURE;
788 return EditorBase::OnFocus(aOriginalEventTargetNode);
791 nsresult HTMLEditor::FocusedElementOrDocumentBecomesNotEditable(
792 HTMLEditor* aHTMLEditor, Document& aDocument, Element* aElement) {
793 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
794 // If HTMLEditor has not been created yet, we just need to adjust
795 // IMEStateManager. So, don't return error.
796 if (!aHTMLEditor) {
797 return NS_OK;
800 nsIContent* const limiter = aHTMLEditor->GetSelectionAncestorLimiter();
801 // The HTMLEditor has not received `focus` event so that it does not need to
802 // emulate `blur`.
803 if (!limiter) {
804 return NS_OK;
807 // If we should be in the design mode, we should treat it as blur from
808 // the document node.
809 if (aHTMLEditor->IsInDesignMode() &&
810 (!aElement || aElement->IsInDesignMode())) {
811 MOZ_ASSERT(aHTMLEditor->GetDocument() == &aDocument);
812 nsresult rv = aHTMLEditor->OnBlur(&aDocument);
813 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnBlur() failed");
814 return rv;
816 // If the HTMLEditor has already received `focus` event for different
817 // element than aElement, we'll receive `blur` event later so that we need
818 // to do nothing here.
819 if (aElement != limiter) {
820 return NS_OK;
823 // Otherwise, even though the limiter keeps having focus but becomes not
824 // editable. From HTMLEditor point of view, this is equivalent to the
825 // elements gets blurred. Therefore, we should treat it as losing
826 // focus.
827 nsresult rv = aHTMLEditor->OnBlur(aElement);
828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnBlur() failed");
829 return rv;
830 }();
832 // If the element becomes not editable without focus change, IMEStateManager
833 // does not have a chance to disable IME. Therefore, (even if we fail to
834 // handle the emulated blur above,) we should notify IMEStateManager of the
835 // editing state change.
836 RefPtr<Element> focusedElement = aElement ? aElement
837 : aHTMLEditor
838 ? aHTMLEditor->GetFocusedElement()
839 : nullptr;
840 RefPtr<nsPresContext> presContext =
841 focusedElement ? focusedElement->GetPresContext(
842 Element::PresContextFor::eForComposedDoc)
843 : aDocument.GetPresContext();
844 if (presContext) {
845 IMEStateManager::MaybeOnEditableStateDisabled(*presContext, focusedElement);
848 return rv;
851 nsresult HTMLEditor::OnBlur(const EventTarget* aEventTarget) {
852 // check if something else is focused. If another element is focused, then
853 // we should not change the selection.
854 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
855 if (MOZ_UNLIKELY(!focusManager)) {
856 return NS_OK;
859 // If another element already has focus, we should not maintain the selection
860 // because we may not have the rights doing it.
861 if (focusManager->GetFocusedElement()) {
862 return NS_OK;
865 // If it's in the designMode, and blur occurs, the target must be the
866 // document node. If a blur event is fired and the target is an element, it
867 // must be delayed blur event at initializing the `HTMLEditor`.
868 if (IsInDesignMode() && Element::FromEventTargetOrNull(aEventTarget)) {
869 return NS_OK;
871 nsresult rv = FinalizeSelection();
872 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
873 "EditorBase::FinalizeSelection() failed");
874 return rv;
877 Element* HTMLEditor::FindSelectionRoot(const nsINode& aNode) const {
878 MOZ_ASSERT(aNode.IsDocument() || aNode.IsContent(),
879 "aNode must be content or document node");
881 if (NS_WARN_IF(!aNode.IsInComposedDoc())) {
882 return nullptr;
885 if (aNode.IsInDesignMode()) {
886 return GetDocument()->GetRootElement();
889 nsIContent* content = const_cast<nsIContent*>(aNode.AsContent());
890 if (!content->HasFlag(NODE_IS_EDITABLE)) {
891 // If the content is in read-write state but is not editable itself,
892 // return it as the selection root.
893 if (content->IsElement() &&
894 content->AsElement()->State().HasState(ElementState::READWRITE)) {
895 return content->AsElement();
897 return nullptr;
900 // For non-readonly editors we want to find the root of the editable subtree
901 // containing aContent.
902 return content->GetEditingHost();
905 bool HTMLEditor::IsInDesignMode() const {
906 // TODO: If active editing host is in a shadow tree, it means that we should
907 // behave exactly same as contenteditable mode because shadow tree
908 // content is not editable even if composed document is in design mode,
909 // but contenteditable elements in shoadow trees are focusable and
910 // their content is editable. Changing this affects to drop event
911 // handler and blur event handler, so please add new tests for them
912 // when you change here.
913 Document* document = GetDocument();
914 return document && document->IsInDesignMode();
917 bool HTMLEditor::EntireDocumentIsEditable() const {
918 Document* document = GetDocument();
919 return document && document->GetDocumentElement() &&
920 (document->GetDocumentElement()->IsEditable() ||
921 (document->GetBody() && document->GetBody()->IsEditable()));
924 void HTMLEditor::CreateEventListeners() {
925 // Don't create the handler twice
926 if (!mEventListener) {
927 mEventListener = new HTMLEditorEventListener();
931 nsresult HTMLEditor::InstallEventListeners() {
932 // FIXME InstallEventListeners() should not be called if we failed to set
933 // document or create an event listener. So, these checks should be
934 // MOZ_DIAGNOSTIC_ASSERT instead.
935 MOZ_ASSERT(GetDocument());
936 if (MOZ_UNLIKELY(!GetDocument()) || NS_WARN_IF(!mEventListener)) {
937 return NS_ERROR_NOT_INITIALIZED;
940 // NOTE: HTMLEditor doesn't need to initialize mEventTarget here because
941 // the target must be document node and it must be referenced as weak pointer.
943 HTMLEditorEventListener* listener =
944 reinterpret_cast<HTMLEditorEventListener*>(mEventListener.get());
945 nsresult rv = listener->Connect(this);
946 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
947 "HTMLEditorEventListener::Connect() failed");
948 return rv;
951 void HTMLEditor::Detach(
952 const ComposerCommandsUpdater& aComposerCommandsUpdater) {
953 MOZ_DIAGNOSTIC_ASSERT_IF(
954 mComposerCommandsUpdater,
955 &aComposerCommandsUpdater == mComposerCommandsUpdater);
956 if (mComposerCommandsUpdater == &aComposerCommandsUpdater) {
957 mComposerCommandsUpdater = nullptr;
958 if (mTransactionManager) {
959 mTransactionManager->Detach(*this);
964 NS_IMETHODIMP HTMLEditor::BeginningOfDocument() {
965 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
966 if (NS_WARN_IF(!editActionData.CanHandle())) {
967 return NS_ERROR_NOT_INITIALIZED;
970 nsresult rv = MaybeCollapseSelectionAtFirstEditableNode(false);
971 NS_WARNING_ASSERTION(
972 NS_SUCCEEDED(rv),
973 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
974 return rv;
977 NS_IMETHODIMP HTMLEditor::EndOfDocument() {
978 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
979 if (NS_WARN_IF(!editActionData.CanHandle())) {
980 return NS_ERROR_NOT_INITIALIZED;
982 nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument();
983 NS_WARNING_ASSERTION(
984 NS_SUCCEEDED(rv),
985 "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() failed");
986 // This is low level API for embedders and chrome script so that we can return
987 // raw error code here.
988 return rv;
991 nsresult HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() const {
992 MOZ_ASSERT(IsEditActionDataAvailable());
994 // We should do nothing with the result of GetRoot() if only a part of the
995 // document is editable.
996 if (!EntireDocumentIsEditable()) {
997 return NS_OK;
1000 RefPtr<Element> bodyOrDocumentElement = GetRoot();
1001 if (NS_WARN_IF(!bodyOrDocumentElement)) {
1002 return NS_ERROR_NULL_POINTER;
1005 auto pointToPutCaret = [&]() -> EditorRawDOMPoint {
1006 nsCOMPtr<nsIContent> lastLeafContent = HTMLEditUtils::GetLastLeafContent(
1007 *bodyOrDocumentElement, {LeafNodeType::OnlyLeafNode});
1008 if (!lastLeafContent) {
1009 return EditorRawDOMPoint::AtEndOf(*bodyOrDocumentElement);
1011 // TODO: We should put caret into text node if it's visible.
1012 return lastLeafContent->IsText() ||
1013 HTMLEditUtils::IsContainerNode(*lastLeafContent)
1014 ? EditorRawDOMPoint::AtEndOf(*lastLeafContent)
1015 : EditorRawDOMPoint(lastLeafContent);
1016 }();
1017 nsresult rv = CollapseSelectionTo(pointToPutCaret);
1018 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1019 "EditorBase::CollapseSelectionTo() failed");
1020 return rv;
1023 void HTMLEditor::InitializeSelectionAncestorLimit(
1024 nsIContent& aAncestorLimit) const {
1025 MOZ_ASSERT(IsEditActionDataAvailable());
1027 // Hack for initializing selection.
1028 // HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode() will try to
1029 // collapse selection at first editable text node or inline element which
1030 // cannot have text nodes as its children. However, selection has already
1031 // set into the new editing host by user, we should not change it. For
1032 // solving this issue, we should do nothing if selection range is in active
1033 // editing host except it's not collapsed at start of the editing host since
1034 // aSelection.SetAncestorLimiter(aAncestorLimit) will collapse selection
1035 // at start of the new limiter if focus node of aSelection is outside of the
1036 // editing host. However, we need to check here if selection is already
1037 // collapsed at start of the editing host because it's possible JS to do it.
1038 // In such case, we should not modify selection with calling
1039 // MaybeCollapseSelectionAtFirstEditableNode().
1041 // Basically, we should try to collapse selection at first editable node
1042 // in HTMLEditor.
1043 bool tryToCollapseSelectionAtFirstEditableNode = true;
1044 if (SelectionRef().RangeCount() == 1 && SelectionRef().IsCollapsed()) {
1045 Element* editingHost = ComputeEditingHost();
1046 const nsRange* range = SelectionRef().GetRangeAt(0);
1047 if (range->GetStartContainer() == editingHost && !range->StartOffset()) {
1048 // JS or user operation has already collapsed selection at start of
1049 // the editing host. So, we don't need to try to change selection
1050 // in this case.
1051 tryToCollapseSelectionAtFirstEditableNode = false;
1055 EditorBase::InitializeSelectionAncestorLimit(aAncestorLimit);
1057 // XXX Do we need to check if we still need to change selection? E.g.,
1058 // we could have already lost focus while we're changing the ancestor
1059 // limiter because it may causes "selectionchange" event.
1060 if (tryToCollapseSelectionAtFirstEditableNode) {
1061 DebugOnly<nsresult> rvIgnored =
1062 MaybeCollapseSelectionAtFirstEditableNode(true);
1063 NS_WARNING_ASSERTION(
1064 NS_SUCCEEDED(rvIgnored),
1065 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(true) failed, "
1066 "but ignored");
1069 // If the target is a text control element, we won't handle user input
1070 // for the `TextEditor` in it. However, we need to be open for `execCommand`.
1071 // Therefore, we shouldn't set ancestor limit in this case.
1072 // Note that we should do this once setting ancestor limiter for backward
1073 // compatiblity of select events, etc. (Selection should be collapsed into
1074 // the text control element.)
1075 if (aAncestorLimit.HasIndependentSelection()) {
1076 SelectionRef().SetAncestorLimiter(nullptr);
1080 nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
1081 bool aIgnoreIfSelectionInEditingHost) const {
1082 MOZ_ASSERT(IsEditActionDataAvailable());
1084 RefPtr<Element> editingHost = ComputeEditingHost(LimitInBodyElement::No);
1085 if (NS_WARN_IF(!editingHost)) {
1086 return NS_OK;
1089 // If selection range is already in the editing host and the range is not
1090 // start of the editing host, we shouldn't reset selection. E.g., window
1091 // is activated when the editor had focus before inactivated.
1092 if (aIgnoreIfSelectionInEditingHost && SelectionRef().RangeCount() == 1) {
1093 const nsRange* range = SelectionRef().GetRangeAt(0);
1094 if (!range->Collapsed() ||
1095 range->GetStartContainer() != editingHost.get() ||
1096 range->StartOffset()) {
1097 return NS_OK;
1101 for (nsIContent* leafContent = HTMLEditUtils::GetFirstLeafContent(
1102 *editingHost,
1103 {LeafNodeType::LeafNodeOrNonEditableNode,
1104 LeafNodeType::LeafNodeOrChildBlock},
1105 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
1106 leafContent;) {
1107 // If we meet a non-editable node first, we should move caret to start
1108 // of the container block or editing host.
1109 if (!EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) {
1110 MOZ_ASSERT(leafContent->GetParent());
1111 MOZ_ASSERT(EditorUtils::IsEditableContent(*leafContent->GetParent(),
1112 EditorType::HTML));
1113 if (const Element* editableBlockElementOrInlineEditingHost =
1114 HTMLEditUtils::GetAncestorElement(
1115 *leafContent,
1116 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
1117 BlockInlineCheck::UseComputedDisplayStyle)) {
1118 nsresult rv = CollapseSelectionTo(
1119 EditorDOMPoint(editableBlockElementOrInlineEditingHost, 0));
1120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1121 "EditorBase::CollapseSelectionTo() failed");
1122 return rv;
1124 NS_WARNING("Found leaf content did not have editable parent, why?");
1125 return NS_ERROR_FAILURE;
1128 // When we meet an empty inline element, we should look for a next sibling.
1129 // For example, if current editor is:
1130 // <div contenteditable><span></span><b><br></b></div>
1131 // then, we should put caret at the <br> element. So, let's check if found
1132 // node is an empty inline container element.
1133 if (Element* leafElement = Element::FromNode(leafContent)) {
1134 if (HTMLEditUtils::IsInlineContent(
1135 *leafElement, BlockInlineCheck::UseComputedDisplayStyle) &&
1136 !HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafElement) &&
1137 HTMLEditUtils::CanNodeContain(*leafElement,
1138 *nsGkAtoms::textTagName)) {
1139 // Chromium collapses selection to start of the editing host when this
1140 // is the last leaf content. So, we don't need special handling here.
1141 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1142 *leafElement, *editingHost,
1143 {LeafNodeType::LeafNodeOrNonEditableNode,
1144 LeafNodeType::LeafNodeOrChildBlock},
1145 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
1146 continue;
1150 if (Text* text = leafContent->GetAsText()) {
1151 // If there is editable and visible text node, move caret at first of
1152 // the visible character.
1153 WSScanResult scanResultInTextNode =
1154 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
1155 editingHost, EditorRawDOMPoint(text, 0),
1156 BlockInlineCheck::UseComputedDisplayStyle);
1157 if ((scanResultInTextNode.InVisibleOrCollapsibleCharacters() ||
1158 scanResultInTextNode.ReachedPreformattedLineBreak()) &&
1159 scanResultInTextNode.TextPtr() == text) {
1160 nsresult rv = CollapseSelectionTo(
1161 scanResultInTextNode.Point<EditorRawDOMPoint>());
1162 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1163 "EditorBase::CollapseSelectionTo() failed");
1164 return rv;
1166 // If it's an invisible text node, keep scanning next leaf.
1167 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1168 *leafContent, *editingHost,
1169 {LeafNodeType::LeafNodeOrNonEditableNode,
1170 LeafNodeType::LeafNodeOrChildBlock},
1171 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
1172 continue;
1175 // If there is editable <br> or something void element like <img>, <input>,
1176 // <hr> etc, move caret before it.
1177 if (!HTMLEditUtils::CanNodeContain(*leafContent, *nsGkAtoms::textTagName) ||
1178 HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) {
1179 MOZ_ASSERT(leafContent->GetParent());
1180 if (EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) {
1181 nsresult rv = CollapseSelectionTo(EditorDOMPoint(leafContent));
1182 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1183 "EditorBase::CollapseSelectionTo() failed");
1184 return rv;
1186 MOZ_ASSERT_UNREACHABLE(
1187 "How do we reach editable leaf in non-editable element?");
1188 // But if it's not editable, let's put caret at start of editing host
1189 // for now.
1190 nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0));
1191 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1192 "EditorBase::CollapseSelectionTo() failed");
1193 return rv;
1196 // If we meet non-empty block element, we need to scan its child too.
1197 if (HTMLEditUtils::IsBlockElement(
1198 *leafContent, BlockInlineCheck::UseComputedDisplayStyle) &&
1199 !HTMLEditUtils::IsEmptyNode(
1200 *leafContent,
1201 {EmptyCheckOption::TreatSingleBRElementAsVisible,
1202 EmptyCheckOption::TreatNonEditableContentAsInvisible}) &&
1203 !HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) {
1204 leafContent = HTMLEditUtils::GetFirstLeafContent(
1205 *leafContent,
1206 {LeafNodeType::LeafNodeOrNonEditableNode,
1207 LeafNodeType::LeafNodeOrChildBlock},
1208 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
1209 continue;
1212 // Otherwise, we must meet an empty block element or a data node like
1213 // comment node. Let's ignore it.
1214 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1215 *leafContent, *editingHost,
1216 {LeafNodeType::LeafNodeOrNonEditableNode,
1217 LeafNodeType::LeafNodeOrChildBlock},
1218 BlockInlineCheck::UseComputedDisplayStyle, editingHost);
1221 // If there is no visible/editable node except another block element in
1222 // current editing host, we should move caret to very first of the editing
1223 // host.
1224 // XXX This may not make sense, but Chromium behaves so. Therefore, the
1225 // reason why we do this is just compatibility with Chromium.
1226 nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0));
1227 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1228 "EditorBase::CollapseSelectionTo() failed");
1229 return rv;
1232 bool HTMLEditor::ArePreservingSelection() const {
1233 return IsEditActionDataAvailable() && SavedSelectionRef().RangeCount();
1236 void HTMLEditor::PreserveSelectionAcrossActions() {
1237 MOZ_ASSERT(IsEditActionDataAvailable());
1239 SavedSelectionRef().SaveSelection(SelectionRef());
1240 RangeUpdaterRef().RegisterSelectionState(SavedSelectionRef());
1243 nsresult HTMLEditor::RestorePreservedSelection() {
1244 MOZ_ASSERT(IsEditActionDataAvailable());
1246 if (!SavedSelectionRef().RangeCount()) {
1247 // XXX Returning error when it does not store is odd because no selection
1248 // ranges is not illegal case in general.
1249 return NS_ERROR_FAILURE;
1251 DebugOnly<nsresult> rvIgnored =
1252 SavedSelectionRef().RestoreSelection(SelectionRef());
1253 NS_WARNING_ASSERTION(
1254 NS_SUCCEEDED(rvIgnored),
1255 "SelectionState::RestoreSelection() failed, but ignored");
1256 StopPreservingSelection();
1257 return NS_OK;
1260 void HTMLEditor::StopPreservingSelection() {
1261 MOZ_ASSERT(IsEditActionDataAvailable());
1263 RangeUpdaterRef().DropSelectionState(SavedSelectionRef());
1264 SavedSelectionRef().RemoveAllRanges();
1267 void HTMLEditor::PreHandleMouseDown(const MouseEvent& aMouseDownEvent) {
1268 if (mPendingStylesToApplyToNewContent) {
1269 // mPendingStylesToApplyToNewContent will be notified of selection change
1270 // even if aMouseDownEvent is not an acceptable event for this editor.
1271 // Therefore, we need to notify it of this event too.
1272 mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseDownEvent);
1276 void HTMLEditor::PreHandleMouseUp(const MouseEvent& aMouseUpEvent) {
1277 if (mPendingStylesToApplyToNewContent) {
1278 // mPendingStylesToApplyToNewContent will be notified of selection change
1279 // even if aMouseUpEvent is not an acceptable event for this editor.
1280 // Therefore, we need to notify it of this event too.
1281 mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseUpEvent);
1285 void HTMLEditor::PreHandleSelectionChangeCommand(Command aCommand) {
1286 if (mPendingStylesToApplyToNewContent) {
1287 mPendingStylesToApplyToNewContent->PreHandleSelectionChangeCommand(
1288 aCommand);
1292 void HTMLEditor::PostHandleSelectionChangeCommand(Command aCommand) {
1293 if (!mPendingStylesToApplyToNewContent) {
1294 return;
1297 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1298 if (!editActionData.CanHandle()) {
1299 return;
1301 mPendingStylesToApplyToNewContent->PostHandleSelectionChangeCommand(*this,
1302 aCommand);
1305 nsresult HTMLEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
1306 // NOTE: When you change this method, you should also change:
1307 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
1308 if (NS_WARN_IF(!aKeyboardEvent)) {
1309 return NS_ERROR_UNEXPECTED;
1312 if (IsReadonly()) {
1313 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent);
1314 return NS_OK;
1317 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
1318 "HandleKeyPressEvent gets non-keypress event");
1320 switch (aKeyboardEvent->mKeyCode) {
1321 case NS_VK_META:
1322 case NS_VK_WIN:
1323 case NS_VK_SHIFT:
1324 case NS_VK_CONTROL:
1325 case NS_VK_ALT:
1326 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress
1327 // event.
1328 aKeyboardEvent->PreventDefault();
1329 return NS_OK;
1331 case NS_VK_BACK:
1332 case NS_VK_DELETE: {
1333 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
1334 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1335 "EditorBase::HandleKeyPressEvent() failed");
1336 return rv;
1338 case NS_VK_TAB: {
1339 // Basically, "Tab" key be used only for focus navigation.
1340 // FYI: In web apps, this is always true.
1341 if (IsTabbable()) {
1342 return NS_OK;
1345 // If we're in the plaintext mode, and not tabbable editor, let's
1346 // insert a horizontal tabulation.
1347 if (IsPlaintextMailComposer()) {
1348 if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
1349 aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta()) {
1350 return NS_OK;
1353 // else we insert the tab straight through
1354 aKeyboardEvent->PreventDefault();
1355 nsresult rv = OnInputText(u"\t"_ns);
1356 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1357 "EditorBase::OnInputText(\\t) failed");
1358 return rv;
1361 // Otherwise, e.g., we're an embedding editor in chrome, we can handle
1362 // "Tab" key as an input.
1363 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
1364 aKeyboardEvent->IsMeta()) {
1365 return NS_OK;
1368 RefPtr<Selection> selection = GetSelection();
1369 if (NS_WARN_IF(!selection) || NS_WARN_IF(!selection->RangeCount())) {
1370 return NS_ERROR_FAILURE;
1373 nsINode* startContainer = selection->GetRangeAt(0)->GetStartContainer();
1374 MOZ_ASSERT(startContainer);
1375 if (!startContainer->IsContent()) {
1376 break;
1379 const Element* editableBlockElement =
1380 HTMLEditUtils::GetInclusiveAncestorElement(
1381 *startContainer->AsContent(),
1382 HTMLEditUtils::ClosestEditableBlockElement,
1383 BlockInlineCheck::UseComputedDisplayOutsideStyle);
1384 if (!editableBlockElement) {
1385 break;
1388 // If selection is in a table element, we need special handling.
1389 if (HTMLEditUtils::IsAnyTableElement(editableBlockElement)) {
1390 Result<EditActionResult, nsresult> result =
1391 HandleTabKeyPressInTable(aKeyboardEvent);
1392 if (MOZ_UNLIKELY(result.isErr())) {
1393 NS_WARNING("HTMLEditor::HandleTabKeyPressInTable() failed");
1394 return EditorBase::ToGenericNSResult(result.unwrapErr());
1396 if (!result.inspect().Handled()) {
1397 return NS_OK;
1399 nsresult rv = ScrollSelectionFocusIntoView();
1400 NS_WARNING_ASSERTION(
1401 NS_SUCCEEDED(rv),
1402 "EditorBase::ScrollSelectionFocusIntoView() failed");
1403 return EditorBase::ToGenericNSResult(rv);
1406 // If selection is in an list item element, treat it as indent or outdent.
1407 if (HTMLEditUtils::IsListItem(editableBlockElement)) {
1408 aKeyboardEvent->PreventDefault();
1409 if (!aKeyboardEvent->IsShift()) {
1410 nsresult rv = IndentAsAction();
1411 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1412 "HTMLEditor::IndentAsAction() failed");
1413 return EditorBase::ToGenericNSResult(rv);
1415 nsresult rv = OutdentAsAction();
1416 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1417 "HTMLEditor::OutdentAsAction() failed");
1418 return EditorBase::ToGenericNSResult(rv);
1421 // If only "Tab" key is pressed in normal context, just treat it as
1422 // horizontal tab character input.
1423 if (aKeyboardEvent->IsShift()) {
1424 return NS_OK;
1426 aKeyboardEvent->PreventDefault();
1427 nsresult rv = OnInputText(u"\t"_ns);
1428 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1429 "EditorBase::OnInputText(\\t) failed");
1430 return EditorBase::ToGenericNSResult(rv);
1432 case NS_VK_RETURN:
1433 if (!aKeyboardEvent->IsInputtingLineBreak()) {
1434 return NS_OK;
1436 aKeyboardEvent->PreventDefault(); // consumed
1437 if (aKeyboardEvent->IsShift()) {
1438 // Only inserts a <br> element.
1439 nsresult rv = InsertLineBreakAsAction();
1440 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1441 "HTMLEditor::InsertLineBreakAsAction() failed");
1442 return EditorBase::ToGenericNSResult(rv);
1444 // uses rules to figure out what to insert
1445 nsresult rv = InsertParagraphSeparatorAsAction();
1446 NS_WARNING_ASSERTION(
1447 NS_SUCCEEDED(rv),
1448 "HTMLEditor::InsertParagraphSeparatorAsAction() failed");
1449 return EditorBase::ToGenericNSResult(rv);
1452 if (!aKeyboardEvent->IsInputtingText()) {
1453 // we don't PreventDefault() here or keybindings like control-x won't work
1454 return NS_OK;
1456 aKeyboardEvent->PreventDefault();
1457 // If we dispatch 2 keypress events for a surrogate pair and we set only
1458 // first `.key` value to the surrogate pair, the preceding one has it and the
1459 // other has empty string. In this case, we should handle only the first one
1460 // with the key value.
1461 if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() &&
1462 !StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() &&
1463 aKeyboardEvent->mKeyValue.IsEmpty() &&
1464 IS_SURROGATE(aKeyboardEvent->mCharCode)) {
1465 return NS_OK;
1467 nsAutoString str(aKeyboardEvent->mKeyValue);
1468 if (str.IsEmpty()) {
1469 str.Assign(static_cast<char16_t>(aKeyboardEvent->mCharCode));
1471 // FYI: DIfferent from TextEditor, we can treat \r (CR) as-is in HTMLEditor.
1472 nsresult rv = OnInputText(str);
1473 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed");
1474 return rv;
1477 NS_IMETHODIMP HTMLEditor::NodeIsBlock(nsINode* aNode, bool* aIsBlock) {
1478 if (NS_WARN_IF(!aNode)) {
1479 return NS_ERROR_INVALID_ARG;
1481 if (MOZ_UNLIKELY(!aNode->IsElement())) {
1482 *aIsBlock = false;
1483 return NS_OK;
1485 // If the node is in composed doc, we'll refer its style. If we don't flush
1486 // pending style here, another API call may change the style. Therefore,
1487 // let's flush the pending style changes right now.
1488 if (aNode->IsInComposedDoc()) {
1489 if (RefPtr<PresShell> presShell = GetPresShell()) {
1490 presShell->FlushPendingNotifications(FlushType::Style);
1493 *aIsBlock = HTMLEditUtils::IsBlockElement(
1494 *aNode->AsElement(), BlockInlineCheck::UseComputedDisplayOutsideStyle);
1495 return NS_OK;
1498 NS_IMETHODIMP HTMLEditor::UpdateBaseURL() {
1499 RefPtr<Document> document = GetDocument();
1500 if (NS_WARN_IF(!document)) {
1501 return NS_ERROR_FAILURE;
1504 // Look for an HTML <base> tag
1505 RefPtr<nsContentList> baseElementList =
1506 document->GetElementsByTagName(u"base"_ns);
1508 // If no base tag, then set baseURL to the document's URL. This is very
1509 // important, else relative URLs for links and images are wrong
1510 if (!baseElementList || !baseElementList->Item(0)) {
1511 document->SetBaseURI(document->GetDocumentURI());
1513 return NS_OK;
1516 NS_IMETHODIMP HTMLEditor::InsertLineBreak() {
1517 // XPCOM method's InsertLineBreak() should insert paragraph separator in
1518 // HTMLEditor.
1519 AutoEditActionDataSetter editActionData(
1520 *this, EditAction::eInsertParagraphSeparator);
1521 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1522 if (NS_FAILED(rv)) {
1523 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1524 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1525 return EditorBase::ToGenericNSResult(rv);
1528 const RefPtr<Element> editingHost = ComputeEditingHost();
1529 if (!editingHost) {
1530 return NS_SUCCESS_DOM_NO_OPERATION;
1533 Result<EditActionResult, nsresult> result =
1534 InsertParagraphSeparatorAsSubAction(*editingHost);
1535 if (MOZ_UNLIKELY(result.isErr())) {
1536 NS_WARNING("HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
1537 return EditorBase::ToGenericNSResult(result.unwrapErr());
1539 return NS_OK;
1542 nsresult HTMLEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) {
1543 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak,
1544 aPrincipal);
1545 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1546 if (NS_FAILED(rv)) {
1547 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1548 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1549 return EditorBase::ToGenericNSResult(rv);
1552 if (IsSelectionRangeContainerNotContent()) {
1553 return NS_SUCCESS_DOM_NO_OPERATION;
1556 rv = InsertLineBreakAsSubAction();
1557 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1558 "HTMLEditor::InsertLineBreakAsSubAction() failed");
1559 // Don't return NS_SUCCESS_DOM_NO_OPERATION for compatibility of `execCommand`
1560 // result of Chrome.
1561 return NS_FAILED(rv) ? rv : NS_OK;
1564 nsresult HTMLEditor::InsertParagraphSeparatorAsAction(
1565 nsIPrincipal* aPrincipal) {
1566 AutoEditActionDataSetter editActionData(
1567 *this, EditAction::eInsertParagraphSeparator, aPrincipal);
1568 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1569 if (NS_FAILED(rv)) {
1570 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1571 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1572 return EditorBase::ToGenericNSResult(rv);
1575 const RefPtr<Element> editingHost = ComputeEditingHost();
1576 if (!editingHost) {
1577 return NS_SUCCESS_DOM_NO_OPERATION;
1580 Result<EditActionResult, nsresult> result =
1581 InsertParagraphSeparatorAsSubAction(*editingHost);
1582 if (MOZ_UNLIKELY(result.isErr())) {
1583 NS_WARNING("HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
1584 return EditorBase::ToGenericNSResult(result.unwrapErr());
1586 return NS_OK;
1589 Result<EditActionResult, nsresult> HTMLEditor::HandleTabKeyPressInTable(
1590 WidgetKeyboardEvent* aKeyboardEvent) {
1591 MOZ_ASSERT(aKeyboardEvent);
1593 AutoEditActionDataSetter dummyEditActionData(*this, EditAction::eNotEditing);
1594 if (NS_WARN_IF(!dummyEditActionData.CanHandle())) {
1595 // Do nothing if we didn't find a table cell.
1596 return EditActionResult::IgnoredResult();
1599 // Find enclosing table cell from selection (cell may be selected element)
1600 const RefPtr<Element> cellElement =
1601 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
1602 if (!cellElement) {
1603 NS_WARNING(
1604 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td) "
1605 "returned nullptr");
1606 // Do nothing if we didn't find a table cell.
1607 return EditActionResult::IgnoredResult();
1610 // find enclosing table
1611 RefPtr<Element> table =
1612 HTMLEditUtils::GetClosestAncestorTableElement(*cellElement);
1613 if (!table) {
1614 NS_WARNING("HTMLEditor::GetClosestAncestorTableElement() failed");
1615 return EditActionResult::IgnoredResult();
1618 // advance to next cell
1619 // first create an iterator over the table
1620 PostContentIterator postOrderIter;
1621 nsresult rv = postOrderIter.Init(table);
1622 if (NS_FAILED(rv)) {
1623 NS_WARNING("PostContentIterator::Init() failed");
1624 return Err(rv);
1626 // position postOrderIter at block
1627 rv = postOrderIter.PositionAt(cellElement);
1628 if (NS_FAILED(rv)) {
1629 NS_WARNING("PostContentIterator::PositionAt() failed");
1630 return Err(rv);
1633 do {
1634 if (aKeyboardEvent->IsShift()) {
1635 postOrderIter.Prev();
1636 } else {
1637 postOrderIter.Next();
1640 nsCOMPtr<nsINode> node = postOrderIter.GetCurrentNode();
1641 if (node && HTMLEditUtils::IsTableCell(node) &&
1642 HTMLEditUtils::GetClosestAncestorTableElement(*node->AsElement()) ==
1643 table) {
1644 aKeyboardEvent->PreventDefault();
1645 CollapseSelectionToDeepestNonTableFirstChild(node);
1646 if (NS_WARN_IF(Destroyed())) {
1647 return Err(NS_ERROR_EDITOR_DESTROYED);
1649 return EditActionResult::HandledResult();
1651 } while (!postOrderIter.IsDone());
1653 if (aKeyboardEvent->IsShift()) {
1654 return EditActionResult::IgnoredResult();
1657 // If we haven't handled it yet, then we must have run off the end of the
1658 // table. Insert a new row.
1659 // XXX We should investigate whether this behavior is supported by other
1660 // browsers later.
1661 AutoEditActionDataSetter editActionData(*this,
1662 EditAction::eInsertTableRowElement);
1663 rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1664 if (NS_FAILED(rv)) {
1665 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1666 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1667 return Err(rv);
1669 rv = InsertTableRowsWithTransaction(*cellElement, 1,
1670 InsertPosition::eAfterSelectedCell);
1671 if (NS_WARN_IF(Destroyed())) {
1672 return Err(NS_ERROR_EDITOR_DESTROYED);
1674 if (NS_FAILED(rv)) {
1675 NS_WARNING(
1676 "HTMLEditor::InsertTableRowsWithTransaction(*cellElement, 1, "
1677 "InsertPosition::eAfterSelectedCell) failed");
1678 return Err(rv);
1680 aKeyboardEvent->PreventDefault();
1681 // Put selection in right place. Use table code to get selection and index
1682 // to new row...
1683 RefPtr<Element> tblElement, cell;
1684 int32_t row;
1685 rv = GetCellContext(getter_AddRefs(tblElement), getter_AddRefs(cell), nullptr,
1686 nullptr, &row, nullptr);
1687 if (NS_FAILED(rv)) {
1688 NS_WARNING("HTMLEditor::GetCellContext() failed");
1689 return Err(rv);
1691 if (!tblElement) {
1692 NS_WARNING("HTMLEditor::GetCellContext() didn't return table element");
1693 return Err(NS_ERROR_FAILURE);
1695 // ...so that we can ask for first cell in that row...
1696 cell = GetTableCellElementAt(*tblElement, row, 0);
1697 // ...and then set selection there. (Note that normally you should use
1698 // CollapseSelectionToDeepestNonTableFirstChild(), but we know cell is an
1699 // empty new cell, so this works fine)
1700 if (cell) {
1701 nsresult rv = CollapseSelectionToStartOf(*cell);
1702 if (NS_FAILED(rv)) {
1703 NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed");
1704 return Err(NS_ERROR_EDITOR_DESTROYED);
1707 if (NS_WARN_IF(Destroyed())) {
1708 return Err(NS_ERROR_EDITOR_DESTROYED);
1710 return EditActionResult::HandledResult();
1713 void HTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsINode* aNode) {
1714 MOZ_ASSERT(IsEditActionDataAvailable());
1716 MOZ_ASSERT(aNode);
1718 nsCOMPtr<nsINode> node = aNode;
1720 for (nsIContent* child = node->GetFirstChild(); child;
1721 child = child->GetFirstChild()) {
1722 // Stop if we find a table, don't want to go into nested tables
1723 if (HTMLEditUtils::IsTable(child) ||
1724 !HTMLEditUtils::IsContainerNode(*child)) {
1725 break;
1727 node = child;
1730 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(*node);
1731 NS_WARNING_ASSERTION(
1732 NS_SUCCEEDED(rvIgnored),
1733 "EditorBase::CollapseSelectionToStartOf() failed, but ignored");
1736 nsresult HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction(
1737 const nsAString& aSourceToInsert) {
1738 MOZ_ASSERT(IsEditActionDataAvailable());
1740 // don't do any post processing, rules get confused
1741 IgnoredErrorResult ignoredError;
1742 AutoEditSubActionNotifier startToHandleEditSubAction(
1743 *this, EditSubAction::eReplaceHeadWithHTMLSource, nsIEditor::eNone,
1744 ignoredError);
1745 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1746 return ignoredError.StealNSResult();
1748 NS_WARNING_ASSERTION(
1749 !ignoredError.Failed(),
1750 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1752 CommitComposition();
1754 // Do not use AutoEditSubActionNotifier -- rules code won't let us insert in
1755 // <head>. Use the head node as a parent and delete/insert directly.
1756 // XXX We're using AutoEditSubActionNotifier above...
1757 RefPtr<Document> document = GetDocument();
1758 if (NS_WARN_IF(!document)) {
1759 return NS_ERROR_NOT_INITIALIZED;
1762 RefPtr<nsContentList> headElementList =
1763 document->GetElementsByTagName(u"head"_ns);
1764 if (NS_WARN_IF(!headElementList)) {
1765 return NS_ERROR_FAILURE;
1768 RefPtr<Element> primaryHeadElement = headElementList->Item(0)->AsElement();
1769 if (NS_WARN_IF(!primaryHeadElement)) {
1770 return NS_ERROR_FAILURE;
1773 // First, make sure there are no return chars in the source. Bad things
1774 // happen if you insert returns (instead of dom newlines, \n) into an editor
1775 // document.
1776 nsAutoString inputString(aSourceToInsert);
1778 // Windows linebreaks: Map CRLF to LF:
1779 inputString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
1781 // Mac linebreaks: Map any remaining CR to LF:
1782 inputString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
1784 AutoPlaceholderBatch treatAsOneTransaction(
1785 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1787 // Get the first range in the selection, for context:
1788 RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0);
1789 if (NS_WARN_IF(!range)) {
1790 return NS_ERROR_FAILURE;
1793 ErrorResult error;
1794 RefPtr<DocumentFragment> documentFragment =
1795 range->CreateContextualFragment(inputString, error);
1797 // XXXX BUG 50965: This is not returning the text between <title>...</title>
1798 // Special code is needed in JS to handle title anyway, so it doesn't matter!
1800 if (error.Failed()) {
1801 NS_WARNING("nsRange::CreateContextualFragment() failed");
1802 return error.StealNSResult();
1804 if (NS_WARN_IF(!documentFragment)) {
1805 NS_WARNING(
1806 "nsRange::CreateContextualFragment() didn't create DocumentFragment");
1807 return NS_ERROR_FAILURE;
1810 // First delete all children in head
1811 while (nsCOMPtr<nsIContent> child = primaryHeadElement->GetFirstChild()) {
1812 nsresult rv = DeleteNodeWithTransaction(*child);
1813 if (NS_FAILED(rv)) {
1814 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1815 return rv;
1819 // Now insert the new nodes
1820 int32_t offsetOfNewNode = 0;
1822 // Loop over the contents of the fragment and move into the document
1823 while (nsCOMPtr<nsIContent> child = documentFragment->GetFirstChild()) {
1824 Result<CreateContentResult, nsresult> insertChildContentResult =
1825 InsertNodeWithTransaction(
1826 *child, EditorDOMPoint(primaryHeadElement, offsetOfNewNode++));
1827 if (MOZ_UNLIKELY(insertChildContentResult.isErr())) {
1828 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1829 return insertChildContentResult.unwrapErr();
1831 // We probably don't need to adjust selection here, although we've done it
1832 // unless AutoTransactionsConserveSelection is created in a caller.
1833 insertChildContentResult.inspect().IgnoreCaretPointSuggestion();
1836 return NS_OK;
1839 NS_IMETHODIMP HTMLEditor::RebuildDocumentFromSource(
1840 const nsAString& aSourceString) {
1841 CommitComposition();
1843 AutoEditActionDataSetter editActionData(*this, EditAction::eSetHTML);
1844 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1845 if (NS_FAILED(rv)) {
1846 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1847 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1848 return EditorBase::ToGenericNSResult(rv);
1851 RefPtr<Element> rootElement = GetRoot();
1852 if (NS_WARN_IF(!rootElement)) {
1853 return NS_ERROR_NULL_POINTER;
1856 // Find where the <body> tag starts.
1857 nsReadingIterator<char16_t> beginbody;
1858 nsReadingIterator<char16_t> endbody;
1859 aSourceString.BeginReading(beginbody);
1860 aSourceString.EndReading(endbody);
1861 bool foundbody =
1862 CaseInsensitiveFindInReadable(u"<body"_ns, beginbody, endbody);
1864 nsReadingIterator<char16_t> beginhead;
1865 nsReadingIterator<char16_t> endhead;
1866 aSourceString.BeginReading(beginhead);
1867 aSourceString.EndReading(endhead);
1868 bool foundhead =
1869 CaseInsensitiveFindInReadable(u"<head"_ns, beginhead, endhead);
1870 // a valid head appears before the body
1871 if (foundbody && beginhead.get() > beginbody.get()) {
1872 foundhead = false;
1875 nsReadingIterator<char16_t> beginclosehead;
1876 nsReadingIterator<char16_t> endclosehead;
1877 aSourceString.BeginReading(beginclosehead);
1878 aSourceString.EndReading(endclosehead);
1880 // Find the index after "<head>"
1881 bool foundclosehead = CaseInsensitiveFindInReadable(
1882 u"</head>"_ns, beginclosehead, endclosehead);
1883 // a valid close head appears after a found head
1884 if (foundhead && beginhead.get() > beginclosehead.get()) {
1885 foundclosehead = false;
1887 // a valid close head appears before a found body
1888 if (foundbody && beginclosehead.get() > beginbody.get()) {
1889 foundclosehead = false;
1892 // Time to change the document
1893 AutoPlaceholderBatch treatAsOneTransaction(
1894 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1896 nsReadingIterator<char16_t> endtotal;
1897 aSourceString.EndReading(endtotal);
1899 if (foundhead) {
1900 if (foundclosehead) {
1901 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1902 Substring(beginhead, beginclosehead));
1903 if (NS_FAILED(rv)) {
1904 NS_WARNING(
1905 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1906 "failed");
1907 return rv;
1909 } else if (foundbody) {
1910 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1911 Substring(beginhead, beginbody));
1912 if (NS_FAILED(rv)) {
1913 NS_WARNING(
1914 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1915 "failed");
1916 return rv;
1918 } else {
1919 // XXX Without recourse to some parser/content sink/docshell hackery we
1920 // don't really know where the head ends and the body begins so we assume
1921 // that there is no body
1922 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1923 Substring(beginhead, endtotal));
1924 if (NS_FAILED(rv)) {
1925 NS_WARNING(
1926 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1927 "failed");
1928 return rv;
1931 } else {
1932 nsReadingIterator<char16_t> begintotal;
1933 aSourceString.BeginReading(begintotal);
1934 constexpr auto head = u"<head>"_ns;
1935 if (foundclosehead) {
1936 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1937 head + Substring(begintotal, beginclosehead));
1938 if (NS_FAILED(rv)) {
1939 NS_WARNING(
1940 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1941 "failed");
1942 return rv;
1944 } else if (foundbody) {
1945 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1946 head + Substring(begintotal, beginbody));
1947 if (NS_FAILED(rv)) {
1948 NS_WARNING(
1949 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1950 "failed");
1951 return rv;
1953 } else {
1954 // XXX Without recourse to some parser/content sink/docshell hackery we
1955 // don't really know where the head ends and the body begins so we assume
1956 // that there is no head
1957 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(head);
1958 if (NS_FAILED(rv)) {
1959 NS_WARNING(
1960 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1961 "failed");
1962 return rv;
1967 rv = SelectAll();
1968 if (NS_FAILED(rv)) {
1969 NS_WARNING("EditorBase::SelectAll() failed");
1970 return rv;
1973 if (!foundbody) {
1974 constexpr auto body = u"<body>"_ns;
1975 // XXX Without recourse to some parser/content sink/docshell hackery we
1976 // don't really know where the head ends and the body begins
1977 if (foundclosehead) {
1978 // assume body starts after the head ends
1979 nsresult rv = LoadHTML(body + Substring(endclosehead, endtotal));
1980 if (NS_FAILED(rv)) {
1981 NS_WARNING("HTMLEditor::LoadHTML() failed");
1982 return rv;
1984 } else if (foundhead) {
1985 // assume there is no body
1986 nsresult rv = LoadHTML(body);
1987 if (NS_FAILED(rv)) {
1988 NS_WARNING("HTMLEditor::LoadHTML() failed");
1989 return rv;
1991 } else {
1992 // assume there is no head, the entire source is body
1993 nsresult rv = LoadHTML(body + aSourceString);
1994 if (NS_FAILED(rv)) {
1995 NS_WARNING("HTMLEditor::LoadHTML() failed");
1996 return rv;
2000 RefPtr<Element> divElement = CreateElementWithDefaults(*nsGkAtoms::div);
2001 if (!divElement) {
2002 NS_WARNING(
2003 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::div) failed");
2004 return NS_ERROR_FAILURE;
2006 CloneAttributesWithTransaction(*rootElement, *divElement);
2008 nsresult rv = MaybeCollapseSelectionAtFirstEditableNode(false);
2009 NS_WARNING_ASSERTION(
2010 NS_SUCCEEDED(rv),
2011 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
2012 return rv;
2015 rv = LoadHTML(Substring(beginbody, endtotal));
2016 if (NS_FAILED(rv)) {
2017 NS_WARNING("HTMLEditor::LoadHTML() failed");
2018 return rv;
2021 // Now we must copy attributes user might have edited on the <body> tag
2022 // because InsertHTML (actually, CreateContextualFragment()) will never
2023 // return a body node in the DOM fragment
2025 // We already know where "<body" begins
2026 nsReadingIterator<char16_t> beginclosebody = beginbody;
2027 nsReadingIterator<char16_t> endclosebody;
2028 aSourceString.EndReading(endclosebody);
2029 if (!FindInReadable(u">"_ns, beginclosebody, endclosebody)) {
2030 NS_WARNING("'>' was not found");
2031 return NS_ERROR_FAILURE;
2034 // Truncate at the end of the body tag. Kludge of the year: fool the parser
2035 // by replacing "body" with "div" so we get a node
2036 nsAutoString bodyTag;
2037 bodyTag.AssignLiteral("<div ");
2038 bodyTag.Append(Substring(endbody, endclosebody));
2040 RefPtr<const nsRange> range = SelectionRef().GetRangeAt(0);
2041 if (NS_WARN_IF(!range)) {
2042 return NS_ERROR_FAILURE;
2045 ErrorResult error;
2046 RefPtr<DocumentFragment> documentFragment =
2047 range->CreateContextualFragment(bodyTag, error);
2048 if (error.Failed()) {
2049 NS_WARNING("nsRange::CreateContextualFragment() failed");
2050 return error.StealNSResult();
2052 if (!documentFragment) {
2053 NS_WARNING(
2054 "nsRange::CreateContextualFragment() didn't create DocumentFagement");
2055 return NS_ERROR_FAILURE;
2058 nsCOMPtr<nsIContent> firstChild = documentFragment->GetFirstChild();
2059 if (!firstChild || !firstChild->IsElement()) {
2060 NS_WARNING("First child of DocumentFragment was not an Element node");
2061 return NS_ERROR_FAILURE;
2064 // Copy all attributes from the div child to current body element
2065 CloneAttributesWithTransaction(*rootElement,
2066 MOZ_KnownLive(*firstChild->AsElement()));
2068 // place selection at first editable content
2069 rv = MaybeCollapseSelectionAtFirstEditableNode(false);
2070 NS_WARNING_ASSERTION(
2071 NS_SUCCEEDED(rv),
2072 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
2073 return rv;
2076 NS_IMETHODIMP HTMLEditor::InsertElementAtSelection(Element* aElement,
2077 bool aDeleteSelection) {
2078 nsresult rv = InsertElementAtSelectionAsAction(aElement, aDeleteSelection);
2079 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2080 "HTMLEditor::InsertElementAtSelectionAsAction() failed");
2081 return rv;
2084 nsresult HTMLEditor::InsertElementAtSelectionAsAction(
2085 Element* aElement, bool aDeleteSelection, nsIPrincipal* aPrincipal) {
2086 if (NS_WARN_IF(!aElement)) {
2087 return NS_ERROR_INVALID_ARG;
2090 if (IsReadonly()) {
2091 return NS_OK;
2094 AutoEditActionDataSetter editActionData(
2095 *this, HTMLEditUtils::GetEditActionForInsert(*aElement), aPrincipal);
2096 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2097 if (NS_FAILED(rv)) {
2098 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2099 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2100 return EditorBase::ToGenericNSResult(rv);
2103 DebugOnly<nsresult> rvIgnored = CommitComposition();
2104 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2105 "EditorBase::CommitComposition() failed, but ignored");
2108 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
2109 if (MOZ_UNLIKELY(result.isErr())) {
2110 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
2111 return EditorBase::ToGenericNSResult(result.unwrapErr());
2113 if (result.inspect().Canceled()) {
2114 return NS_OK;
2118 UndefineCaretBidiLevel();
2120 AutoPlaceholderBatch treatAsOneTransaction(
2121 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2122 IgnoredErrorResult ignoredError;
2123 AutoEditSubActionNotifier startToHandleEditSubAction(
2124 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
2125 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2126 return ignoredError.StealNSResult();
2128 NS_WARNING_ASSERTION(
2129 !ignoredError.Failed(),
2130 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2132 rv = EnsureNoPaddingBRElementForEmptyEditor();
2133 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2134 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2136 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2137 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
2138 "failed, but ignored");
2140 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
2141 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
2142 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2143 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2145 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2146 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
2147 "failed, but ignored");
2148 if (NS_SUCCEEDED(rv)) {
2149 nsresult rv = PrepareInlineStylesForCaret();
2150 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2151 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2153 NS_WARNING_ASSERTION(
2154 NS_SUCCEEDED(rv),
2155 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2159 if (aDeleteSelection) {
2160 if (!HTMLEditUtils::IsBlockElement(
2161 *aElement, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
2162 // E.g., inserting an image. In this case we don't need to delete any
2163 // inline wrappers before we do the insertion. Otherwise we let
2164 // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
2165 // calls DeleteSelection with aStripWrappers = eStrip.
2166 nsresult rv = DeleteSelectionAsSubAction(eNone, eNoStrip);
2167 if (NS_FAILED(rv)) {
2168 NS_WARNING(
2169 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
2170 return EditorBase::ToGenericNSResult(rv);
2174 nsresult rv = DeleteSelectionAndPrepareToCreateNode();
2175 if (NS_FAILED(rv)) {
2176 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
2177 return rv;
2180 // If deleting, selection will be collapsed.
2181 // so if not, we collapse it
2182 else {
2183 // Named Anchor is a special case,
2184 // We collapse to insert element BEFORE the selection
2185 // For all other tags, we insert AFTER the selection
2186 if (HTMLEditUtils::IsNamedAnchor(aElement)) {
2187 IgnoredErrorResult ignoredError;
2188 SelectionRef().CollapseToStart(ignoredError);
2189 if (NS_WARN_IF(Destroyed())) {
2190 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2192 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2193 "Selection::CollapseToStart() failed, but ignored");
2194 } else {
2195 IgnoredErrorResult ignoredError;
2196 SelectionRef().CollapseToEnd(ignoredError);
2197 if (NS_WARN_IF(Destroyed())) {
2198 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2200 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2201 "Selection::CollapseToEnd() failed, but ignored");
2205 if (!SelectionRef().GetAnchorNode()) {
2206 return NS_OK;
2209 Element* editingHost = ComputeEditingHost(LimitInBodyElement::No);
2210 if (NS_WARN_IF(!editingHost)) {
2211 return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE);
2214 EditorRawDOMPoint atAnchor(SelectionRef().AnchorRef());
2215 // Adjust position based on the node we are going to insert.
2216 EditorDOMPoint pointToInsert =
2217 HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>(
2218 *aElement, atAnchor, *editingHost);
2219 if (!pointToInsert.IsSet()) {
2220 NS_WARNING("HTMLEditUtils::GetBetterInsertionPointFor() failed");
2221 return NS_ERROR_FAILURE;
2225 Result<CreateElementResult, nsresult> insertElementResult =
2226 InsertNodeIntoProperAncestorWithTransaction<Element>(
2227 *aElement, pointToInsert,
2228 SplitAtEdges::eAllowToCreateEmptyContainer);
2229 if (MOZ_UNLIKELY(insertElementResult.isErr())) {
2230 NS_WARNING(
2231 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction("
2232 "SplitAtEdges::eAllowToCreateEmptyContainer) failed");
2233 return EditorBase::ToGenericNSResult(insertElementResult.unwrapErr());
2235 insertElementResult.inspect().IgnoreCaretPointSuggestion();
2237 // Set caret after element, but check for special case
2238 // of inserting table-related elements: set in first cell instead
2239 if (!SetCaretInTableCell(aElement)) {
2240 if (NS_WARN_IF(Destroyed())) {
2241 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
2243 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint::After(*aElement));
2244 if (NS_FAILED(rv)) {
2245 NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
2246 return EditorBase::ToGenericNSResult(rv);
2250 // check for inserting a whole table at the end of a block. If so insert
2251 // a br after it.
2252 if (!HTMLEditUtils::IsTable(aElement) ||
2253 !HTMLEditUtils::IsLastChild(*aElement,
2254 {WalkTreeOption::IgnoreNonEditableNode})) {
2255 return NS_OK;
2258 const auto afterElement = EditorDOMPoint::After(*aElement);
2259 // Collapse selection to the new `<br>` element node after creating it.
2260 Result<CreateElementResult, nsresult> insertBRElementResult =
2261 InsertBRElement(WithTransaction::Yes, afterElement, ePrevious);
2262 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
2263 NS_WARNING(
2264 "HTMLEditor::InsertBRElement(WithTransaction::Yes, ePrevious) failed");
2265 return EditorBase::ToGenericNSResult(insertBRElementResult.unwrapErr());
2267 rv = insertBRElementResult.inspect().SuggestCaretPointTo(*this, {});
2268 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2269 "EditorBase::CollapseSelectionTo() failed");
2270 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
2271 return EditorBase::ToGenericNSResult(rv);
2274 template <typename NodeType>
2275 Result<CreateNodeResultBase<NodeType>, nsresult>
2276 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(
2277 NodeType& aContentToInsert, const EditorDOMPoint& aPointToInsert,
2278 SplitAtEdges aSplitAtEdges) {
2279 MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc());
2280 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
2281 return Err(NS_ERROR_FAILURE);
2283 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
2285 if (aContentToInsert.NodeType() == nsINode::DOCUMENT_TYPE_NODE ||
2286 aContentToInsert.NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) {
2287 return CreateNodeResultBase<NodeType>::NotHandled();
2290 // Search up the parent chain to find a suitable container.
2291 EditorDOMPoint pointToInsert(aPointToInsert);
2292 MOZ_ASSERT(pointToInsert.IsSet());
2293 while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
2294 aContentToInsert)) {
2295 // If the current parent is a root (body or table element)
2296 // then go no further - we can't insert.
2297 if (MOZ_UNLIKELY(
2298 pointToInsert.IsContainerHTMLElement(nsGkAtoms::body) ||
2299 HTMLEditUtils::IsAnyTableElement(pointToInsert.GetContainer()))) {
2300 NS_WARNING(
2301 "There was no proper container element to insert the content node in "
2302 "the document");
2303 return Err(NS_ERROR_FAILURE);
2306 // Get the next point.
2307 pointToInsert = pointToInsert.ParentPoint();
2309 if (MOZ_UNLIKELY(
2310 !pointToInsert.IsInContentNode() ||
2311 !EditorUtils::IsEditableContent(
2312 *pointToInsert.ContainerAs<nsIContent>(), EditorType::HTML))) {
2313 NS_WARNING(
2314 "There was no proper container element to insert the content node in "
2315 "the editing host");
2316 return Err(NS_ERROR_FAILURE);
2320 if (pointToInsert != aPointToInsert) {
2321 // We need to split some levels above the original selection parent.
2322 MOZ_ASSERT(pointToInsert.GetChild());
2323 Result<SplitNodeResult, nsresult> splitNodeResult =
2324 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
2325 aPointToInsert, aSplitAtEdges);
2326 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
2327 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
2328 return splitNodeResult.propagateErr();
2330 pointToInsert =
2331 splitNodeResult.inspect().template AtSplitPoint<EditorDOMPoint>();
2332 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc());
2333 // Caret should be set by the caller of this method so that we don't
2334 // need to handle it here.
2335 splitNodeResult.inspect().IgnoreCaretPointSuggestion();
2338 // Now we can insert the new node.
2339 Result<CreateNodeResultBase<NodeType>, nsresult> insertContentNodeResult =
2340 InsertNodeWithTransaction<NodeType>(aContentToInsert, pointToInsert);
2341 if (MOZ_LIKELY(insertContentNodeResult.isOk()) &&
2342 MOZ_UNLIKELY(NS_WARN_IF(!aContentToInsert.GetParentNode()) ||
2343 NS_WARN_IF(aContentToInsert.GetParentNode() !=
2344 pointToInsert.GetContainer()))) {
2345 NS_WARNING(
2346 "EditorBase::InsertNodeWithTransaction() succeeded, but the inserted "
2347 "node was moved or removed by the web app");
2348 insertContentNodeResult.inspect().IgnoreCaretPointSuggestion();
2349 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2351 NS_WARNING_ASSERTION(insertContentNodeResult.isOk(),
2352 "EditorBase::InsertNodeWithTransaction() failed");
2353 return insertContentNodeResult;
2356 NS_IMETHODIMP HTMLEditor::SelectElement(Element* aElement) {
2357 if (NS_WARN_IF(!aElement)) {
2358 return NS_ERROR_INVALID_ARG;
2361 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2362 if (NS_WARN_IF(!editActionData.CanHandle())) {
2363 return NS_ERROR_NOT_INITIALIZED;
2366 nsresult rv = SelectContentInternal(MOZ_KnownLive(*aElement));
2367 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2368 "HTMLEditor::SelectContentInternal() failed");
2369 return rv;
2372 nsresult HTMLEditor::SelectContentInternal(nsIContent& aContentToSelect) {
2373 MOZ_ASSERT(IsEditActionDataAvailable());
2375 // Must be sure that element is contained in the editing host
2376 const RefPtr<Element> editingHost = ComputeEditingHost();
2377 if (NS_WARN_IF(!editingHost) ||
2378 NS_WARN_IF(!aContentToSelect.IsInclusiveDescendantOf(editingHost))) {
2379 return NS_ERROR_FAILURE;
2382 EditorRawDOMPoint newSelectionStart(&aContentToSelect);
2383 if (NS_WARN_IF(!newSelectionStart.IsSet())) {
2384 return NS_ERROR_FAILURE;
2386 EditorRawDOMPoint newSelectionEnd(EditorRawDOMPoint::After(aContentToSelect));
2387 MOZ_ASSERT(newSelectionEnd.IsSet());
2388 ErrorResult error;
2389 SelectionRef().SetStartAndEndInLimiter(newSelectionStart, newSelectionEnd,
2390 error);
2391 NS_WARNING_ASSERTION(!error.Failed(),
2392 "Selection::SetStartAndEndInLimiter() failed");
2393 return error.StealNSResult();
2396 nsresult HTMLEditor::AppendContentToSelectionAsRange(nsIContent& aContent) {
2397 MOZ_ASSERT(IsEditActionDataAvailable());
2399 EditorRawDOMPoint atContent(&aContent);
2400 if (NS_WARN_IF(!atContent.IsSet())) {
2401 return NS_ERROR_FAILURE;
2404 RefPtr<nsRange> range = nsRange::Create(
2405 atContent.ToRawRangeBoundary(),
2406 atContent.NextPoint().ToRawRangeBoundary(), IgnoreErrors());
2407 if (NS_WARN_IF(!range)) {
2408 NS_WARNING("nsRange::Create() failed");
2409 return NS_ERROR_FAILURE;
2412 ErrorResult error;
2413 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error);
2414 if (NS_WARN_IF(Destroyed())) {
2415 if (error.Failed()) {
2416 error.SuppressException();
2418 return NS_ERROR_EDITOR_DESTROYED;
2420 NS_WARNING_ASSERTION(!error.Failed(), "Failed to add range to Selection");
2421 return error.StealNSResult();
2424 nsresult HTMLEditor::ClearSelection() {
2425 MOZ_ASSERT(IsEditActionDataAvailable());
2427 ErrorResult error;
2428 SelectionRef().RemoveAllRanges(error);
2429 if (NS_WARN_IF(Destroyed())) {
2430 if (error.Failed()) {
2431 error.SuppressException();
2433 return NS_ERROR_EDITOR_DESTROYED;
2435 NS_WARNING_ASSERTION(!error.Failed(), "Selection::RemoveAllRanges() failed");
2436 return error.StealNSResult();
2439 nsresult HTMLEditor::FormatBlockAsAction(const nsAString& aParagraphFormat,
2440 nsIPrincipal* aPrincipal) {
2441 AutoEditActionDataSetter editActionData(
2442 *this, EditAction::eInsertBlockElement, aPrincipal);
2443 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2444 if (NS_FAILED(rv)) {
2445 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2446 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2447 return EditorBase::ToGenericNSResult(rv);
2450 if (NS_WARN_IF(aParagraphFormat.IsEmpty())) {
2451 return NS_ERROR_INVALID_ARG;
2454 RefPtr<nsAtom> tagName = NS_Atomize(aParagraphFormat);
2455 MOZ_ASSERT(tagName);
2456 if (NS_WARN_IF(!tagName->IsStatic()) ||
2457 NS_WARN_IF(!HTMLEditUtils::IsFormatTagForFormatBlockCommand(
2458 *tagName->AsStatic()))) {
2459 return NS_ERROR_INVALID_ARG;
2462 if (tagName == nsGkAtoms::dd || tagName == nsGkAtoms::dt) {
2463 // MOZ_KnownLive(tagName->AsStatic()) because nsStaticAtom instances live
2464 // while the process is running.
2465 Result<EditActionResult, nsresult> result =
2466 MakeOrChangeListAndListItemAsSubAction(
2467 MOZ_KnownLive(*tagName->AsStatic()), u""_ns,
2468 SelectAllOfCurrentList::No);
2469 if (MOZ_UNLIKELY(result.isErr())) {
2470 NS_WARNING(
2471 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction("
2472 "SelectAllOfCurrentList::No) failed");
2473 return EditorBase::ToGenericNSResult(result.unwrapErr());
2475 return NS_OK;
2478 rv = FormatBlockContainerAsSubAction(MOZ_KnownLive(*tagName->AsStatic()),
2479 FormatBlockMode::HTMLFormatBlockCommand);
2480 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2481 "HTMLEditor::FormatBlockContainerAsSubAction() failed");
2482 return EditorBase::ToGenericNSResult(rv);
2485 nsresult HTMLEditor::SetParagraphStateAsAction(
2486 const nsAString& aParagraphFormat, nsIPrincipal* aPrincipal) {
2487 AutoEditActionDataSetter editActionData(
2488 *this, EditAction::eInsertBlockElement, aPrincipal);
2489 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2490 if (NS_FAILED(rv)) {
2491 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2492 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2493 return EditorBase::ToGenericNSResult(rv);
2496 // TODO: Computing the editing host here makes the `execCommand` in
2497 // docshell/base/crashtests/file_432114-2.xhtml cannot run
2498 // `DOMNodeRemoved` event listener with deleting the bogus <br> element.
2499 // So that it should be rewritten with different mutation event listener
2500 // since we'd like to stop using it.
2502 nsAutoString lowerCaseTagName(aParagraphFormat);
2503 ToLowerCase(lowerCaseTagName);
2504 RefPtr<nsAtom> tagName = NS_Atomize(lowerCaseTagName);
2505 MOZ_ASSERT(tagName);
2506 if (NS_WARN_IF(!tagName->IsStatic())) {
2507 return NS_ERROR_INVALID_ARG;
2509 if (tagName == nsGkAtoms::dd || tagName == nsGkAtoms::dt) {
2510 // MOZ_KnownLive(tagName->AsStatic()) because nsStaticAtom instances live
2511 // while the process is running.
2512 Result<EditActionResult, nsresult> result =
2513 MakeOrChangeListAndListItemAsSubAction(
2514 MOZ_KnownLive(*tagName->AsStatic()), u""_ns,
2515 SelectAllOfCurrentList::No);
2516 if (MOZ_UNLIKELY(result.isErr())) {
2517 NS_WARNING(
2518 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction("
2519 "SelectAllOfCurrentList::No) failed");
2520 return EditorBase::ToGenericNSResult(result.unwrapErr());
2522 return NS_OK;
2525 rv = FormatBlockContainerAsSubAction(
2526 MOZ_KnownLive(*tagName->AsStatic()),
2527 FormatBlockMode::XULParagraphStateCommand);
2528 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2529 "HTMLEditor::FormatBlockContainerAsSubAction() failed");
2530 return EditorBase::ToGenericNSResult(rv);
2533 // static
2534 bool HTMLEditor::IsFormatElement(FormatBlockMode aFormatBlockMode,
2535 const nsIContent& aContent) {
2536 // FYI: Optimize for HTML command because it may run too many times.
2537 return MOZ_LIKELY(aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand)
2538 ? HTMLEditUtils::IsFormatElementForFormatBlockCommand(aContent)
2539 : (HTMLEditUtils::IsFormatElementForParagraphStateCommand(
2540 aContent) &&
2541 // XXX The XUL paragraph state command treats <dl>, <dd> and
2542 // <dt> elements but all handlers do not treat them as a format
2543 // node. Therefore, we keep the traditional behavior here.
2544 !aContent.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dl,
2545 nsGkAtoms::dt));
2548 NS_IMETHODIMP HTMLEditor::GetParagraphState(bool* aMixed,
2549 nsAString& aFirstParagraphState) {
2550 if (NS_WARN_IF(!aMixed)) {
2551 return NS_ERROR_INVALID_ARG;
2553 if (!mInitSucceeded) {
2554 return NS_ERROR_NOT_INITIALIZED;
2557 ErrorResult error;
2558 ParagraphStateAtSelection paragraphState(
2559 *this, FormatBlockMode::XULParagraphStateCommand, error);
2560 if (error.Failed()) {
2561 NS_WARNING("ParagraphStateAtSelection failed");
2562 return error.StealNSResult();
2565 *aMixed = paragraphState.IsMixed();
2566 if (NS_WARN_IF(!paragraphState.GetFirstParagraphStateAtSelection())) {
2567 // XXX Odd result, but keep this behavior for now...
2568 aFirstParagraphState.AssignASCII("x");
2569 } else {
2570 paragraphState.GetFirstParagraphStateAtSelection()->ToString(
2571 aFirstParagraphState);
2573 return NS_OK;
2576 nsresult HTMLEditor::GetBackgroundColorState(bool* aMixed,
2577 nsAString& aOutColor) {
2578 if (NS_WARN_IF(!aMixed)) {
2579 return NS_ERROR_INVALID_ARG;
2582 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2583 if (NS_WARN_IF(!editActionData.CanHandle())) {
2584 return NS_ERROR_NOT_INITIALIZED;
2587 if (IsCSSEnabled()) {
2588 // if we are in CSS mode, we have to check if the containing block defines
2589 // a background color
2590 nsresult rv = GetCSSBackgroundColorState(aMixed, aOutColor, true);
2591 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2592 "HTMLEditor::GetCSSBackgroundColorState() failed");
2593 return EditorBase::ToGenericNSResult(rv);
2595 // in HTML mode, we look only at page's background
2596 nsresult rv = GetHTMLBackgroundColorState(aMixed, aOutColor);
2597 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2598 "HTMLEditor::GetCSSBackgroundColorState() failed");
2599 return EditorBase::ToGenericNSResult(rv);
2602 NS_IMETHODIMP HTMLEditor::GetHighlightColorState(bool* aMixed,
2603 nsAString& aOutColor) {
2604 if (NS_WARN_IF(!aMixed)) {
2605 return NS_ERROR_INVALID_ARG;
2608 *aMixed = false;
2609 aOutColor.AssignLiteral("transparent");
2610 if (!IsCSSEnabled()) {
2611 return NS_OK;
2614 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2615 if (NS_WARN_IF(!editActionData.CanHandle())) {
2616 return NS_ERROR_NOT_INITIALIZED;
2619 // in CSS mode, text background can be added by the Text Highlight button
2620 // we need to query the background of the selection without looking for
2621 // the block container of the ranges in the selection
2622 nsresult rv = GetCSSBackgroundColorState(aMixed, aOutColor, false);
2623 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2624 "HTMLEditor::GetCSSBackgroundColorState() failed");
2625 return rv;
2628 nsresult HTMLEditor::GetCSSBackgroundColorState(bool* aMixed,
2629 nsAString& aOutColor,
2630 bool aBlockLevel) {
2631 MOZ_ASSERT(IsEditActionDataAvailable());
2633 if (NS_WARN_IF(!aMixed)) {
2634 return NS_ERROR_INVALID_ARG;
2637 *aMixed = false;
2638 // the default background color is transparent
2639 aOutColor.AssignLiteral("transparent");
2641 RefPtr<const nsRange> firstRange = SelectionRef().GetRangeAt(0);
2642 if (NS_WARN_IF(!firstRange)) {
2643 return NS_ERROR_FAILURE;
2646 nsCOMPtr<nsINode> startContainer = firstRange->GetStartContainer();
2647 if (NS_WARN_IF(!startContainer) || NS_WARN_IF(!startContainer->IsContent())) {
2648 return NS_ERROR_FAILURE;
2651 // is the selection collapsed?
2652 nsIContent* contentToExamine;
2653 if (SelectionRef().IsCollapsed() || startContainer->IsText()) {
2654 if (NS_WARN_IF(!startContainer->IsContent())) {
2655 return NS_ERROR_FAILURE;
2657 // we want to look at the startContainer and ancestors
2658 contentToExamine = startContainer->AsContent();
2659 } else {
2660 // otherwise we want to look at the first editable node after
2661 // {startContainer,offset} and its ancestors for divs with alignment on them
2662 contentToExamine = firstRange->GetChildAtStartOffset();
2663 // GetNextNode(startContainer, offset, true, address_of(contentToExamine));
2666 if (NS_WARN_IF(!contentToExamine)) {
2667 return NS_ERROR_FAILURE;
2670 if (aBlockLevel) {
2671 // we are querying the block background (and not the text background), let's
2672 // climb to the block container. Note that background color of ancestor
2673 // of editing host may be what the caller wants to know. Therefore, we
2674 // should ignore the editing host boundaries.
2675 Element* const closestBlockElement =
2676 HTMLEditUtils::GetInclusiveAncestorElement(
2677 *contentToExamine, HTMLEditUtils::ClosestBlockElement,
2678 BlockInlineCheck::UseComputedDisplayOutsideStyle);
2679 if (NS_WARN_IF(!closestBlockElement)) {
2680 return NS_OK;
2683 for (RefPtr<Element> blockElement = closestBlockElement; blockElement;) {
2684 RefPtr<Element> nextBlockElement = HTMLEditUtils::GetAncestorElement(
2685 *blockElement, HTMLEditUtils::ClosestBlockElement,
2686 BlockInlineCheck::UseComputedDisplayOutsideStyle);
2687 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
2688 *blockElement, *nsGkAtoms::backgroundColor, aOutColor);
2689 if (NS_WARN_IF(Destroyed())) {
2690 return NS_ERROR_EDITOR_DESTROYED;
2692 if (MayHaveMutationEventListeners() &&
2693 NS_WARN_IF(nextBlockElement !=
2694 HTMLEditUtils::GetAncestorElement(
2695 *blockElement, HTMLEditUtils::ClosestBlockElement,
2696 BlockInlineCheck::UseComputedDisplayOutsideStyle))) {
2697 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2699 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2700 "CSSEditUtils::GetComputedProperty(nsGkAtoms::"
2701 "backgroundColor) failed, but ignored");
2702 // look at parent if the queried color is transparent and if the node to
2703 // examine is not the root of the document
2704 if (!aOutColor.EqualsLiteral("transparent") &&
2705 !aOutColor.EqualsLiteral("rgba(0, 0, 0, 0)")) {
2706 break;
2708 blockElement = std::move(nextBlockElement);
2711 if (aOutColor.EqualsLiteral("transparent") ||
2712 aOutColor.EqualsLiteral("rgba(0, 0, 0, 0)")) {
2713 // we have hit the root of the document and the color is still transparent
2714 // ! Grumble... Let's look at the default background color because that's
2715 // the color we are looking for
2716 CSSEditUtils::GetDefaultBackgroundColor(aOutColor);
2718 } else {
2719 // no, we are querying the text background for the Text Highlight button
2720 if (contentToExamine->IsText()) {
2721 // if the node of interest is a text node, let's climb a level
2722 contentToExamine = contentToExamine->GetParent();
2724 // Return default value due to no parent node
2725 if (!contentToExamine) {
2726 return NS_OK;
2729 for (RefPtr<Element> element =
2730 contentToExamine->GetAsElementOrParentElement();
2731 element; element = element->GetParentElement()) {
2732 // is the node to examine a block ?
2733 if (HTMLEditUtils::IsBlockElement(
2734 *element, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
2735 // yes it is a block; in that case, the text background color is
2736 // transparent
2737 aOutColor.AssignLiteral("transparent");
2738 break;
2741 // no, it's not; let's retrieve the computed style of background-color
2742 // for the node to examine
2743 nsCOMPtr<nsINode> parentNode = element->GetParentNode();
2744 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
2745 *element, *nsGkAtoms::backgroundColor, aOutColor);
2746 if (NS_WARN_IF(Destroyed())) {
2747 return NS_ERROR_EDITOR_DESTROYED;
2749 if (NS_WARN_IF(parentNode != element->GetParentNode())) {
2750 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2752 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2753 "CSSEditUtils::GetComputedProperty(nsGkAtoms::"
2754 "backgroundColor) failed, but ignored");
2755 if (!aOutColor.EqualsLiteral("transparent")) {
2756 break;
2760 return NS_OK;
2763 nsresult HTMLEditor::GetHTMLBackgroundColorState(bool* aMixed,
2764 nsAString& aOutColor) {
2765 MOZ_ASSERT(IsEditActionDataAvailable());
2767 // TODO: We don't handle "mixed" correctly!
2768 if (NS_WARN_IF(!aMixed)) {
2769 return NS_ERROR_INVALID_ARG;
2772 *aMixed = false;
2773 aOutColor.Truncate();
2775 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
2776 GetSelectedOrParentTableElement();
2777 if (cellOrRowOrTableElementOrError.isErr()) {
2778 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() returned error");
2779 return cellOrRowOrTableElementOrError.unwrapErr();
2782 for (RefPtr<Element> element = cellOrRowOrTableElementOrError.unwrap();
2783 element; element = element->GetParentElement()) {
2784 // We are in a cell or selected table
2785 element->GetAttr(nsGkAtoms::bgcolor, aOutColor);
2787 // Done if we have a color explicitly set
2788 if (!aOutColor.IsEmpty()) {
2789 return NS_OK;
2792 // Once we hit the body, we're done
2793 if (element->IsHTMLElement(nsGkAtoms::body)) {
2794 return NS_OK;
2797 // No color is set, but we need to report visible color inherited
2798 // from nested cells/tables, so search up parent chain so that
2799 // let's keep checking the ancestors.
2802 // If no table or cell found, get page body
2803 Element* rootElement = GetRoot();
2804 if (NS_WARN_IF(!rootElement)) {
2805 return NS_ERROR_FAILURE;
2808 rootElement->GetAttr(nsGkAtoms::bgcolor, aOutColor);
2809 return NS_OK;
2812 NS_IMETHODIMP HTMLEditor::GetListState(bool* aMixed, bool* aOL, bool* aUL,
2813 bool* aDL) {
2814 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aOL) || NS_WARN_IF(!aUL) ||
2815 NS_WARN_IF(!aDL)) {
2816 return NS_ERROR_INVALID_ARG;
2818 if (!mInitSucceeded) {
2819 return NS_ERROR_NOT_INITIALIZED;
2822 ErrorResult error;
2823 ListElementSelectionState state(*this, error);
2824 if (error.Failed()) {
2825 NS_WARNING("ListElementSelectionState failed");
2826 return error.StealNSResult();
2829 *aMixed = state.IsNotOneTypeListElementSelected();
2830 *aOL = state.IsOLElementSelected();
2831 *aUL = state.IsULElementSelected();
2832 *aDL = state.IsDLElementSelected();
2833 return NS_OK;
2836 NS_IMETHODIMP HTMLEditor::GetListItemState(bool* aMixed, bool* aLI, bool* aDT,
2837 bool* aDD) {
2838 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aLI) || NS_WARN_IF(!aDT) ||
2839 NS_WARN_IF(!aDD)) {
2840 return NS_ERROR_INVALID_ARG;
2842 if (!mInitSucceeded) {
2843 return NS_ERROR_NOT_INITIALIZED;
2846 ErrorResult error;
2847 ListItemElementSelectionState state(*this, error);
2848 if (error.Failed()) {
2849 NS_WARNING("ListItemElementSelectionState failed");
2850 return error.StealNSResult();
2853 // XXX Why do we ignore `<li>` element selected state?
2854 *aMixed = state.IsNotOneTypeDefinitionListItemElementSelected();
2855 *aLI = state.IsLIElementSelected();
2856 *aDT = state.IsDTElementSelected();
2857 *aDD = state.IsDDElementSelected();
2858 return NS_OK;
2861 NS_IMETHODIMP HTMLEditor::GetAlignment(bool* aMixed,
2862 nsIHTMLEditor::EAlignment* aAlign) {
2863 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aAlign)) {
2864 return NS_ERROR_INVALID_ARG;
2866 if (!mInitSucceeded) {
2867 return NS_ERROR_NOT_INITIALIZED;
2870 ErrorResult error;
2871 AlignStateAtSelection state(*this, error);
2872 if (error.Failed()) {
2873 NS_WARNING("AlignStateAtSelection failed");
2874 return error.StealNSResult();
2877 *aMixed = false;
2878 *aAlign = state.AlignmentAtSelectionStart();
2879 return NS_OK;
2882 NS_IMETHODIMP HTMLEditor::MakeOrChangeList(const nsAString& aListType,
2883 bool aEntireList,
2884 const nsAString& aBulletType) {
2885 RefPtr<nsAtom> listTagName = NS_Atomize(aListType);
2886 if (NS_WARN_IF(!listTagName) || NS_WARN_IF(!listTagName->IsStatic())) {
2887 return NS_ERROR_INVALID_ARG;
2889 // MOZ_KnownLive(listTagName->AsStatic()) because nsStaticAtom instances live
2890 // while the process is running.
2891 nsresult rv = MakeOrChangeListAsAction(
2892 MOZ_KnownLive(*listTagName->AsStatic()), aBulletType,
2893 aEntireList ? SelectAllOfCurrentList::Yes : SelectAllOfCurrentList::No);
2894 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2895 "HTMLEditor::MakeOrChangeListAsAction() failed");
2896 return rv;
2899 nsresult HTMLEditor::MakeOrChangeListAsAction(
2900 const nsStaticAtom& aListElementTagName, const nsAString& aBulletType,
2901 SelectAllOfCurrentList aSelectAllOfCurrentList, nsIPrincipal* aPrincipal) {
2902 if (NS_WARN_IF(!mInitSucceeded)) {
2903 return NS_ERROR_NOT_INITIALIZED;
2906 AutoEditActionDataSetter editActionData(
2907 *this, HTMLEditUtils::GetEditActionForInsert(aListElementTagName),
2908 aPrincipal);
2909 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2910 if (NS_FAILED(rv)) {
2911 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2912 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2913 return EditorBase::ToGenericNSResult(rv);
2916 Result<EditActionResult, nsresult> result =
2917 MakeOrChangeListAndListItemAsSubAction(aListElementTagName, aBulletType,
2918 aSelectAllOfCurrentList);
2919 if (MOZ_UNLIKELY(result.isErr())) {
2920 NS_WARNING("HTMLEditor::MakeOrChangeListAndListItemAsSubAction() failed");
2921 return EditorBase::ToGenericNSResult(result.unwrapErr());
2923 return NS_OK;
2926 NS_IMETHODIMP HTMLEditor::RemoveList(const nsAString& aListType) {
2927 nsresult rv = RemoveListAsAction(aListType);
2928 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2929 "HTMLEditor::RemoveListAsAction() failed");
2930 return rv;
2933 nsresult HTMLEditor::RemoveListAsAction(const nsAString& aListType,
2934 nsIPrincipal* aPrincipal) {
2935 if (NS_WARN_IF(!mInitSucceeded)) {
2936 return NS_ERROR_NOT_INITIALIZED;
2939 // Note that we ignore aListType when we actually remove parent list elements.
2940 // However, we need to set InputEvent.inputType to "insertOrderedList" or
2941 // "insertedUnorderedList" when this is called for
2942 // execCommand("insertorderedlist") or execCommand("insertunorderedlist").
2943 // Otherwise, comm-central UI may call this methods with "dl" or "".
2944 // So, it's okay to use mismatched EditAction here if this is called in
2945 // comm-central.
2947 RefPtr<nsAtom> listAtom = NS_Atomize(aListType);
2948 if (NS_WARN_IF(!listAtom)) {
2949 return NS_ERROR_INVALID_ARG;
2951 AutoEditActionDataSetter editActionData(
2952 *this, HTMLEditUtils::GetEditActionForRemoveList(*listAtom), aPrincipal);
2953 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2954 if (NS_FAILED(rv)) {
2955 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2956 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2957 return EditorBase::ToGenericNSResult(rv);
2960 const RefPtr<Element> editingHost = ComputeEditingHost();
2961 if (!editingHost) {
2962 return NS_SUCCESS_DOM_NO_OPERATION;
2965 rv = RemoveListAtSelectionAsSubAction(*editingHost);
2966 NS_WARNING_ASSERTION(NS_FAILED(rv),
2967 "HTMLEditor::RemoveListAtSelectionAsSubAction() failed");
2968 return rv;
2971 nsresult HTMLEditor::FormatBlockContainerAsSubAction(
2972 const nsStaticAtom& aTagName, FormatBlockMode aFormatBlockMode) {
2973 MOZ_ASSERT(IsEditActionDataAvailable());
2975 if (NS_WARN_IF(!mInitSucceeded)) {
2976 return NS_ERROR_NOT_INITIALIZED;
2979 MOZ_ASSERT(&aTagName != nsGkAtoms::dd && &aTagName != nsGkAtoms::dt);
2981 AutoPlaceholderBatch treatAsOneTransaction(
2982 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2983 IgnoredErrorResult ignoredError;
2984 AutoEditSubActionNotifier startToHandleEditSubAction(
2985 *this, EditSubAction::eCreateOrRemoveBlock, nsIEditor::eNext,
2986 ignoredError);
2987 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2988 return ignoredError.StealNSResult();
2990 NS_WARNING_ASSERTION(
2991 !ignoredError.Failed(),
2992 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2995 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
2996 if (MOZ_UNLIKELY(result.isErr())) {
2997 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
2998 return result.unwrapErr();
3000 if (result.inspect().Canceled()) {
3001 return NS_OK;
3005 if (IsSelectionRangeContainerNotContent()) {
3006 return NS_SUCCESS_DOM_NO_OPERATION;
3009 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
3010 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3011 return NS_ERROR_EDITOR_DESTROYED;
3013 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3014 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
3015 "failed, but ignored");
3017 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) {
3018 nsresult rv = EnsureCaretNotAfterInvisibleBRElement();
3019 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3020 return NS_ERROR_EDITOR_DESTROYED;
3022 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3023 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() "
3024 "failed, but ignored");
3025 if (NS_SUCCEEDED(rv)) {
3026 nsresult rv = PrepareInlineStylesForCaret();
3027 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3028 return NS_ERROR_EDITOR_DESTROYED;
3030 NS_WARNING_ASSERTION(
3031 NS_SUCCEEDED(rv),
3032 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
3036 // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
3037 // Therefore, even if it returns NS_OK, editor might have been destroyed
3038 // at restoring Selection.
3039 const RefPtr<Element> editingHost = ComputeEditingHost();
3040 if (MOZ_UNLIKELY(!editingHost)) {
3041 return NS_SUCCESS_DOM_NO_OPERATION;
3043 AutoRangeArray selectionRanges(SelectionRef());
3044 Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError =
3045 FormatBlockContainerWithTransaction(selectionRanges, aTagName,
3046 aFormatBlockMode, *editingHost);
3047 if (suggestBlockElementToPutCaretOrError.isErr()) {
3048 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed");
3049 return suggestBlockElementToPutCaretOrError.unwrapErr();
3052 if (selectionRanges.HasSavedRanges()) {
3053 selectionRanges.RestoreFromSavedRanges();
3056 rv = selectionRanges.ApplyTo(SelectionRef());
3057 if (NS_WARN_IF(Destroyed())) {
3058 return NS_ERROR_EDITOR_DESTROYED;
3060 if (NS_FAILED(rv)) {
3061 NS_WARNING("AutoRangeArray::ApplyTo(SelectionRef()) failed, but ignored");
3062 return rv;
3065 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
3066 NS_WARNING_ASSERTION(
3067 NS_SUCCEEDED(rv),
3068 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
3069 "failed");
3071 if (!suggestBlockElementToPutCaretOrError.inspect() ||
3072 !SelectionRef().IsCollapsed()) {
3073 return rv;
3075 const auto firstSelectionStartPoint =
3076 GetFirstSelectionStartPoint<EditorRawDOMPoint>();
3077 if (MOZ_UNLIKELY(!firstSelectionStartPoint.IsSet())) {
3078 return rv;
3080 Result<EditorRawDOMPoint, nsresult> pointInBlockElementOrError =
3081 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
3082 EditorRawDOMPoint>(*suggestBlockElementToPutCaretOrError.inspect(),
3083 firstSelectionStartPoint);
3084 if (MOZ_UNLIKELY(pointInBlockElementOrError.isErr())) {
3085 NS_WARNING(
3086 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, but "
3087 "ignored");
3088 return rv;
3090 // Note that if the point is unset, it means that firstSelectionStartPoint is
3091 // in the block element.
3092 if (pointInBlockElementOrError.inspect().IsSet()) {
3093 nsresult rvOfCollapseSelection =
3094 CollapseSelectionTo(pointInBlockElementOrError.inspect());
3095 if (MOZ_UNLIKELY(rvOfCollapseSelection == NS_ERROR_EDITOR_DESTROYED)) {
3096 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
3097 return NS_ERROR_EDITOR_DESTROYED;
3099 NS_WARNING_ASSERTION(
3100 NS_SUCCEEDED(rvOfCollapseSelection),
3101 "EditorBase::CollapseSelectionTo() failed, but ignored");
3103 return rv;
3106 nsresult HTMLEditor::IndentAsAction(nsIPrincipal* aPrincipal) {
3107 if (NS_WARN_IF(!mInitSucceeded)) {
3108 return NS_ERROR_NOT_INITIALIZED;
3111 AutoEditActionDataSetter editActionData(*this, EditAction::eIndent,
3112 aPrincipal);
3113 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3114 if (NS_FAILED(rv)) {
3115 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3116 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3117 return EditorBase::ToGenericNSResult(rv);
3120 const RefPtr<Element> editingHost = ComputeEditingHost();
3121 if (!editingHost) {
3122 return NS_SUCCESS_DOM_NO_OPERATION;
3125 Result<EditActionResult, nsresult> result = IndentAsSubAction(*editingHost);
3126 if (MOZ_UNLIKELY(result.isErr())) {
3127 NS_WARNING("HTMLEditor::IndentAsSubAction() failed");
3128 return EditorBase::ToGenericNSResult(result.unwrapErr());
3130 return NS_OK;
3133 nsresult HTMLEditor::OutdentAsAction(nsIPrincipal* aPrincipal) {
3134 if (NS_WARN_IF(!mInitSucceeded)) {
3135 return NS_ERROR_NOT_INITIALIZED;
3138 AutoEditActionDataSetter editActionData(*this, EditAction::eOutdent,
3139 aPrincipal);
3140 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3141 if (NS_FAILED(rv)) {
3142 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3143 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3144 return EditorBase::ToGenericNSResult(rv);
3147 const RefPtr<Element> editingHost = ComputeEditingHost();
3148 if (!editingHost) {
3149 return NS_SUCCESS_DOM_NO_OPERATION;
3152 Result<EditActionResult, nsresult> result = OutdentAsSubAction(*editingHost);
3153 if (MOZ_UNLIKELY(result.isErr())) {
3154 NS_WARNING("HTMLEditor::OutdentAsSubAction() failed");
3155 return EditorBase::ToGenericNSResult(result.unwrapErr());
3157 return NS_OK;
3160 // TODO: IMPLEMENT ALIGNMENT!
3162 nsresult HTMLEditor::AlignAsAction(const nsAString& aAlignType,
3163 nsIPrincipal* aPrincipal) {
3164 AutoEditActionDataSetter editActionData(
3165 *this, HTMLEditUtils::GetEditActionForAlignment(aAlignType), aPrincipal);
3166 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3167 if (NS_FAILED(rv)) {
3168 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3169 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3170 return EditorBase::ToGenericNSResult(rv);
3173 const RefPtr<Element> editingHost = ComputeEditingHost();
3174 if (!editingHost) {
3175 return NS_SUCCESS_DOM_NO_OPERATION;
3178 Result<EditActionResult, nsresult> result =
3179 AlignAsSubAction(aAlignType, *editingHost);
3180 if (MOZ_UNLIKELY(result.isErr())) {
3181 NS_WARNING("HTMLEditor::AlignAsSubAction() failed");
3182 return EditorBase::ToGenericNSResult(result.unwrapErr());
3184 return NS_OK;
3187 Element* HTMLEditor::GetInclusiveAncestorByTagName(const nsStaticAtom& aTagName,
3188 nsIContent& aContent) const {
3189 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
3191 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
3192 if (NS_WARN_IF(!editActionData.CanHandle())) {
3193 return nullptr;
3196 return GetInclusiveAncestorByTagNameInternal(aTagName, aContent);
3199 Element* HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(
3200 const nsStaticAtom& aTagName) const {
3201 MOZ_ASSERT(IsEditActionDataAvailable());
3202 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
3204 // If no node supplied, get it from anchor node of current selection
3205 const EditorRawDOMPoint atAnchor(SelectionRef().AnchorRef());
3206 if (NS_WARN_IF(!atAnchor.IsInContentNode())) {
3207 return nullptr;
3210 // Try to get the actual selected node
3211 nsIContent* content = nullptr;
3212 if (atAnchor.GetContainer()->HasChildNodes() &&
3213 atAnchor.ContainerAs<nsIContent>()) {
3214 content = atAnchor.GetChild();
3216 // Anchor node is probably a text node - just use that
3217 if (!content) {
3218 content = atAnchor.ContainerAs<nsIContent>();
3219 if (NS_WARN_IF(!content)) {
3220 return nullptr;
3223 return GetInclusiveAncestorByTagNameInternal(aTagName, *content);
3226 Element* HTMLEditor::GetInclusiveAncestorByTagNameInternal(
3227 const nsStaticAtom& aTagName, const nsIContent& aContent) const {
3228 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
3230 Element* currentElement = aContent.GetAsElementOrParentElement();
3231 if (NS_WARN_IF(!currentElement)) {
3232 MOZ_ASSERT(!aContent.GetParentNode());
3233 return nullptr;
3236 bool lookForLink = IsLinkTag(aTagName);
3237 bool lookForNamedAnchor = IsNamedAnchorTag(aTagName);
3238 for (Element* element : currentElement->InclusiveAncestorsOfType<Element>()) {
3239 // Stop searching if parent is a body element. Note: Originally used
3240 // IsRoot() to/ stop at table cells, but that's too messy when you are
3241 // trying to find the parent table.
3242 if (element->IsHTMLElement(nsGkAtoms::body)) {
3243 return nullptr;
3245 if (lookForLink) {
3246 // Test if we have a link (an anchor with href set)
3247 if (HTMLEditUtils::IsLink(element)) {
3248 return element;
3250 } else if (lookForNamedAnchor) {
3251 // Test if we have a named anchor (an anchor with name set)
3252 if (HTMLEditUtils::IsNamedAnchor(element)) {
3253 return element;
3255 } else if (&aTagName == nsGkAtoms::list_) {
3256 // Match "ol", "ul", or "dl" for lists
3257 if (HTMLEditUtils::IsAnyListElement(element)) {
3258 return element;
3260 } else if (&aTagName == nsGkAtoms::td) {
3261 // Table cells are another special case: match either "td" or "th"
3262 if (HTMLEditUtils::IsTableCell(element)) {
3263 return element;
3265 } else if (&aTagName == element->NodeInfo()->NameAtom()) {
3266 return element;
3269 return nullptr;
3272 NS_IMETHODIMP HTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName,
3273 nsINode* aNode,
3274 Element** aReturn) {
3275 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) {
3276 return NS_ERROR_INVALID_ARG;
3279 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
3280 if (NS_WARN_IF(!tagName)) {
3281 // We don't need to support custom elements since this is an internal API.
3282 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3284 if (NS_WARN_IF(tagName == nsGkAtoms::_empty)) {
3285 return NS_ERROR_INVALID_ARG;
3288 if (!aNode) {
3289 AutoEditActionDataSetter dummyEditAction(*this, EditAction::eNotEditing);
3290 if (NS_WARN_IF(!dummyEditAction.CanHandle())) {
3291 return NS_ERROR_NOT_AVAILABLE;
3293 RefPtr<Element> parentElement =
3294 GetInclusiveAncestorByTagNameAtSelection(*tagName);
3295 if (!parentElement) {
3296 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3298 parentElement.forget(aReturn);
3299 return NS_OK;
3302 if (!aNode->IsContent() || !aNode->GetAsElementOrParentElement()) {
3303 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3306 RefPtr<Element> parentElement =
3307 GetInclusiveAncestorByTagName(*tagName, *aNode->AsContent());
3308 if (!parentElement) {
3309 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
3311 parentElement.forget(aReturn);
3312 return NS_OK;
3315 NS_IMETHODIMP HTMLEditor::GetSelectedElement(const nsAString& aTagName,
3316 nsISupports** aReturn) {
3317 if (NS_WARN_IF(!aReturn)) {
3318 return NS_ERROR_INVALID_ARG;
3320 *aReturn = nullptr;
3322 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
3323 if (NS_WARN_IF(!editActionData.CanHandle())) {
3324 return NS_ERROR_NOT_INITIALIZED;
3327 ErrorResult error;
3328 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
3329 if (!aTagName.IsEmpty() && !tagName) {
3330 // We don't need to support custom elements becaus of internal API.
3331 return NS_OK;
3333 RefPtr<nsINode> selectedNode = GetSelectedElement(tagName, error);
3334 NS_WARNING_ASSERTION(!error.Failed(),
3335 "HTMLEditor::GetSelectedElement() failed");
3336 selectedNode.forget(aReturn);
3337 return error.StealNSResult();
3340 already_AddRefed<Element> HTMLEditor::GetSelectedElement(const nsAtom* aTagName,
3341 ErrorResult& aRv) {
3342 MOZ_ASSERT(IsEditActionDataAvailable());
3344 MOZ_ASSERT(!aRv.Failed());
3346 // If there is no Selection or two or more selection ranges, that means that
3347 // not only one element is selected so that return nullptr.
3348 if (SelectionRef().RangeCount() != 1) {
3349 return nullptr;
3352 bool isLinkTag = aTagName && IsLinkTag(*aTagName);
3353 bool isNamedAnchorTag = aTagName && IsNamedAnchorTag(*aTagName);
3355 RefPtr<nsRange> firstRange = SelectionRef().GetRangeAt(0);
3356 MOZ_ASSERT(firstRange);
3358 const RangeBoundary& startRef = firstRange->StartRef();
3359 if (NS_WARN_IF(!startRef.IsSet())) {
3360 aRv.Throw(NS_ERROR_FAILURE);
3361 return nullptr;
3363 const RangeBoundary& endRef = firstRange->EndRef();
3364 if (NS_WARN_IF(!endRef.IsSet())) {
3365 aRv.Throw(NS_ERROR_FAILURE);
3366 return nullptr;
3369 // Optimization for a single selected element
3370 if (startRef.Container() == endRef.Container()) {
3371 nsIContent* startContent = startRef.GetChildAtOffset();
3372 nsIContent* endContent = endRef.GetChildAtOffset();
3373 if (startContent && endContent &&
3374 startContent->GetNextSibling() == endContent) {
3375 if (!aTagName) {
3376 if (!startContent->IsElement()) {
3377 // This means only a text node or something is selected. We should
3378 // return nullptr in this case since no other elements are selected.
3379 return nullptr;
3381 return do_AddRef(startContent->AsElement());
3383 // Test for appropriate node type requested
3384 if (aTagName == startContent->NodeInfo()->NameAtom() ||
3385 (isLinkTag && HTMLEditUtils::IsLink(startContent)) ||
3386 (isNamedAnchorTag && HTMLEditUtils::IsNamedAnchor(startContent))) {
3387 MOZ_ASSERT(startContent->IsElement());
3388 return do_AddRef(startContent->AsElement());
3393 if (isLinkTag && startRef.Container()->IsContent() &&
3394 endRef.Container()->IsContent()) {
3395 // Link node must be the same for both ends of selection.
3396 Element* parentLinkOfStart = GetInclusiveAncestorByTagNameInternal(
3397 *nsGkAtoms::href, *startRef.Container()->AsContent());
3398 if (parentLinkOfStart) {
3399 if (SelectionRef().IsCollapsed()) {
3400 // We have just a caret in the link.
3401 return do_AddRef(parentLinkOfStart);
3403 // Link node must be the same for both ends of selection.
3404 Element* parentLinkOfEnd = GetInclusiveAncestorByTagNameInternal(
3405 *nsGkAtoms::href, *endRef.Container()->AsContent());
3406 if (parentLinkOfStart == parentLinkOfEnd) {
3407 return do_AddRef(parentLinkOfStart);
3412 if (SelectionRef().IsCollapsed()) {
3413 return nullptr;
3416 PostContentIterator postOrderIter;
3417 postOrderIter.Init(firstRange);
3419 RefPtr<Element> lastElementInRange;
3420 for (nsINode* lastNodeInRange = nullptr; !postOrderIter.IsDone();
3421 postOrderIter.Next()) {
3422 if (lastElementInRange) {
3423 // When any node follows an element node, not only one element is
3424 // selected so that return nullptr.
3425 return nullptr;
3428 // This loop ignors any non-element nodes before first element node.
3429 // Its purpose must be that this method treats this case as selecting
3430 // the <b> element:
3431 // - <p>abc <b>d[ef</b>}</p>
3432 // because children of an element node is listed up before the element.
3433 // However, this case must not be expected by the initial developer:
3434 // - <p>a[bc <b>def</b>}</p>
3435 // When we meet non-parent and non-next-sibling node of previous node,
3436 // it means that the range across element boundary (open tag in HTML
3437 // source). So, in this case, we should not say only the following
3438 // element is selected.
3439 nsINode* currentNode = postOrderIter.GetCurrentNode();
3440 MOZ_ASSERT(currentNode);
3441 if (lastNodeInRange && lastNodeInRange->GetParentNode() != currentNode &&
3442 lastNodeInRange->GetNextSibling() != currentNode) {
3443 return nullptr;
3446 lastNodeInRange = currentNode;
3448 lastElementInRange = Element::FromNodeOrNull(lastNodeInRange);
3449 if (!lastElementInRange) {
3450 continue;
3453 // And also, if it's followed by a <br> element, we shouldn't treat the
3454 // the element is selected like this case:
3455 // - <p><b>[def</b>}<br></p>
3456 // Note that we don't need special handling for <a href> because double
3457 // clicking it selects the element and we use the first path to handle it.
3458 // Additionally, we have this case too:
3459 // - <p><b>[def</b><b>}<br></b></p>
3460 // In these cases, the <br> element is not listed up by PostContentIterator.
3461 // So, we should return nullptr if next sibling is a `<br>` element or
3462 // next sibling starts with `<br>` element.
3463 if (nsIContent* nextSibling = lastElementInRange->GetNextSibling()) {
3464 if (nextSibling->IsHTMLElement(nsGkAtoms::br)) {
3465 return nullptr;
3467 nsIContent* firstEditableLeaf = HTMLEditUtils::GetFirstLeafContent(
3468 *nextSibling, {LeafNodeType::OnlyLeafNode});
3469 if (firstEditableLeaf &&
3470 firstEditableLeaf->IsHTMLElement(nsGkAtoms::br)) {
3471 return nullptr;
3475 if (!aTagName) {
3476 continue;
3479 if (isLinkTag && HTMLEditUtils::IsLink(lastElementInRange)) {
3480 continue;
3483 if (isNamedAnchorTag && HTMLEditUtils::IsNamedAnchor(lastElementInRange)) {
3484 continue;
3487 if (aTagName == lastElementInRange->NodeInfo()->NameAtom()) {
3488 continue;
3491 // First element in the range does not match what the caller is looking
3492 // for.
3493 return nullptr;
3495 return lastElementInRange.forget();
3498 Result<CreateElementResult, nsresult> HTMLEditor::CreateAndInsertElement(
3499 WithTransaction aWithTransaction, const nsAtom& aTagName,
3500 const EditorDOMPoint& aPointToInsert,
3501 const InitializeInsertingElement& aInitializer) {
3502 MOZ_ASSERT(IsEditActionDataAvailable());
3503 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
3505 // XXX We need offset at new node for RangeUpdaterRef(). Therefore, we need
3506 // to compute the offset now but this is expensive. So, if it's possible,
3507 // we need to redesign RangeUpdaterRef() as avoiding using indices.
3508 Unused << aPointToInsert.Offset();
3510 IgnoredErrorResult ignoredError;
3511 AutoEditSubActionNotifier startToHandleEditSubAction(
3512 *this, EditSubAction::eCreateNode, nsIEditor::eNext, ignoredError);
3513 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3514 return Err(NS_ERROR_EDITOR_DESTROYED);
3516 NS_WARNING_ASSERTION(
3517 !ignoredError.Failed(),
3518 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3520 // TODO: This method should have a callback function which is called
3521 // immediately after creating an element but before it's inserted into
3522 // the DOM tree. Then, caller can init the new element's attributes
3523 // and children **without** transactions (it'll reduce the number of
3524 // legacy mutation events). Finally, we can get rid of
3525 // CreatElementTransaction since we can use InsertNodeTransaction
3526 // instead.
3528 auto createNewElementResult =
3529 [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> {
3530 RefPtr<Element> newElement = CreateHTMLContent(&aTagName);
3531 if (MOZ_UNLIKELY(!newElement)) {
3532 NS_WARNING("EditorBase::CreateHTMLContent() failed");
3533 return Err(NS_ERROR_FAILURE);
3535 nsresult rv = MarkElementDirty(*newElement);
3536 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
3537 NS_WARNING("EditorBase::MarkElementDirty() caused destroying the editor");
3538 return Err(NS_ERROR_EDITOR_DESTROYED);
3540 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3541 "EditorBase::MarkElementDirty() failed, but ignored");
3542 rv = aInitializer(*this, *newElement, aPointToInsert);
3543 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "aInitializer failed");
3544 if (NS_WARN_IF(Destroyed())) {
3545 return Err(NS_ERROR_EDITOR_DESTROYED);
3547 RefPtr<InsertNodeTransaction> transaction =
3548 InsertNodeTransaction::Create(*this, *newElement, aPointToInsert);
3549 rv = aWithTransaction == WithTransaction::Yes
3550 ? DoTransactionInternal(transaction)
3551 : transaction->DoTransaction();
3552 // FYI: Transaction::DoTransaction never returns NS_ERROR_EDITOR_*.
3553 if (MOZ_UNLIKELY(Destroyed())) {
3554 NS_WARNING(
3555 "InsertNodeTransaction::DoTransaction() caused destroying the "
3556 "editor");
3557 return Err(NS_ERROR_EDITOR_DESTROYED);
3559 if (NS_FAILED(rv)) {
3560 NS_WARNING("InsertNodeTransaction::DoTransaction() failed");
3561 return Err(rv);
3563 // Override the success code if new element was moved by the web apps.
3564 if (newElement &&
3565 newElement->GetParentNode() != aPointToInsert.GetContainer()) {
3566 NS_WARNING("The new element was not inserted into the expected node");
3567 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
3569 return CreateElementResult(
3570 std::move(newElement),
3571 transaction->SuggestPointToPutCaret<EditorDOMPoint>());
3572 }();
3574 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
3575 NS_WARNING("EditorBase::DoTransactionInternal() failed");
3576 // XXX Why do we do this even when DoTransaction() returned error?
3577 DebugOnly<nsresult> rvIgnored =
3578 RangeUpdaterRef().SelAdjCreateNode(aPointToInsert);
3579 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3580 "RangeUpdater::SelAdjCreateNode() failed");
3581 return createNewElementResult;
3584 // If we succeeded to create and insert new element, we need to adjust
3585 // ranges in RangeUpdaterRef(). It currently requires offset of the new
3586 // node. So, let's call it with original offset. Note that if
3587 // aPointToInsert stores child node, it may not be at the offset since new
3588 // element must be inserted before the old child. Although, mutation
3589 // observer can do anything, but currently, we don't check it.
3590 DebugOnly<nsresult> rvIgnored =
3591 RangeUpdaterRef().SelAdjCreateNode(EditorRawDOMPoint(
3592 aPointToInsert.GetContainer(), aPointToInsert.Offset()));
3593 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3594 "RangeUpdater::SelAdjCreateNode() failed, but ignored");
3595 if (MOZ_LIKELY(createNewElementResult.inspect().GetNewNode())) {
3596 TopLevelEditSubActionDataRef().DidCreateElement(
3597 *this, *createNewElementResult.inspect().GetNewNode());
3600 return createNewElementResult;
3603 nsresult HTMLEditor::CopyAttributes(WithTransaction aWithTransaction,
3604 Element& aDestElement, Element& aSrcElement,
3605 const AttributeFilter& aFilterFunc) {
3606 RefPtr<nsDOMAttributeMap> srcAttributes = aSrcElement.Attributes();
3607 if (!srcAttributes->Length()) {
3608 return NS_OK;
3610 AutoTArray<OwningNonNull<Attr>, 16> srcAttrs;
3611 srcAttrs.SetCapacity(srcAttributes->Length());
3612 for (uint32_t i = 0; i < srcAttributes->Length(); i++) {
3613 RefPtr<Attr> attr = srcAttributes->Item(i);
3614 if (!attr) {
3615 break;
3617 srcAttrs.AppendElement(std::move(attr));
3619 if (aWithTransaction == WithTransaction::No) {
3620 for (const OwningNonNull<Attr>& attr : srcAttrs) {
3621 nsString value;
3622 attr->GetValue(value);
3623 if (!aFilterFunc(*this, aSrcElement, aDestElement, attr, value)) {
3624 continue;
3626 DebugOnly<nsresult> rvIgnored =
3627 aDestElement.SetAttr(attr->NodeInfo()->NamespaceID(),
3628 attr->NodeInfo()->NameAtom(), value, false);
3629 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3630 "Element::SetAttr() failed, but ignored");
3632 if (NS_WARN_IF(Destroyed())) {
3633 return NS_ERROR_EDITOR_DESTROYED;
3635 return NS_OK;
3637 MOZ_ASSERT_UNREACHABLE("Not implemented yet, but you try to use this");
3638 return NS_ERROR_NOT_IMPLEMENTED;
3641 already_AddRefed<Element> HTMLEditor::CreateElementWithDefaults(
3642 const nsAtom& aTagName) {
3643 // NOTE: Despite of public method, this can be called for internal use.
3645 // Although this creates an element, but won't change the DOM tree nor
3646 // transaction. So, EditAtion::eNotEditing is proper value here. If
3647 // this is called for internal when there is already AutoEditActionDataSetter
3648 // instance, this would be initialized with its EditAction value.
3649 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
3650 if (NS_WARN_IF(!editActionData.CanHandle())) {
3651 return nullptr;
3654 const nsAtom* realTagName = IsLinkTag(aTagName) || IsNamedAnchorTag(aTagName)
3655 ? nsGkAtoms::a
3656 : &aTagName;
3658 // We don't use editor's CreateElement because we don't want to go through
3659 // the transaction system
3661 // New call to use instead to get proper HTML element, bug 39919
3662 RefPtr<Element> newElement = CreateHTMLContent(realTagName);
3663 if (!newElement) {
3664 return nullptr;
3667 // Mark the new element dirty, so it will be formatted
3668 // XXX Don't we need to check the error result of setting _moz_dirty attr?
3669 IgnoredErrorResult ignoredError;
3670 newElement->SetAttribute(u"_moz_dirty"_ns, u""_ns, ignoredError);
3671 NS_WARNING_ASSERTION(!ignoredError.Failed(),
3672 "Element::SetAttribute(_moz_dirty) failed, but ignored");
3673 ignoredError.SuppressException();
3675 // Set default values for new elements
3676 if (realTagName == nsGkAtoms::table) {
3677 newElement->SetAttr(nsGkAtoms::cellpadding, u"2"_ns, ignoredError);
3678 if (ignoredError.Failed()) {
3679 NS_WARNING("Element::SetAttr(nsGkAtoms::cellpadding, 2) failed");
3680 return nullptr;
3682 ignoredError.SuppressException();
3684 newElement->SetAttr(nsGkAtoms::cellspacing, u"2"_ns, ignoredError);
3685 if (ignoredError.Failed()) {
3686 NS_WARNING("Element::SetAttr(nsGkAtoms::cellspacing, 2) failed");
3687 return nullptr;
3689 ignoredError.SuppressException();
3691 newElement->SetAttr(nsGkAtoms::border, u"1"_ns, ignoredError);
3692 if (ignoredError.Failed()) {
3693 NS_WARNING("Element::SetAttr(nsGkAtoms::border, 1) failed");
3694 return nullptr;
3696 } else if (realTagName == nsGkAtoms::td) {
3697 nsresult rv = SetAttributeOrEquivalent(newElement, nsGkAtoms::valign,
3698 u"top"_ns, true);
3699 if (NS_FAILED(rv)) {
3700 NS_WARNING(
3701 "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::valign, top) "
3702 "failed");
3703 return nullptr;
3706 // ADD OTHER TAGS HERE
3708 return newElement.forget();
3711 NS_IMETHODIMP HTMLEditor::CreateElementWithDefaults(const nsAString& aTagName,
3712 Element** aReturn) {
3713 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) {
3714 return NS_ERROR_INVALID_ARG;
3717 *aReturn = nullptr;
3719 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
3720 if (NS_WARN_IF(!tagName)) {
3721 return NS_ERROR_INVALID_ARG;
3723 RefPtr<Element> newElement =
3724 CreateElementWithDefaults(MOZ_KnownLive(*tagName));
3725 if (!newElement) {
3726 NS_WARNING("HTMLEditor::CreateElementWithDefaults() failed");
3727 return NS_ERROR_FAILURE;
3729 newElement.forget(aReturn);
3730 return NS_OK;
3733 NS_IMETHODIMP HTMLEditor::InsertLinkAroundSelection(Element* aAnchorElement) {
3734 nsresult rv = InsertLinkAroundSelectionAsAction(aAnchorElement);
3735 NS_WARNING_ASSERTION(
3736 NS_SUCCEEDED(rv),
3737 "HTMLEditor::InsertLinkAroundSelectionAsAction() failed");
3738 return rv;
3741 nsresult HTMLEditor::InsertLinkAroundSelectionAsAction(
3742 Element* aAnchorElement, nsIPrincipal* aPrincipal) {
3743 if (NS_WARN_IF(!aAnchorElement)) {
3744 return NS_ERROR_INVALID_ARG;
3747 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLinkElement,
3748 aPrincipal);
3749 if (NS_WARN_IF(!editActionData.CanHandle())) {
3750 return NS_ERROR_NOT_INITIALIZED;
3753 if (SelectionRef().IsCollapsed()) {
3754 NS_WARNING("Selection was collapsed");
3755 return NS_OK;
3758 // Be sure we were given an anchor element
3759 RefPtr<HTMLAnchorElement> anchor =
3760 HTMLAnchorElement::FromNodeOrNull(aAnchorElement);
3761 if (!anchor) {
3762 return NS_OK;
3765 nsAutoString rawHref;
3766 anchor->GetAttr(nsGkAtoms::href, rawHref);
3767 editActionData.SetData(rawHref);
3769 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
3770 if (NS_FAILED(rv)) {
3771 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3772 "MaybeDispatchBeforeInputEvent(), failed");
3773 return EditorBase::ToGenericNSResult(rv);
3776 // XXX Is this ok? Does this just want to check that we're a link? If so
3777 // there are faster ways to do this.
3779 nsAutoCString href;
3780 anchor->GetHref(href);
3781 if (href.IsEmpty()) {
3782 return NS_OK;
3786 AutoPlaceholderBatch treatAsOneTransaction(
3787 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3789 // Set all attributes found on the supplied anchor element
3790 RefPtr<nsDOMAttributeMap> attributeMap = anchor->Attributes();
3791 if (NS_WARN_IF(!attributeMap)) {
3792 return NS_ERROR_FAILURE;
3795 // TODO: We should stop using this loop for adding attributes to newly created
3796 // `<a href="...">` elements. Then, we can avoid to increate the ref-
3797 // counter of attribute names since we can use nsStaticAtom if we don't
3798 // need to support unknown attributes.
3799 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
3800 stylesToSet.SetCapacity(attributeMap->Length());
3801 nsString value;
3802 for (uint32_t i : IntegerRange(attributeMap->Length())) {
3803 RefPtr<Attr> attribute = attributeMap->Item(i);
3804 if (!attribute) {
3805 continue;
3808 RefPtr<nsAtom> attributeName = attribute->NodeInfo()->NameAtom();
3810 MOZ_ASSERT(value.IsEmpty());
3811 attribute->GetValue(value);
3813 stylesToSet.AppendElement(EditorInlineStyleAndValue(
3814 *nsGkAtoms::a, std::move(attributeName), std::move(value)));
3816 rv = SetInlinePropertiesAsSubAction(stylesToSet);
3817 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3818 "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
3819 return rv;
3822 nsresult HTMLEditor::SetHTMLBackgroundColorWithTransaction(
3823 const nsAString& aColor) {
3824 MOZ_ASSERT(IsEditActionDataAvailable());
3826 // Find a selected or enclosing table element to set background on
3827 bool isCellSelected = false;
3828 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
3829 GetSelectedOrParentTableElement(&isCellSelected);
3830 if (cellOrRowOrTableElementOrError.isErr()) {
3831 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
3832 return cellOrRowOrTableElementOrError.unwrapErr();
3835 bool setColor = !aColor.IsEmpty();
3836 RefPtr<Element> rootElementOfBackgroundColor =
3837 cellOrRowOrTableElementOrError.unwrap();
3838 if (rootElementOfBackgroundColor) {
3839 // Needs to set or remove background color of each selected cell elements.
3840 // Therefore, just the cell contains selection range, we don't need to
3841 // do this. Note that users can select each cell, but with Selection API,
3842 // web apps can select <tr> and <td> at same time. With <table>, looks
3843 // odd, though.
3844 if (isCellSelected || rootElementOfBackgroundColor->IsAnyOfHTMLElements(
3845 nsGkAtoms::table, nsGkAtoms::tr)) {
3846 SelectedTableCellScanner scanner(SelectionRef());
3847 if (scanner.IsInTableCellSelectionMode()) {
3848 if (setColor) {
3849 for (const OwningNonNull<Element>& cellElement :
3850 scanner.ElementsRef()) {
3851 // `MOZ_KnownLive(cellElement)` is safe because of `scanner`
3852 // is stack only class and keeps grabbing it until it's destroyed.
3853 nsresult rv = SetAttributeWithTransaction(
3854 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor, aColor);
3855 if (NS_WARN_IF(Destroyed())) {
3856 return NS_ERROR_EDITOR_DESTROYED;
3858 if (NS_FAILED(rv)) {
3859 NS_WARNING(
3860 "EditorBase::::SetAttributeWithTransaction(nsGkAtoms::"
3861 "bgcolor) failed");
3862 return rv;
3865 return NS_OK;
3867 for (const OwningNonNull<Element>& cellElement :
3868 scanner.ElementsRef()) {
3869 // `MOZ_KnownLive(cellElement)` is safe because of `scanner`
3870 // is stack only class and keeps grabbing it until it's destroyed.
3871 nsresult rv = RemoveAttributeWithTransaction(
3872 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor);
3873 if (NS_FAILED(rv)) {
3874 NS_WARNING(
3875 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor)"
3876 " failed");
3877 return rv;
3880 return NS_OK;
3883 // If we failed to find a cell, fall through to use originally-found element
3884 } else {
3885 // No table element -- set the background color on the body tag
3886 rootElementOfBackgroundColor = GetRoot();
3887 if (NS_WARN_IF(!rootElementOfBackgroundColor)) {
3888 return NS_ERROR_FAILURE;
3891 // Use the editor method that goes through the transaction system
3892 if (setColor) {
3893 nsresult rv = SetAttributeWithTransaction(*rootElementOfBackgroundColor,
3894 *nsGkAtoms::bgcolor, aColor);
3895 NS_WARNING_ASSERTION(
3896 NS_SUCCEEDED(rv),
3897 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3898 return rv;
3900 nsresult rv = RemoveAttributeWithTransaction(*rootElementOfBackgroundColor,
3901 *nsGkAtoms::bgcolor);
3902 NS_WARNING_ASSERTION(
3903 NS_SUCCEEDED(rv),
3904 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3905 return rv;
3908 nsresult HTMLEditor::RemoveEmptyInclusiveAncestorInlineElements(
3909 nsIContent& aContent) {
3910 MOZ_ASSERT(IsEditActionDataAvailable());
3911 MOZ_ASSERT(!aContent.Length());
3913 Element* editingHost = aContent.GetEditingHost();
3914 if (NS_WARN_IF(!editingHost)) {
3915 return NS_ERROR_FAILURE;
3918 if (&aContent == editingHost ||
3919 HTMLEditUtils::IsBlockElement(
3920 aContent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
3921 !EditorUtils::IsEditableContent(aContent, EditorType::HTML) ||
3922 !aContent.GetParent()) {
3923 return NS_OK;
3926 // Don't strip wrappers if this is the only wrapper in the block. Then we'll
3927 // add a <br> later, so it won't be an empty wrapper in the end.
3928 // XXX This is different from Blink. We should delete empty inline element
3929 // even if it's only child of the block element.
3931 const Element* editableBlockElement = HTMLEditUtils::GetAncestorElement(
3932 aContent, HTMLEditUtils::ClosestEditableBlockElement,
3933 BlockInlineCheck::UseComputedDisplayOutsideStyle);
3934 if (!editableBlockElement ||
3935 HTMLEditUtils::IsEmptyNode(
3936 *editableBlockElement,
3937 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3938 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
3939 return NS_OK;
3943 OwningNonNull<nsIContent> content = aContent;
3944 for (nsIContent* parentContent : aContent.AncestorsOfType<nsIContent>()) {
3945 if (HTMLEditUtils::IsBlockElement(
3946 *parentContent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
3947 parentContent->Length() != 1 ||
3948 !EditorUtils::IsEditableContent(*parentContent, EditorType::HTML) ||
3949 parentContent == editingHost) {
3950 break;
3952 content = *parentContent;
3955 nsresult rv = DeleteNodeWithTransaction(content);
3956 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3957 "EditorBase::DeleteNodeWithTransaction() failed");
3958 return rv;
3961 nsresult HTMLEditor::DeleteAllChildrenWithTransaction(Element& aElement) {
3962 MOZ_ASSERT(IsEditActionDataAvailable());
3964 // Prevent rules testing until we're done
3965 IgnoredErrorResult ignoredError;
3966 AutoEditSubActionNotifier startToHandleEditSubAction(
3967 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
3968 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3969 return ignoredError.StealNSResult();
3971 NS_WARNING_ASSERTION(
3972 !ignoredError.Failed(),
3973 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3975 while (nsCOMPtr<nsIContent> child = aElement.GetLastChild()) {
3976 nsresult rv = DeleteNodeWithTransaction(*child);
3977 if (NS_FAILED(rv)) {
3978 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3979 return rv;
3982 return NS_OK;
3985 NS_IMETHODIMP HTMLEditor::DeleteNode(nsINode* aNode, bool aPreserveSelection,
3986 uint8_t aOptionalArgCount) {
3987 if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aNode->IsContent())) {
3988 return NS_ERROR_INVALID_ARG;
3991 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveNode);
3992 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3993 if (NS_FAILED(rv)) {
3994 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3995 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3996 return EditorBase::ToGenericNSResult(rv);
3999 // Make dispatch `input` event after stopping preserving selection.
4000 AutoPlaceholderBatch treatAsOneTransaction(
4001 *this,
4002 ScrollSelectionIntoView::No, // not a user interaction
4003 __FUNCTION__);
4005 Maybe<AutoTransactionsConserveSelection> preserveSelection;
4006 if (aOptionalArgCount && aPreserveSelection) {
4007 preserveSelection.emplace(*this);
4010 rv = DeleteNodeWithTransaction(MOZ_KnownLive(*aNode->AsContent()));
4011 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4012 "EditorBase::DeleteNodeWithTransaction() failed");
4013 return rv;
4016 Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction(
4017 Text& aTextNode, uint32_t aOffset, uint32_t aLength) {
4018 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
4019 return Err(NS_ERROR_FAILURE);
4022 Result<CaretPoint, nsresult> caretPointOrError =
4023 EditorBase::DeleteTextWithTransaction(aTextNode, aOffset, aLength);
4024 NS_WARNING_ASSERTION(caretPointOrError.isOk(),
4025 "EditorBase::DeleteTextWithTransaction() failed");
4026 return caretPointOrError;
4029 Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
4030 Text& aTextNode, uint32_t aOffset, uint32_t aLength,
4031 const nsAString& aStringToInsert) {
4032 MOZ_ASSERT(IsEditActionDataAvailable());
4033 MOZ_ASSERT(aLength > 0 || !aStringToInsert.IsEmpty());
4035 if (aStringToInsert.IsEmpty()) {
4036 Result<CaretPoint, nsresult> caretPointOrError =
4037 DeleteTextWithTransaction(aTextNode, aOffset, aLength);
4038 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
4039 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4040 return caretPointOrError.propagateErr();
4042 return InsertTextResult(EditorDOMPointInText(&aTextNode, aOffset),
4043 caretPointOrError.unwrap());
4046 if (!aLength) {
4047 RefPtr<Document> document = GetDocument();
4048 if (NS_WARN_IF(!document)) {
4049 return Err(NS_ERROR_NOT_INITIALIZED);
4051 Result<InsertTextResult, nsresult> insertTextResult =
4052 InsertTextWithTransaction(*document, aStringToInsert,
4053 EditorDOMPoint(&aTextNode, aOffset));
4054 NS_WARNING_ASSERTION(insertTextResult.isOk(),
4055 "HTMLEditor::InsertTextWithTransaction() failed");
4056 return insertTextResult;
4059 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
4060 return Err(NS_ERROR_FAILURE);
4063 // This should emulates inserting text for better undo/redo behavior.
4064 IgnoredErrorResult ignoredError;
4065 AutoEditSubActionNotifier startToHandleEditSubAction(
4066 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
4067 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4068 return Err(NS_ERROR_EDITOR_DESTROYED);
4070 NS_WARNING_ASSERTION(
4071 !ignoredError.Failed(),
4072 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4074 // FYI: Create the insertion point before changing the DOM tree because
4075 // the point may become invalid offset after that.
4076 EditorDOMPointInText pointToInsert(&aTextNode, aOffset);
4078 RefPtr<ReplaceTextTransaction> transaction = ReplaceTextTransaction::Create(
4079 *this, aStringToInsert, aTextNode, aOffset, aLength);
4080 MOZ_ASSERT(transaction);
4082 if (aLength && !mActionListeners.IsEmpty()) {
4083 for (auto& listener : mActionListeners.Clone()) {
4084 DebugOnly<nsresult> rvIgnored =
4085 listener->WillDeleteText(&aTextNode, aOffset, aLength);
4086 NS_WARNING_ASSERTION(
4087 NS_SUCCEEDED(rvIgnored),
4088 "nsIEditActionListener::WillDeleteText() failed, but ignored");
4092 nsresult rv = DoTransactionInternal(transaction);
4093 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4094 "EditorBase::DoTransactionInternal() failed");
4096 // Don't check whether we've been destroyed here because we need to notify
4097 // listeners and observers below even if we've already destroyed.
4099 EditorDOMPointInText endOfInsertedText(&aTextNode,
4100 aOffset + aStringToInsert.Length());
4102 if (pointToInsert.IsSet()) {
4103 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
4104 if (begin.IsSet() && end.IsSet()) {
4105 TopLevelEditSubActionDataRef().DidDeleteText(
4106 *this, begin.To<EditorRawDOMPoint>());
4107 TopLevelEditSubActionDataRef().DidInsertText(
4108 *this, begin.To<EditorRawDOMPoint>(), end.To<EditorRawDOMPoint>());
4111 // XXX Should we update endOfInsertedText here?
4114 if (!mActionListeners.IsEmpty()) {
4115 for (auto& listener : mActionListeners.Clone()) {
4116 DebugOnly<nsresult> rvIgnored =
4117 listener->DidInsertText(&aTextNode, aOffset, aStringToInsert, rv);
4118 NS_WARNING_ASSERTION(
4119 NS_SUCCEEDED(rvIgnored),
4120 "nsIEditActionListener::DidInsertText() failed, but ignored");
4124 if (NS_WARN_IF(Destroyed())) {
4125 return Err(NS_ERROR_EDITOR_DESTROYED);
4128 return InsertTextResult(
4129 std::move(endOfInsertedText),
4130 transaction->SuggestPointToPutCaret<EditorDOMPoint>());
4133 Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction(
4134 Document& aDocument, const nsAString& aStringToInsert,
4135 const EditorDOMPoint& aPointToInsert) {
4136 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
4137 return Err(NS_ERROR_INVALID_ARG);
4140 // Do nothing if the node is read-only
4141 if (MOZ_UNLIKELY(NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
4142 *aPointToInsert.GetContainer())))) {
4143 return Err(NS_ERROR_FAILURE);
4146 return EditorBase::InsertTextWithTransaction(aDocument, aStringToInsert,
4147 aPointToInsert);
4150 Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertBRElement(
4151 const EditorDOMPoint& aPointToInsert) {
4152 MOZ_ASSERT(IsEditActionDataAvailable());
4154 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
4155 return Err(NS_ERROR_FAILURE);
4158 if (!aPointToInsert.IsInTextNode()) {
4159 return aPointToInsert;
4162 if (aPointToInsert.IsStartOfContainer()) {
4163 // Insert before the text node.
4164 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
4165 if (!pointInContainer.IsSet()) {
4166 NS_WARNING("Failed to climb up the DOM tree from text node");
4167 return Err(NS_ERROR_FAILURE);
4169 return pointInContainer;
4172 if (aPointToInsert.IsEndOfContainer()) {
4173 // Insert after the text node.
4174 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
4175 if (NS_WARN_IF(!pointInContainer.IsSet())) {
4176 NS_WARNING("Failed to climb up the DOM tree from text node");
4177 return Err(NS_ERROR_FAILURE);
4179 MOZ_ALWAYS_TRUE(pointInContainer.AdvanceOffset());
4180 return pointInContainer;
4183 MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
4185 // Unfortunately, we need to split the text node at the offset.
4186 Result<SplitNodeResult, nsresult> splitTextNodeResult =
4187 SplitNodeWithTransaction(aPointToInsert);
4188 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
4189 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4190 return splitTextNodeResult.propagateErr();
4192 nsresult rv = splitTextNodeResult.inspect().SuggestCaretPointTo(
4193 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
4194 if (NS_FAILED(rv)) {
4195 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
4196 return Err(rv);
4199 // Insert new <br> before the right node.
4200 auto atNextContent =
4201 splitTextNodeResult.inspect().AtNextContent<EditorDOMPoint>();
4202 if (MOZ_UNLIKELY(!atNextContent.IsSet())) {
4203 NS_WARNING("The next node seems not in the DOM tree");
4204 return Err(NS_ERROR_FAILURE);
4206 return atNextContent;
4209 Result<CreateElementResult, nsresult> HTMLEditor::InsertBRElement(
4210 WithTransaction aWithTransaction, const EditorDOMPoint& aPointToInsert,
4211 EDirection aSelect /* = eNone */) {
4212 MOZ_ASSERT(IsEditActionDataAvailable());
4214 Result<EditorDOMPoint, nsresult> maybePointToInsert =
4215 PrepareToInsertBRElement(aPointToInsert);
4216 if (maybePointToInsert.isErr()) {
4217 NS_WARNING(
4218 nsPrintfCString("HTMLEditor::PrepareToInsertBRElement(%s) failed",
4219 ToString(aWithTransaction).c_str())
4220 .get());
4221 return maybePointToInsert.propagateErr();
4223 MOZ_ASSERT(maybePointToInsert.inspect().IsSetAndValid());
4225 Result<CreateElementResult, nsresult> createNewBRElementResult =
4226 CreateAndInsertElement(aWithTransaction, *nsGkAtoms::br,
4227 maybePointToInsert.inspect());
4228 if (MOZ_UNLIKELY(createNewBRElementResult.isErr())) {
4229 NS_WARNING(nsPrintfCString("HTMLEditor::CreateAndInsertElement(%s) failed",
4230 ToString(aWithTransaction).c_str())
4231 .get());
4232 return createNewBRElementResult.propagateErr();
4234 CreateElementResult unwrappedCreateNewBRElementResult =
4235 createNewBRElementResult.unwrap();
4236 RefPtr<Element> newBRElement =
4237 unwrappedCreateNewBRElementResult.UnwrapNewNode();
4238 MOZ_ASSERT(newBRElement);
4240 unwrappedCreateNewBRElementResult.IgnoreCaretPointSuggestion();
4241 switch (aSelect) {
4242 case eNext: {
4243 const auto pointToPutCaret = EditorDOMPoint::After(
4244 *newBRElement, Selection::InterlinePosition::StartOfNextLine);
4245 return CreateElementResult(std::move(newBRElement), pointToPutCaret);
4247 case ePrevious: {
4248 const auto pointToPutCaret = EditorDOMPoint(
4249 newBRElement, Selection::InterlinePosition::StartOfNextLine);
4250 return CreateElementResult(std::move(newBRElement), pointToPutCaret);
4252 default:
4253 NS_WARNING(
4254 "aSelect has invalid value, the caller need to set selection "
4255 "by itself");
4256 [[fallthrough]];
4257 case eNone:
4258 return CreateElementResult(
4259 std::move(newBRElement),
4260 unwrappedCreateNewBRElementResult.UnwrapCaretPoint());
4264 Result<CreateElementResult, nsresult>
4265 HTMLEditor::InsertContainerWithTransaction(
4266 nsIContent& aContentToBeWrapped, const nsAtom& aWrapperTagName,
4267 const InitializeInsertingElement& aInitializer) {
4268 EditorDOMPoint pointToInsertNewContainer(&aContentToBeWrapped);
4269 if (NS_WARN_IF(!pointToInsertNewContainer.IsSet())) {
4270 return Err(NS_ERROR_FAILURE);
4272 // aContentToBeWrapped will be moved to the new container before inserting the
4273 // new container. So, when we insert the container, the insertion point is
4274 // before the next sibling of aContentToBeWrapped.
4275 // XXX If pointerToInsertNewContainer stores offset here, the offset and
4276 // referring child node become mismatched. Although, currently this
4277 // is not a problem since InsertNodeTransaction refers only child node.
4278 MOZ_ALWAYS_TRUE(pointToInsertNewContainer.AdvanceOffset());
4280 // Create new container.
4281 RefPtr<Element> newContainer = CreateHTMLContent(&aWrapperTagName);
4282 if (NS_WARN_IF(!newContainer)) {
4283 return Err(NS_ERROR_FAILURE);
4286 if (&aInitializer != &HTMLEditor::DoNothingForNewElement) {
4287 nsresult rv = aInitializer(*this, *newContainer,
4288 EditorDOMPoint(&aContentToBeWrapped));
4289 if (NS_WARN_IF(Destroyed())) {
4290 return Err(NS_ERROR_EDITOR_DESTROYED);
4292 if (NS_FAILED(rv)) {
4293 NS_WARNING("aInitializer() failed");
4294 return Err(rv);
4298 // Notify our internal selection state listener
4299 AutoInsertContainerSelNotify selNotify(RangeUpdaterRef());
4301 // Put aNode in the new container, first.
4302 // XXX Perhaps, we should not remove the container if it's not editable.
4303 nsresult rv = DeleteNodeWithTransaction(aContentToBeWrapped);
4304 if (NS_FAILED(rv)) {
4305 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4306 return Err(rv);
4310 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
4311 // in normal cases. However, it may be required for nested edit
4312 // actions which may be caused by legacy mutation event listeners or
4313 // chrome script.
4314 AutoTransactionsConserveSelection conserveSelection(*this);
4315 Result<CreateContentResult, nsresult> insertContentNodeResult =
4316 InsertNodeWithTransaction(aContentToBeWrapped,
4317 EditorDOMPoint(newContainer, 0u));
4318 if (MOZ_UNLIKELY(insertContentNodeResult.isErr())) {
4319 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
4320 return insertContentNodeResult.propagateErr();
4322 insertContentNodeResult.inspect().IgnoreCaretPointSuggestion();
4325 // Put the new container where aNode was.
4326 Result<CreateElementResult, nsresult> insertNewContainerElementResult =
4327 InsertNodeWithTransaction<Element>(*newContainer,
4328 pointToInsertNewContainer);
4329 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(),
4330 "EditorBase::InsertNodeWithTransaction() failed");
4331 return insertNewContainerElementResult;
4334 Result<CreateElementResult, nsresult>
4335 HTMLEditor::ReplaceContainerWithTransactionInternal(
4336 Element& aOldContainer, const nsAtom& aTagName, const nsAtom& aAttribute,
4337 const nsAString& aAttributeValue, bool aCloneAllAttributes) {
4338 MOZ_ASSERT(IsEditActionDataAvailable());
4340 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aOldContainer)) ||
4341 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aOldContainer))) {
4342 return Err(NS_ERROR_FAILURE);
4345 // If we're replacing <dd> or <dt> with different type of element, we need to
4346 // split the parent <dl>.
4347 OwningNonNull<Element> containerElementToDelete = aOldContainer;
4348 if (aOldContainer.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt) &&
4349 &aTagName != nsGkAtoms::dt && &aTagName != nsGkAtoms::dd &&
4350 // aOldContainer always has a parent node because of removable.
4351 aOldContainer.GetParentNode()->IsHTMLElement(nsGkAtoms::dl)) {
4352 OwningNonNull<Element> const dlElement = *aOldContainer.GetParentElement();
4353 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(dlElement)) ||
4354 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(dlElement))) {
4355 return Err(NS_ERROR_FAILURE);
4357 Result<SplitRangeOffFromNodeResult, nsresult> splitDLElementResult =
4358 SplitRangeOffFromElement(dlElement, aOldContainer, aOldContainer);
4359 if (MOZ_UNLIKELY(splitDLElementResult.isErr())) {
4360 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
4361 return splitDLElementResult.propagateErr();
4363 splitDLElementResult.inspect().IgnoreCaretPointSuggestion();
4364 RefPtr<Element> middleDLElement = aOldContainer.GetParentElement();
4365 if (NS_WARN_IF(!middleDLElement) ||
4366 NS_WARN_IF(!middleDLElement->IsHTMLElement(nsGkAtoms::dl)) ||
4367 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleDLElement))) {
4368 NS_WARNING("The parent <dl> was lost at splitting it");
4369 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4371 containerElementToDelete = std::move(middleDLElement);
4374 const RefPtr<Element> newContainer = CreateHTMLContent(&aTagName);
4375 if (NS_WARN_IF(!newContainer)) {
4376 return Err(NS_ERROR_FAILURE);
4379 // Set or clone attribute if needed.
4380 // FIXME: What should we do attributes of <dl> elements if we removed it
4381 // above?
4382 if (aCloneAllAttributes) {
4383 MOZ_ASSERT(&aAttribute == nsGkAtoms::_empty);
4384 CloneAttributesWithTransaction(*newContainer, aOldContainer);
4385 } else if (&aAttribute != nsGkAtoms::_empty) {
4386 nsresult rv = newContainer->SetAttr(kNameSpaceID_None,
4387 const_cast<nsAtom*>(&aAttribute),
4388 aAttributeValue, true);
4389 if (NS_FAILED(rv)) {
4390 NS_WARNING("Element::SetAttr() failed");
4391 return Err(NS_ERROR_FAILURE);
4395 const OwningNonNull<nsINode> parentNode =
4396 *containerElementToDelete->GetParentNode();
4397 const nsCOMPtr<nsINode> referenceNode =
4398 containerElementToDelete->GetNextSibling();
4399 AutoReplaceContainerSelNotify selStateNotify(RangeUpdaterRef(), aOldContainer,
4400 *newContainer);
4402 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildren;
4403 HTMLEditUtils::CollectChildren(
4404 aOldContainer, arrayOfChildren, 0u,
4405 // Move non-editable children too because its container, aElement, is
4406 // editable so that all children must be removable node.
4407 {});
4408 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
4409 // in normal cases. However, it may be required for nested edit
4410 // actions which may be caused by legacy mutation event listeners or
4411 // chrome script.
4412 AutoTransactionsConserveSelection conserveSelection(*this);
4413 // Move all children from the old container to the new container.
4414 // For making all MoveNodeTransactions have a reference node in the current
4415 // parent, move nodes from last one to preceding ones.
4416 for (const OwningNonNull<nsIContent>& child : Reversed(arrayOfChildren)) {
4417 Result<MoveNodeResult, nsresult> moveChildResult =
4418 MoveNodeWithTransaction(MOZ_KnownLive(child), // due to bug 1622253.
4419 EditorDOMPoint(newContainer, 0u));
4420 if (MOZ_UNLIKELY(moveChildResult.isErr())) {
4421 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4422 return moveChildResult.propagateErr();
4424 // We'll suggest new caret point which is suggested by new container
4425 // element insertion result. Therefore, we need to do nothing here.
4426 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4430 // Delete containerElementToDelete from the DOM tree to make it not referred
4431 // by InsertNodeTransaction.
4432 nsresult rv = DeleteNodeWithTransaction(containerElementToDelete);
4433 if (NS_FAILED(rv)) {
4434 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4435 return Err(rv);
4438 if (referenceNode && (!referenceNode->GetParentNode() ||
4439 parentNode != referenceNode->GetParentNode())) {
4440 NS_WARNING(
4441 "The reference node for insertion has been moved to different parent, "
4442 "so we got lost the insertion point");
4443 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4446 // Finally, insert the new node to where probably aOldContainer was.
4447 Result<CreateElementResult, nsresult> insertNewContainerElementResult =
4448 InsertNodeWithTransaction<Element>(
4449 *newContainer, referenceNode ? EditorDOMPoint(referenceNode)
4450 : EditorDOMPoint::AtEndOf(*parentNode));
4451 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(),
4452 "EditorBase::InsertNodeWithTransaction() failed");
4453 MOZ_ASSERT_IF(
4454 insertNewContainerElementResult.isOk(),
4455 insertNewContainerElementResult.inspect().GetNewNode() == newContainer);
4456 return insertNewContainerElementResult;
4459 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveContainerWithTransaction(
4460 Element& aElement) {
4461 MOZ_ASSERT(IsEditActionDataAvailable());
4463 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aElement)) ||
4464 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aElement))) {
4465 return Err(NS_ERROR_FAILURE);
4468 // Notify our internal selection state listener.
4469 AutoRemoveContainerSelNotify selNotify(RangeUpdaterRef(),
4470 EditorRawDOMPoint(&aElement));
4472 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildren;
4473 HTMLEditUtils::CollectChildren(
4474 aElement, arrayOfChildren, 0u,
4475 // Move non-editable children too because its container, aElement, is
4476 // editable so that all children must be removable node.
4477 {});
4478 const OwningNonNull<nsINode> parentNode = *aElement.GetParentNode();
4479 nsCOMPtr<nsIContent> previousChild = aElement.GetPreviousSibling();
4480 // For making all MoveNodeTransactions have a referenc node in the current
4481 // parent, move nodes from last one to preceding ones.
4482 for (const OwningNonNull<nsIContent>& child : Reversed(arrayOfChildren)) {
4483 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(child))) {
4484 continue;
4486 Result<MoveNodeResult, nsresult> moveChildResult = MoveNodeWithTransaction(
4487 MOZ_KnownLive(child), // due to bug 1622253.
4488 previousChild ? EditorDOMPoint::After(previousChild)
4489 : EditorDOMPoint(parentNode, 0u));
4490 if (MOZ_UNLIKELY(moveChildResult.isErr())) {
4491 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4492 return moveChildResult.propagateErr();
4494 // If the reference node was moved to different container, try to recover
4495 // the original position.
4496 if (previousChild &&
4497 MOZ_UNLIKELY(previousChild->GetParentNode() != parentNode)) {
4498 if (MOZ_UNLIKELY(child->GetParentNode() != parentNode)) {
4499 NS_WARNING(
4500 "Neither the reference (previous) sibling nor the moved child was "
4501 "in the expected parent node");
4502 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4503 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4505 previousChild = child->GetPreviousSibling();
4507 // We'll need to put caret at next sibling of aElement if nobody moves
4508 // content nodes under the parent node except us.
4509 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4512 if (aElement.GetParentNode() && aElement.GetParentNode() != parentNode) {
4513 NS_WARNING(
4514 "The removing element has already been moved to another element");
4515 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4518 NS_WARNING_ASSERTION(!aElement.GetFirstChild(),
4519 "The removing container still has some children, but "
4520 "they are removed by removing the container");
4522 auto GetNextSiblingOf =
4523 [](const nsTArray<OwningNonNull<nsIContent>>& aArrayOfMovedContent,
4524 const nsINode& aExpectedParentNode) -> nsIContent* {
4525 for (const OwningNonNull<nsIContent>& movedChild :
4526 Reversed(aArrayOfMovedContent)) {
4527 if (movedChild != &aExpectedParentNode) {
4528 continue; // Ignore moved node which was moved to different place
4530 return movedChild->GetNextSibling();
4532 // XXX If all nodes were moved by web apps, we cannot suggest "collect"
4533 // position without computing the index of aElement. However, I
4534 // don't think that it's necessary for the web apps in the wild.
4535 return nullptr;
4538 nsCOMPtr<nsIContent> nextSibling =
4539 aElement.GetParentNode() ? aElement.GetNextSibling()
4540 : GetNextSiblingOf(arrayOfChildren, *parentNode);
4542 nsresult rv = DeleteNodeWithTransaction(aElement);
4543 if (NS_FAILED(rv)) {
4544 NS_WARNING("HTMLEditor::DeleteNodeTransaction() failed");
4545 return Err(rv);
4548 if (nextSibling && nextSibling->GetParentNode() != parentNode) {
4549 nextSibling = GetNextSiblingOf(arrayOfChildren, *parentNode);
4551 return nextSibling ? EditorDOMPoint(nextSibling)
4552 : EditorDOMPoint::AtEndOf(*parentNode);
4555 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentAppended(
4556 nsIContent* aFirstNewContent) {
4557 DoContentInserted(aFirstNewContent, ContentNodeIs::Appended);
4560 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentInserted(
4561 nsIContent* aChild) {
4562 DoContentInserted(aChild, ContentNodeIs::Inserted);
4565 bool HTMLEditor::IsInObservedSubtree(nsIContent* aChild) {
4566 if (!aChild) {
4567 return false;
4570 // FIXME(emilio, bug 1596856): This should probably work if the root is in the
4571 // same shadow tree as the child, probably? I don't know what the
4572 // contenteditable-in-shadow-dom situation is.
4573 if (Element* root = GetRoot()) {
4574 // To be super safe here, check both ChromeOnlyAccess and NAC / Shadow DOM.
4575 // That catches (also unbound) native anonymous content and ShadowDOM.
4576 if (root->ChromeOnlyAccess() != aChild->ChromeOnlyAccess() ||
4577 root->IsInNativeAnonymousSubtree() !=
4578 aChild->IsInNativeAnonymousSubtree() ||
4579 root->IsInShadowTree() != aChild->IsInShadowTree()) {
4580 return false;
4584 return !aChild->ChromeOnlyAccess() && !aChild->IsInShadowTree() &&
4585 !aChild->IsInNativeAnonymousSubtree();
4588 void HTMLEditor::DoContentInserted(nsIContent* aChild,
4589 ContentNodeIs aContentNodeIs) {
4590 MOZ_ASSERT(aChild);
4591 nsINode* container = aChild->GetParentNode();
4592 MOZ_ASSERT(container);
4594 if (!IsInObservedSubtree(aChild)) {
4595 return;
4598 // XXX Why do we need this? This method is a helper of mutation observer.
4599 // So, the callers of mutation observer should guarantee that this won't
4600 // be deleted at least during the call.
4601 RefPtr<HTMLEditor> kungFuDeathGrip(this);
4603 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
4604 if (NS_WARN_IF(!editActionData.CanHandle())) {
4605 return;
4608 if (ShouldReplaceRootElement()) {
4609 UpdateRootElement();
4610 if (mPendingRootElementUpdatedRunner) {
4611 return;
4613 mPendingRootElementUpdatedRunner = NewRunnableMethod(
4614 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
4615 nsContentUtils::AddScriptRunner(
4616 do_AddRef(mPendingRootElementUpdatedRunner));
4617 return;
4620 // We don't need to handle our own modifications
4621 if (!GetTopLevelEditSubAction() && container->IsEditable()) {
4622 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
4623 // Ignore insertion of the padding <br> element.
4624 return;
4626 nsresult rv = OnDocumentModified();
4627 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
4628 return;
4630 NS_WARNING_ASSERTION(
4631 NS_SUCCEEDED(rv),
4632 "HTMLEditor::OnDocumentModified() failed, but ignored");
4634 // Update spellcheck for only the newly-inserted node (bug 743819)
4635 if (mInlineSpellChecker) {
4636 nsIContent* endContent = aChild;
4637 if (aContentNodeIs == ContentNodeIs::Appended) {
4638 nsIContent* child = nullptr;
4639 for (child = aChild; child; child = child->GetNextSibling()) {
4640 if (child->InclusiveDescendantMayNeedSpellchecking(this)) {
4641 break;
4644 if (!child) {
4645 // No child needed spellchecking, return.
4646 return;
4649 // Maybe more than 1 child was appended.
4650 endContent = container->GetLastChild();
4651 } else if (!aChild->InclusiveDescendantMayNeedSpellchecking(this)) {
4652 return;
4655 RefPtr<nsRange> range = nsRange::Create(aChild);
4656 range->SelectNodesInContainer(container, aChild, endContent);
4657 DebugOnly<nsresult> rvIgnored =
4658 mInlineSpellChecker->SpellCheckRange(range);
4659 NS_WARNING_ASSERTION(
4660 rvIgnored == NS_ERROR_NOT_INITIALIZED || NS_SUCCEEDED(rvIgnored),
4661 "mozInlineSpellChecker::SpellCheckRange() failed, but ignored");
4666 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentRemoved(
4667 nsIContent* aChild, nsIContent* aPreviousSibling) {
4668 if (!IsInObservedSubtree(aChild)) {
4669 return;
4672 // XXX Why do we need to do this? This method is a mutation observer's
4673 // method. Therefore, the caller should guarantee that this won't be
4674 // deleted during the call.
4675 RefPtr<HTMLEditor> kungFuDeathGrip(this);
4677 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
4678 if (NS_WARN_IF(!editActionData.CanHandle())) {
4679 return;
4682 if (SameCOMIdentity(aChild, mRootElement)) {
4683 mRootElement = nullptr;
4684 if (mPendingRootElementUpdatedRunner) {
4685 return;
4687 mPendingRootElementUpdatedRunner = NewRunnableMethod(
4688 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
4689 nsContentUtils::AddScriptRunner(
4690 do_AddRef(mPendingRootElementUpdatedRunner));
4691 return;
4694 // We don't need to handle our own modifications
4695 if (!GetTopLevelEditSubAction() && aChild->GetParentNode()->IsEditable()) {
4696 if (aChild && EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
4697 // Ignore removal of the padding <br> element for empty editor.
4698 return;
4701 nsresult rv = OnDocumentModified();
4702 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
4703 return;
4705 NS_WARNING_ASSERTION(
4706 NS_SUCCEEDED(rv),
4707 "HTMLEditor::OnDocumentModified() failed, but ignored");
4711 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::CharacterDataChanged(
4712 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
4713 if (!mInlineSpellChecker || !aContent->IsEditable() ||
4714 !IsInObservedSubtree(aContent) ||
4715 GetTopLevelEditSubAction() != EditSubAction::eNone) {
4716 return;
4719 nsIContent* parent = aContent->GetParent();
4720 if (!parent || !parent->InclusiveDescendantMayNeedSpellchecking(this)) {
4721 return;
4724 RefPtr<nsRange> range = nsRange::Create(aContent);
4725 range->SelectNodesInContainer(parent, aContent, aContent);
4726 DebugOnly<nsresult> rvIgnored = mInlineSpellChecker->SpellCheckRange(range);
4729 nsresult HTMLEditor::SelectEntireDocument() {
4730 MOZ_ASSERT(IsEditActionDataAvailable());
4732 if (!mInitSucceeded) {
4733 return NS_ERROR_NOT_INITIALIZED;
4736 // XXX It's odd to select all of the document body if an contenteditable
4737 // element has focus.
4738 RefPtr<Element> bodyOrDocumentElement = GetRoot();
4739 if (NS_WARN_IF(!bodyOrDocumentElement)) {
4740 return NS_ERROR_NOT_INITIALIZED;
4743 // If we're empty, don't select all children because that would select the
4744 // padding <br> element for empty editor.
4745 if (IsEmpty()) {
4746 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
4747 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4748 "EditorBase::CollapseSelectionToStartOf() failed");
4749 return rv;
4752 // Otherwise, select all children.
4753 ErrorResult error;
4754 SelectionRef().SelectAllChildren(*bodyOrDocumentElement, error);
4755 if (NS_WARN_IF(Destroyed())) {
4756 error.SuppressException();
4757 return NS_ERROR_EDITOR_DESTROYED;
4759 NS_WARNING_ASSERTION(!error.Failed(),
4760 "Selection::SelectAllChildren() failed");
4761 return error.StealNSResult();
4764 nsresult HTMLEditor::SelectAllInternal() {
4765 MOZ_ASSERT(IsEditActionDataAvailable());
4767 CommitComposition();
4768 if (NS_WARN_IF(Destroyed())) {
4769 return NS_ERROR_EDITOR_DESTROYED;
4772 auto GetBodyElementIfElementIsParentOfHTMLBody =
4773 [](const Element& aElement) -> Element* {
4774 if (!aElement.OwnerDoc()->IsHTMLDocument()) {
4775 return const_cast<Element*>(&aElement);
4777 HTMLBodyElement* bodyElement = aElement.OwnerDoc()->GetBodyElement();
4778 return bodyElement && nsContentUtils::ContentIsFlattenedTreeDescendantOf(
4779 bodyElement, &aElement)
4780 ? bodyElement
4781 : const_cast<Element*>(&aElement);
4784 nsCOMPtr<nsIContent> selectionRootContent =
4785 [&]() MOZ_CAN_RUN_SCRIPT -> nsIContent* {
4786 RefPtr<Element> elementToBeSelected = [&]() -> Element* {
4787 // If there is at least one selection range, we should compute the
4788 // selection root from the anchor node.
4789 if (SelectionRef().RangeCount()) {
4790 if (nsIContent* content =
4791 nsIContent::FromNodeOrNull(SelectionRef().GetAnchorNode())) {
4792 if (content->IsElement()) {
4793 return content->AsElement();
4795 if (Element* parentElement =
4796 content->GetParentElementCrossingShadowRoot()) {
4797 return parentElement;
4801 // If no element contains a selection range, we should select all children
4802 // of the focused element at least.
4803 if (Element* focusedElement = GetFocusedElement()) {
4804 return focusedElement;
4806 // of the body or document element.
4807 Element* bodyOrDocumentElement = GetRoot();
4808 NS_WARNING_ASSERTION(bodyOrDocumentElement,
4809 "There was no element in the document");
4810 return bodyOrDocumentElement;
4811 }();
4813 // If the element to be selected is <input type="text"> or <textarea>,
4814 // GetSelectionRootContent() returns its anonymous <div> element, but we
4815 // want to select all of the document or selection limiter. Therefore,
4816 // we should use its parent to compute the selection root.
4817 if (elementToBeSelected->HasIndependentSelection()) {
4818 Element* parentElement = elementToBeSelected->GetParentElement();
4819 if (MOZ_LIKELY(parentElement)) {
4820 elementToBeSelected = parentElement;
4824 // Then, compute the selection root content to select all including
4825 // elementToBeSelected.
4826 RefPtr<PresShell> presShell = GetPresShell();
4827 nsIContent* computedSelectionRootContent =
4828 elementToBeSelected->GetSelectionRootContent(presShell);
4829 if (NS_WARN_IF(!computedSelectionRootContent)) {
4830 return nullptr;
4832 if (MOZ_UNLIKELY(!computedSelectionRootContent->IsElement())) {
4833 return computedSelectionRootContent;
4835 return GetBodyElementIfElementIsParentOfHTMLBody(
4836 *computedSelectionRootContent->AsElement());
4837 }();
4838 if (NS_WARN_IF(!selectionRootContent)) {
4839 return NS_ERROR_FAILURE;
4842 Maybe<Selection::AutoUserInitiated> userSelection;
4843 // XXX Do we need to mark it as "user initiated" for
4844 // `Document.execCommand("selectAll")`?
4845 if (!selectionRootContent->IsEditable()) {
4846 userSelection.emplace(SelectionRef());
4848 ErrorResult error;
4849 SelectionRef().SelectAllChildren(*selectionRootContent, error);
4850 NS_WARNING_ASSERTION(!error.Failed(),
4851 "Selection::SelectAllChildren() failed");
4852 return error.StealNSResult();
4855 bool HTMLEditor::SetCaretInTableCell(Element* aElement) {
4856 MOZ_ASSERT(IsEditActionDataAvailable());
4858 if (!aElement || !aElement->IsHTMLElement() ||
4859 !HTMLEditUtils::IsAnyTableElement(aElement)) {
4860 return false;
4862 const RefPtr<Element> editingHost = ComputeEditingHost();
4863 if (!editingHost || !aElement->IsInclusiveDescendantOf(editingHost)) {
4864 return false;
4867 nsCOMPtr<nsIContent> deepestFirstChild = aElement;
4868 while (deepestFirstChild->HasChildren()) {
4869 deepestFirstChild = deepestFirstChild->GetFirstChild();
4872 // Set selection at beginning of the found node
4873 nsresult rv = CollapseSelectionToStartOf(*deepestFirstChild);
4874 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4875 "EditorBase::CollapseSelectionToStartOf() failed");
4876 return NS_SUCCEEDED(rv);
4880 * This method scans the selection for adjacent text nodes
4881 * and collapses them into a single text node.
4882 * "adjacent" means literally adjacent siblings of the same parent.
4883 * Uses HTMLEditor::JoinNodesWithTransaction() so action is undoable.
4884 * Should be called within the context of a batch transaction.
4886 nsresult HTMLEditor::CollapseAdjacentTextNodes(nsRange& aInRange) {
4887 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4889 // we can't actually do anything during iteration, so store the text nodes in
4890 // an array first.
4891 DOMSubtreeIterator subtreeIter;
4892 if (NS_FAILED(subtreeIter.Init(aInRange))) {
4893 NS_WARNING("DOMSubtreeIterator::Init() failed");
4894 return NS_ERROR_FAILURE;
4896 AutoTArray<OwningNonNull<Text>, 8> textNodes;
4897 subtreeIter.AppendNodesToArray(
4898 +[](nsINode& aNode, void*) -> bool {
4899 return EditorUtils::IsEditableContent(*aNode.AsText(),
4900 EditorType::HTML);
4902 textNodes);
4904 // now that I have a list of text nodes, collapse adjacent text nodes
4905 while (textNodes.Length() > 1u) {
4906 OwningNonNull<Text>& leftTextNode = textNodes[0u];
4907 OwningNonNull<Text>& rightTextNode = textNodes[1u];
4909 // If the text nodes are not direct siblings, we shouldn't join them, and
4910 // we don't need to handle the left one anymore.
4911 if (rightTextNode->GetPreviousSibling() != leftTextNode) {
4912 textNodes.RemoveElementAt(0u);
4913 continue;
4916 Result<JoinNodesResult, nsresult> joinNodesResult =
4917 JoinNodesWithTransaction(MOZ_KnownLive(*leftTextNode),
4918 MOZ_KnownLive(*rightTextNode));
4919 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
4920 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
4921 return joinNodesResult.unwrapErr();
4923 if (MOZ_LIKELY(joinNodesResult.inspect().RemovedContent() ==
4924 leftTextNode)) {
4925 textNodes.RemoveElementAt(0u);
4926 } else if (MOZ_LIKELY(joinNodesResult.inspect().RemovedContent() ==
4927 rightTextNode)) {
4928 textNodes.RemoveElementAt(1u);
4929 } else {
4930 MOZ_ASSERT_UNREACHABLE(
4931 "HTMLEditor::JoinNodesWithTransaction() removed unexpected node");
4932 return NS_ERROR_UNEXPECTED;
4936 return NS_OK;
4939 nsresult HTMLEditor::SetSelectionAtDocumentStart() {
4940 MOZ_ASSERT(IsEditActionDataAvailable());
4942 RefPtr<Element> rootElement = GetRoot();
4943 if (NS_WARN_IF(!rootElement)) {
4944 return NS_ERROR_FAILURE;
4947 nsresult rv = CollapseSelectionToStartOf(*rootElement);
4948 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4949 "EditorBase::CollapseSelectionToStartOf() failed");
4950 return rv;
4954 * Remove aNode, reparenting any children into the parent of aNode. In
4955 * addition, insert any br's needed to preserve identity of removed block.
4957 Result<EditorDOMPoint, nsresult>
4958 HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
4959 MOZ_ASSERT(IsEditActionDataAvailable());
4961 // Two possibilities: the container could be empty of editable content. If
4962 // that is the case, we need to compare what is before and after aNode to
4963 // determine if we need a br.
4965 // Or it could be not empty, in which case we have to compare previous
4966 // sibling and first child to determine if we need a leading br, and compare
4967 // following sibling and last child to determine if we need a trailing br.
4969 EditorDOMPoint pointToPutCaret;
4970 if (nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild(
4971 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
4972 // The case of aNode not being empty. We need a br at start unless:
4973 // 1) previous sibling of aNode is a block, OR
4974 // 2) previous sibling of aNode is a br, OR
4975 // 3) first child of aNode is a block OR
4976 // 4) either is null
4978 if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling(
4979 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
4980 if (!HTMLEditUtils::IsBlockElement(
4981 *previousSibling,
4982 BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
4983 !previousSibling->IsHTMLElement(nsGkAtoms::br) &&
4984 !HTMLEditUtils::IsBlockElement(
4985 *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
4986 // Insert br node
4987 Result<CreateElementResult, nsresult> insertBRElementResult =
4988 InsertBRElement(WithTransaction::Yes,
4989 EditorDOMPoint(&aElement, 0u));
4990 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
4991 NS_WARNING(
4992 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
4993 return insertBRElementResult.propagateErr();
4995 CreateElementResult unwrappedInsertBRElementResult =
4996 insertBRElementResult.unwrap();
4997 unwrappedInsertBRElementResult.MoveCaretPointTo(
4998 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
4999 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
5003 // We need a br at end unless:
5004 // 1) following sibling of aNode is a block, OR
5005 // 2) last child of aNode is a block, OR
5006 // 3) last child of aNode is a br OR
5007 // 4) either is null
5009 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
5010 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5011 if (nextSibling &&
5012 !HTMLEditUtils::IsBlockElement(
5013 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle)) {
5014 if (nsIContent* lastChild = HTMLEditUtils::GetLastChild(
5015 aElement, {WalkTreeOption::IgnoreNonEditableNode},
5016 BlockInlineCheck::Unused)) {
5017 if (!HTMLEditUtils::IsBlockElement(
5018 *lastChild, BlockInlineCheck::UseComputedDisplayStyle) &&
5019 !lastChild->IsHTMLElement(nsGkAtoms::br)) {
5020 Result<CreateElementResult, nsresult> insertBRElementResult =
5021 InsertBRElement(WithTransaction::Yes,
5022 EditorDOMPoint::AtEndOf(aElement));
5023 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
5024 NS_WARNING(
5025 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
5026 return insertBRElementResult.propagateErr();
5028 CreateElementResult unwrappedInsertBRElementResult =
5029 insertBRElementResult.unwrap();
5030 unwrappedInsertBRElementResult.MoveCaretPointTo(
5031 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
5032 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
5037 } else if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling(
5038 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5039 // The case of aNode being empty. We need a br at start unless:
5040 // 1) previous sibling of aNode is a block, OR
5041 // 2) previous sibling of aNode is a br, OR
5042 // 3) following sibling of aNode is a block, OR
5043 // 4) following sibling of aNode is a br OR
5044 // 5) either is null
5045 if (!HTMLEditUtils::IsBlockElement(
5046 *previousSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
5047 !previousSibling->IsHTMLElement(nsGkAtoms::br)) {
5048 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
5049 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5050 if (!HTMLEditUtils::IsBlockElement(
5051 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
5052 !nextSibling->IsHTMLElement(nsGkAtoms::br)) {
5053 Result<CreateElementResult, nsresult> insertBRElementResult =
5054 InsertBRElement(WithTransaction::Yes,
5055 EditorDOMPoint(&aElement, 0u));
5056 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
5057 NS_WARNING(
5058 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
5059 return insertBRElementResult.propagateErr();
5061 CreateElementResult unwrappedInsertBRElementResult =
5062 insertBRElementResult.unwrap();
5063 unwrappedInsertBRElementResult.MoveCaretPointTo(
5064 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
5065 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
5071 // Now remove container
5072 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
5073 RemoveContainerWithTransaction(aElement);
5074 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
5075 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
5076 return unwrapBlockElementResult;
5078 if (AllowsTransactionsToChangeSelection() &&
5079 unwrapBlockElementResult.inspect().IsSet()) {
5080 pointToPutCaret = unwrapBlockElementResult.unwrap();
5082 return pointToPutCaret; // May be unset
5085 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeWithTransaction(
5086 const EditorDOMPoint& aStartOfRightNode) {
5087 MOZ_ASSERT(IsEditActionDataAvailable());
5089 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) {
5090 return Err(NS_ERROR_INVALID_ARG);
5092 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
5094 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(
5095 *aStartOfRightNode.ContainerAs<nsIContent>()))) {
5096 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5099 IgnoredErrorResult ignoredError;
5100 AutoEditSubActionNotifier startToHandleEditSubAction(
5101 *this, EditSubAction::eSplitNode, nsIEditor::eNext, ignoredError);
5102 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5103 return Err(NS_ERROR_EDITOR_DESTROYED);
5105 NS_WARNING_ASSERTION(
5106 !ignoredError.Failed(),
5107 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5109 RefPtr<SplitNodeTransaction> transaction =
5110 SplitNodeTransaction::Create(*this, aStartOfRightNode);
5111 nsresult rv = DoTransactionInternal(transaction);
5112 if (NS_WARN_IF(Destroyed())) {
5113 NS_WARNING(
5114 "EditorBase::DoTransactionInternal() caused destroying the editor");
5115 return Err(NS_ERROR_EDITOR_DESTROYED);
5117 if (NS_FAILED(rv)) {
5118 NS_WARNING("EditorBase::DoTransactionInternal() failed");
5119 return Err(rv);
5122 nsIContent* newContent = transaction->GetNewContent();
5123 nsIContent* splitContent = transaction->GetSplitContent();
5124 if (NS_WARN_IF(!newContent) || NS_WARN_IF(!splitContent)) {
5125 return Err(NS_ERROR_FAILURE);
5127 TopLevelEditSubActionDataRef().DidSplitContent(*this, *splitContent,
5128 *newContent);
5129 if (NS_WARN_IF(!newContent->IsInComposedDoc()) ||
5130 NS_WARN_IF(!splitContent->IsInComposedDoc())) {
5131 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5134 return SplitNodeResult(*newContent, *splitContent);
5137 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeDeepWithTransaction(
5138 nsIContent& aMostAncestorToSplit,
5139 const EditorDOMPoint& aDeepestStartOfRightNode,
5140 SplitAtEdges aSplitAtEdges) {
5141 MOZ_ASSERT(aDeepestStartOfRightNode.IsSetAndValidInComposedDoc());
5142 MOZ_ASSERT(
5143 aDeepestStartOfRightNode.GetContainer() == &aMostAncestorToSplit ||
5144 EditorUtils::IsDescendantOf(*aDeepestStartOfRightNode.GetContainer(),
5145 aMostAncestorToSplit));
5147 if (NS_WARN_IF(!aDeepestStartOfRightNode.IsInComposedDoc())) {
5148 return Err(NS_ERROR_INVALID_ARG);
5151 nsCOMPtr<nsIContent> newLeftNodeOfMostAncestor;
5152 EditorDOMPoint atStartOfRightNode(aDeepestStartOfRightNode);
5153 // lastResult is as explained by its name, the last result which may not be
5154 // split a node actually.
5155 SplitNodeResult lastResult = SplitNodeResult::NotHandled(atStartOfRightNode);
5156 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5157 .IsSetAndValidInComposedDoc());
5159 while (true) {
5160 // Need to insert rules code call here to do things like not split a list
5161 // if you are after the last <li> or before the first, etc. For now we
5162 // just have some smarts about unnecessarily splitting text nodes, which
5163 // should be universal enough to put straight in this EditorBase routine.
5164 auto* splittingContent = atStartOfRightNode.GetContainerAs<nsIContent>();
5165 if (NS_WARN_IF(!splittingContent)) {
5166 lastResult.IgnoreCaretPointSuggestion();
5167 return Err(NS_ERROR_FAILURE);
5169 // If we meet an orphan node before meeting aMostAncestorToSplit, we need
5170 // to stop splitting. This is a bug of the caller.
5171 if (NS_WARN_IF(splittingContent != &aMostAncestorToSplit &&
5172 !atStartOfRightNode.GetContainerParentAs<nsIContent>())) {
5173 lastResult.IgnoreCaretPointSuggestion();
5174 return Err(NS_ERROR_FAILURE);
5176 // If the container is not splitable node such as comment node, atomic
5177 // element, etc, we should keep it as-is, and try to split its parents.
5178 if (!HTMLEditUtils::IsSplittableNode(*splittingContent)) {
5179 if (splittingContent == &aMostAncestorToSplit) {
5180 return lastResult;
5182 atStartOfRightNode.Set(splittingContent);
5183 continue;
5186 // If the split point is middle of the node or the node is not a text node
5187 // and we're allowed to create empty element node, split it.
5188 if ((aSplitAtEdges == SplitAtEdges::eAllowToCreateEmptyContainer &&
5189 !atStartOfRightNode.IsInTextNode()) ||
5190 (!atStartOfRightNode.IsStartOfContainer() &&
5191 !atStartOfRightNode.IsEndOfContainer())) {
5192 Result<SplitNodeResult, nsresult> splitNodeResult =
5193 SplitNodeWithTransaction(atStartOfRightNode);
5194 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
5195 lastResult.IgnoreCaretPointSuggestion();
5196 return splitNodeResult;
5198 lastResult = SplitNodeResult::MergeWithDeeperSplitNodeResult(
5199 splitNodeResult.unwrap(), lastResult);
5200 if (NS_WARN_IF(!lastResult.AtSplitPoint<EditorRawDOMPoint>()
5201 .IsInComposedDoc())) {
5202 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5204 MOZ_ASSERT(lastResult.HasCaretPointSuggestion());
5205 MOZ_ASSERT(lastResult.GetOriginalContent() == splittingContent);
5206 if (splittingContent == &aMostAncestorToSplit) {
5207 // Actually, we split aMostAncestorToSplit.
5208 return lastResult;
5211 // Then, try to split its parent before current node.
5212 atStartOfRightNode = lastResult.AtNextContent<EditorDOMPoint>();
5214 // If the split point is end of the node and it is a text node or we're not
5215 // allowed to create empty container node, try to split its parent after it.
5216 else if (!atStartOfRightNode.IsStartOfContainer()) {
5217 lastResult = SplitNodeResult::HandledButDidNotSplitDueToEndOfContainer(
5218 *splittingContent, &lastResult);
5219 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5220 .IsSetAndValidInComposedDoc());
5221 if (splittingContent == &aMostAncestorToSplit) {
5222 return lastResult;
5225 // Try to split its parent after current node.
5226 atStartOfRightNode.SetAfter(splittingContent);
5228 // If the split point is start of the node and it is a text node or we're
5229 // not allowed to create empty container node, try to split its parent.
5230 else {
5231 if (splittingContent == &aMostAncestorToSplit) {
5232 return SplitNodeResult::HandledButDidNotSplitDueToStartOfContainer(
5233 *splittingContent, &lastResult);
5236 // Try to split its parent before current node.
5237 // XXX This is logically wrong. If we've already split something but
5238 // this is the last splitable content node in the limiter, this
5239 // method will return "not handled".
5240 lastResult = SplitNodeResult::NotHandled(atStartOfRightNode, &lastResult);
5241 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5242 .IsSetAndValidInComposedDoc());
5243 atStartOfRightNode.Set(splittingContent);
5244 MOZ_ASSERT(atStartOfRightNode.IsSetAndValidInComposedDoc());
5248 // Not reached because while (true) loop never breaks.
5251 Result<SplitNodeResult, nsresult> HTMLEditor::DoSplitNode(
5252 const EditorDOMPoint& aStartOfRightNode, nsIContent& aNewNode) {
5253 // Ensure computing the offset if it's initialized with a child content node.
5254 Unused << aStartOfRightNode.Offset();
5256 // XXX Perhaps, aStartOfRightNode may be invalid if this is a redo
5257 // operation after modifying DOM node with JS.
5258 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) {
5259 return Err(NS_ERROR_INVALID_ARG);
5261 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
5263 // Remember all selection points.
5264 AutoTArray<SavedRange, 10> savedRanges;
5265 for (SelectionType selectionType : kPresentSelectionTypes) {
5266 SavedRange savingRange;
5267 savingRange.mSelection = GetSelection(selectionType);
5268 if (NS_WARN_IF(!savingRange.mSelection &&
5269 selectionType == SelectionType::eNormal)) {
5270 return Err(NS_ERROR_FAILURE);
5272 if (!savingRange.mSelection) {
5273 // For non-normal selections, skip over the non-existing ones.
5274 continue;
5277 for (uint32_t j : IntegerRange(savingRange.mSelection->RangeCount())) {
5278 const nsRange* r = savingRange.mSelection->GetRangeAt(j);
5279 MOZ_ASSERT(r);
5280 MOZ_ASSERT(r->IsPositioned());
5281 // XXX Looks like that SavedRange should have mStart and mEnd which
5282 // are RangeBoundary. Then, we can avoid to compute offset here.
5283 savingRange.mStartContainer = r->GetStartContainer();
5284 savingRange.mStartOffset = r->StartOffset();
5285 savingRange.mEndContainer = r->GetEndContainer();
5286 savingRange.mEndOffset = r->EndOffset();
5288 savedRanges.AppendElement(savingRange);
5292 nsCOMPtr<nsINode> parent = aStartOfRightNode.GetContainerParent();
5293 if (NS_WARN_IF(!parent)) {
5294 return Err(NS_ERROR_FAILURE);
5297 // Fix the child before mutation observer may touch the DOM tree.
5298 nsIContent* firstChildOfRightNode = aStartOfRightNode.GetChild();
5299 IgnoredErrorResult error;
5300 parent->InsertBefore(
5301 aNewNode, aStartOfRightNode.GetContainer()->GetNextSibling(), error);
5302 if (MOZ_UNLIKELY(error.Failed())) {
5303 NS_WARNING("nsINode::InsertBefore() failed");
5304 return Err(error.StealNSResult());
5307 MOZ_DIAGNOSTIC_ASSERT_IF(aStartOfRightNode.IsInTextNode(), aNewNode.IsText());
5308 MOZ_DIAGNOSTIC_ASSERT_IF(!aStartOfRightNode.IsInTextNode(),
5309 !aNewNode.IsText());
5311 // If we are splitting a text node, we need to move its some data to the
5312 // new text node.
5313 if (aStartOfRightNode.IsInTextNode()) {
5314 if (!aStartOfRightNode.IsEndOfContainer()) {
5315 Text* originalTextNode = aStartOfRightNode.ContainerAs<Text>();
5316 Text* newTextNode = aNewNode.AsText();
5317 nsAutoString movingText;
5318 const uint32_t cutStartOffset = aStartOfRightNode.Offset();
5319 const uint32_t cutLength =
5320 originalTextNode->Length() - aStartOfRightNode.Offset();
5321 IgnoredErrorResult error;
5322 originalTextNode->SubstringData(cutStartOffset, cutLength, movingText,
5323 error);
5324 NS_WARNING_ASSERTION(!error.Failed(),
5325 "Text::SubstringData() failed, but ignored");
5326 error.SuppressException();
5328 // XXX This call may destroy us.
5329 DoDeleteText(MOZ_KnownLive(*originalTextNode), cutStartOffset, cutLength,
5330 error);
5331 NS_WARNING_ASSERTION(!error.Failed(),
5332 "EditorBase::DoDeleteText() failed, but ignored");
5333 error.SuppressException();
5335 // XXX This call may destroy us.
5336 DoSetText(MOZ_KnownLive(*newTextNode), movingText, error);
5337 NS_WARNING_ASSERTION(!error.Failed(),
5338 "EditorBase::DoSetText() failed, but ignored");
5341 // If the node has been moved to different parent, we should do nothing
5342 // since web apps should handle everything in such case.
5343 else if (firstChildOfRightNode &&
5344 aStartOfRightNode.GetContainer() !=
5345 firstChildOfRightNode->GetParentNode()) {
5346 NS_WARNING(
5347 "The web app interrupted us and touched the DOM tree, we stopped "
5348 "splitting anything");
5349 } else {
5350 // If the right node is new one and there is no children or splitting at
5351 // end of the node, we need to do nothing.
5352 if (!firstChildOfRightNode) {
5353 // Do nothing.
5355 // If the right node is new one and splitting at start of the container,
5356 // we need to move all children to the new right node.
5357 else if (!firstChildOfRightNode->GetPreviousSibling()) {
5358 // XXX Why do we ignore an error while moving nodes from the right
5359 // node to the left node?
5360 nsresult rv = MoveAllChildren(*aStartOfRightNode.GetContainer(),
5361 EditorRawDOMPoint(&aNewNode, 0u));
5362 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5363 return Err(NS_ERROR_EDITOR_DESTROYED);
5365 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5366 "HTMLEditor::MoveAllChildren() failed, but ignored");
5368 // If the right node is new one and splitting at middle of the node, we need
5369 // to move inclusive next siblings of the split point to the new right node.
5370 else {
5371 // XXX Why do we ignore an error while moving nodes from the right node
5372 // to the left node?
5373 nsresult rv = MoveInclusiveNextSiblings(*firstChildOfRightNode,
5374 EditorRawDOMPoint(&aNewNode, 0u));
5375 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5376 return Err(NS_ERROR_EDITOR_DESTROYED);
5378 NS_WARNING_ASSERTION(
5379 NS_SUCCEEDED(rv),
5380 "HTMLEditor::MoveInclusiveNextSiblings() failed, but ignored");
5384 // Handle selection
5385 // TODO: Stop doing this, this shouldn't be necessary to update selection.
5386 if (RefPtr<PresShell> presShell = GetPresShell()) {
5387 presShell->FlushPendingNotifications(FlushType::Frames);
5389 NS_WARNING_ASSERTION(!Destroyed(),
5390 "The editor is destroyed during splitting a node");
5392 const bool allowedTransactionsToChangeSelection =
5393 AllowsTransactionsToChangeSelection();
5395 RefPtr<Selection> previousSelection;
5396 for (SavedRange& savedRange : savedRanges) {
5397 // If we have not seen the selection yet, clear all of its ranges.
5398 if (savedRange.mSelection != previousSelection) {
5399 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error);
5400 if (MOZ_UNLIKELY(error.Failed())) {
5401 NS_WARNING("Selection::RemoveAllRanges() failed");
5402 return Err(error.StealNSResult());
5404 previousSelection = savedRange.mSelection;
5407 // XXX Looks like that we don't need to modify normal selection here
5408 // because selection will be modified by the caller if
5409 // AllowsTransactionsToChangeSelection() will return true.
5410 if (allowedTransactionsToChangeSelection &&
5411 savedRange.mSelection->Type() == SelectionType::eNormal) {
5412 // If the editor should adjust the selection, don't bother restoring
5413 // the ranges for the normal selection here.
5414 continue;
5417 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5418 uint32_t& aOffset) {
5419 if (aContainer != aStartOfRightNode.GetContainer()) {
5420 return;
5423 // If the container is the left node and offset is after the split
5424 // point, the content was moved from the right node to aNewNode.
5425 // So, we need to change the container to aNewNode and decrease the
5426 // offset.
5427 if (aOffset >= aStartOfRightNode.Offset()) {
5428 aContainer = &aNewNode;
5429 aOffset -= aStartOfRightNode.Offset();
5432 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset);
5433 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset);
5435 RefPtr<nsRange> newRange =
5436 nsRange::Create(savedRange.mStartContainer, savedRange.mStartOffset,
5437 savedRange.mEndContainer, savedRange.mEndOffset, error);
5438 if (MOZ_UNLIKELY(error.Failed())) {
5439 NS_WARNING("nsRange::Create() failed");
5440 return Err(error.StealNSResult());
5442 // The `MOZ_KnownLive` annotation is only necessary because of a bug
5443 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
5444 // static analyzer.
5445 MOZ_KnownLive(savedRange.mSelection)
5446 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
5447 if (MOZ_UNLIKELY(error.Failed())) {
5448 NS_WARNING(
5449 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
5450 return Err(error.StealNSResult());
5454 // We don't need to set selection here because the caller should do that
5455 // in any case.
5457 // If splitting the node causes running mutation event listener and we've
5458 // got unexpected result, we should return error because callers will
5459 // continue to do their work without complicated DOM tree result.
5460 // NOTE: Perhaps, we shouldn't do this immediately after each DOM tree change
5461 // because stopping handling it causes some data loss. E.g., user
5462 // may loose the text which is moved to the new text node.
5463 // XXX We cannot check all descendants in the right node and the new left
5464 // node for performance reason. I think that if caller needs to access
5465 // some of the descendants, they should check by themselves.
5466 if (NS_WARN_IF(parent != aStartOfRightNode.GetContainer()->GetParentNode()) ||
5467 NS_WARN_IF(parent != aNewNode.GetParentNode()) ||
5468 NS_WARN_IF(aNewNode.GetPreviousSibling() !=
5469 aStartOfRightNode.GetContainer())) {
5470 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5473 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjSplitNode(
5474 *aStartOfRightNode.ContainerAs<nsIContent>(), aStartOfRightNode.Offset(),
5475 aNewNode);
5476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5477 "RangeUpdater::SelAdjSplitNode() failed, but ignored");
5479 return SplitNodeResult(aNewNode,
5480 *aStartOfRightNode.ContainerAs<nsIContent>());
5483 Result<JoinNodesResult, nsresult> HTMLEditor::JoinNodesWithTransaction(
5484 nsIContent& aLeftContent, nsIContent& aRightContent) {
5485 MOZ_ASSERT(IsEditActionDataAvailable());
5486 MOZ_ASSERT(&aLeftContent != &aRightContent);
5487 MOZ_ASSERT(aLeftContent.GetParentNode());
5488 MOZ_ASSERT(aRightContent.GetParentNode());
5489 MOZ_ASSERT(aLeftContent.GetParentNode() == aRightContent.GetParentNode());
5491 IgnoredErrorResult ignoredError;
5492 AutoEditSubActionNotifier startToHandleEditSubAction(
5493 *this, EditSubAction::eJoinNodes, nsIEditor::ePrevious, ignoredError);
5494 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5495 return Err(ignoredError.StealNSResult());
5497 NS_WARNING_ASSERTION(
5498 !ignoredError.Failed(),
5499 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5501 if (NS_WARN_IF(!aRightContent.GetParentNode())) {
5502 return Err(NS_ERROR_FAILURE);
5505 RefPtr<JoinNodesTransaction> transaction =
5506 JoinNodesTransaction::MaybeCreate(*this, aLeftContent, aRightContent);
5507 if (MOZ_UNLIKELY(!transaction)) {
5508 NS_WARNING("JoinNodesTransaction::MaybeCreate() failed");
5509 return Err(NS_ERROR_FAILURE);
5512 const nsresult rv = DoTransactionInternal(transaction);
5513 // FYI: Now, DidJoinNodesTransaction() must have been run if succeeded.
5514 if (NS_WARN_IF(Destroyed())) {
5515 return Err(NS_ERROR_EDITOR_DESTROYED);
5518 // This shouldn't occur unless the cycle collector runs by chrome script
5519 // forcibly.
5520 if (NS_WARN_IF(!transaction->GetRemovedContent()) ||
5521 NS_WARN_IF(!transaction->GetExistingContent())) {
5522 return Err(NS_ERROR_UNEXPECTED);
5525 // If joined node is moved to different place, offset may not have any
5526 // meaning. In this case, the web app modified the DOM tree takes on the
5527 // responsibility for the remaning things.
5528 if (NS_WARN_IF(transaction->GetExistingContent()->GetParent() !=
5529 transaction->GetParentNode())) {
5530 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5533 if (NS_FAILED(rv)) {
5534 NS_WARNING("EditorBase::DoTransactionInternal() failed");
5535 return Err(rv);
5538 return JoinNodesResult(transaction->CreateJoinedPoint<EditorDOMPoint>(),
5539 *transaction->GetRemovedContent());
5542 void HTMLEditor::DidJoinNodesTransaction(
5543 const JoinNodesTransaction& aTransaction, nsresult aDoJoinNodesResult) {
5544 // This shouldn't occur unless the cycle collector runs by chrome script
5545 // forcibly.
5546 if (MOZ_UNLIKELY(NS_WARN_IF(!aTransaction.GetRemovedContent()) ||
5547 NS_WARN_IF(!aTransaction.GetExistingContent()))) {
5548 return;
5551 // If joined node is moved to different place, offset may not have any
5552 // meaning. In this case, the web app modified the DOM tree takes on the
5553 // responsibility for the remaning things.
5554 if (MOZ_UNLIKELY(aTransaction.GetExistingContent()->GetParentNode() !=
5555 aTransaction.GetParentNode())) {
5556 return;
5559 // Be aware, the joined point should be created for each call because
5560 // they may refer the child node, but some of them may change the DOM tree
5561 // after that, thus we need to avoid invalid point (Although it shouldn't
5562 // occur).
5563 TopLevelEditSubActionDataRef().DidJoinContents(
5564 *this, aTransaction.CreateJoinedPoint<EditorRawDOMPoint>());
5566 if (NS_SUCCEEDED(aDoJoinNodesResult)) {
5567 if (RefPtr<TextServicesDocument> textServicesDocument =
5568 mTextServicesDocument) {
5569 textServicesDocument->DidJoinContents(
5570 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(),
5571 *aTransaction.GetRemovedContent());
5575 if (!mActionListeners.IsEmpty()) {
5576 for (auto& listener : mActionListeners.Clone()) {
5577 DebugOnly<nsresult> rvIgnored = listener->DidJoinContents(
5578 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(),
5579 aTransaction.GetRemovedContent());
5580 NS_WARNING_ASSERTION(
5581 NS_SUCCEEDED(rvIgnored),
5582 "nsIEditActionListener::DidJoinContents() failed, but ignored");
5587 nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
5588 nsIContent& aContentToRemove) {
5589 MOZ_ASSERT(IsEditActionDataAvailable());
5591 const uint32_t keepingContentLength = aContentToKeep.Length();
5592 const EditorDOMPoint oldPointAtRightContent(&aContentToRemove);
5593 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) {
5594 Unused << oldPointAtRightContent.Offset(); // Fix the offset
5597 // Remember all selection points.
5598 // XXX Do we need to restore all types of selections by ourselves? Normal
5599 // selection should be modified later as result of handling edit action.
5600 // IME selections shouldn't be there when nodes are joined. Spellcheck
5601 // selections should be recreated with newer text. URL selections
5602 // shouldn't be there because of used only by the URL bar.
5603 AutoTArray<SavedRange, 10> savedRanges;
5605 EditorRawDOMPoint atRemovingNode(&aContentToRemove);
5606 EditorRawDOMPoint atNodeToKeep(&aContentToKeep);
5607 for (SelectionType selectionType : kPresentSelectionTypes) {
5608 SavedRange savingRange;
5609 savingRange.mSelection = GetSelection(selectionType);
5610 if (selectionType == SelectionType::eNormal) {
5611 if (NS_WARN_IF(!savingRange.mSelection)) {
5612 return NS_ERROR_FAILURE;
5614 } else if (!savingRange.mSelection) {
5615 // For non-normal selections, skip over the non-existing ones.
5616 continue;
5619 const uint32_t rangeCount = savingRange.mSelection->RangeCount();
5620 for (const uint32_t j : IntegerRange(rangeCount)) {
5621 MOZ_ASSERT(savingRange.mSelection->RangeCount() == rangeCount);
5622 const RefPtr<nsRange> r = savingRange.mSelection->GetRangeAt(j);
5623 MOZ_ASSERT(r);
5624 MOZ_ASSERT(r->IsPositioned());
5625 savingRange.mStartContainer = r->GetStartContainer();
5626 savingRange.mStartOffset = r->StartOffset();
5627 savingRange.mEndContainer = r->GetEndContainer();
5628 savingRange.mEndOffset = r->EndOffset();
5630 // If selection endpoint is between the nodes, remember it as being
5631 // in the one that is going away instead. This simplifies later
5632 // selection adjustment logic at end of this method.
5633 if (savingRange.mStartContainer) {
5634 MOZ_ASSERT(savingRange.mEndContainer);
5635 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5636 uint32_t& aOffset) {
5637 // If range boundary points aContentToRemove and aContentToKeep is
5638 // its left node, remember it as being at end of aContentToKeep.
5639 // Then, it will point start of the first content of moved content
5640 // from aContentToRemove.
5641 if (aContainer == atRemovingNode.GetContainer() &&
5642 atNodeToKeep.Offset() < aOffset &&
5643 aOffset <= atRemovingNode.Offset()) {
5644 aContainer = &aContentToKeep;
5645 aOffset = keepingContentLength;
5648 AdjustDOMPoint(savingRange.mStartContainer, savingRange.mStartOffset);
5649 AdjustDOMPoint(savingRange.mEndContainer, savingRange.mEndOffset);
5652 savedRanges.AppendElement(savingRange);
5657 // OK, ready to do join now.
5658 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
5659 // If it's a text node, just shuffle around some text.
5660 if (aContentToKeep.IsText() && aContentToRemove.IsText()) {
5661 nsAutoString rightText;
5662 nsAutoString leftText;
5663 aContentToRemove.AsText()->GetData(rightText);
5664 aContentToKeep.AsText()->GetData(leftText);
5665 leftText += rightText;
5666 IgnoredErrorResult ignoredError;
5667 DoSetText(MOZ_KnownLive(*aContentToKeep.AsText()), leftText,
5668 ignoredError);
5669 if (NS_WARN_IF(Destroyed())) {
5670 return NS_ERROR_EDITOR_DESTROYED;
5672 NS_WARNING_ASSERTION(!ignoredError.Failed(),
5673 "EditorBase::DoSetText() failed, but ignored");
5674 return NS_OK;
5676 // Otherwise it's an interior node, so shuffle around the children.
5677 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfChildContents;
5678 HTMLEditUtils::CollectAllChildren(aContentToRemove, arrayOfChildContents);
5680 for (const OwningNonNull<nsIContent>& child : arrayOfChildContents) {
5681 IgnoredErrorResult error;
5682 aContentToKeep.AppendChild(child, error);
5683 if (NS_WARN_IF(Destroyed())) {
5684 return NS_ERROR_EDITOR_DESTROYED;
5686 if (error.Failed()) {
5687 NS_WARNING("nsINode::AppendChild() failed");
5688 return error.StealNSResult();
5691 return NS_OK;
5692 }();
5694 // Delete the extra node.
5695 if (NS_SUCCEEDED(rv)) {
5696 aContentToRemove.Remove();
5697 if (NS_WARN_IF(Destroyed())) {
5698 return NS_ERROR_EDITOR_DESTROYED;
5702 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) {
5703 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjJoinNodes(
5704 EditorRawDOMPoint(&aContentToKeep, std::min(keepingContentLength,
5705 aContentToKeep.Length())),
5706 aContentToRemove, oldPointAtRightContent);
5707 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5708 "RangeUpdater::SelAdjJoinNodes() failed, but ignored");
5710 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
5711 return rv;
5714 const bool allowedTransactionsToChangeSelection =
5715 AllowsTransactionsToChangeSelection();
5717 // And adjust the selection if needed.
5718 RefPtr<Selection> previousSelection;
5719 for (SavedRange& savedRange : savedRanges) {
5720 // If we have not seen the selection yet, clear all of its ranges.
5721 if (savedRange.mSelection != previousSelection) {
5722 IgnoredErrorResult error;
5723 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error);
5724 if (NS_WARN_IF(Destroyed())) {
5725 return NS_ERROR_EDITOR_DESTROYED;
5727 if (error.Failed()) {
5728 NS_WARNING("Selection::RemoveAllRanges() failed");
5729 return error.StealNSResult();
5731 previousSelection = savedRange.mSelection;
5734 if (allowedTransactionsToChangeSelection &&
5735 savedRange.mSelection->Type() == SelectionType::eNormal) {
5736 // If the editor should adjust the selection, don't bother restoring
5737 // the ranges for the normal selection here.
5738 continue;
5741 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5742 uint32_t& aOffset) {
5743 // Now, all content of aContentToRemove are moved to end of
5744 // aContentToKeep. Therefore, if a range boundary was in
5745 // aContentToRemove, we need to change the container to aContentToKeep and
5746 // adjust the offset to after the original content of aContentToKeep.
5747 if (aContainer == &aContentToRemove) {
5748 aContainer = &aContentToKeep;
5749 aOffset += keepingContentLength;
5752 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset);
5753 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset);
5755 const RefPtr<nsRange> newRange = nsRange::Create(
5756 savedRange.mStartContainer, savedRange.mStartOffset,
5757 savedRange.mEndContainer, savedRange.mEndOffset, IgnoreErrors());
5758 if (!newRange) {
5759 NS_WARNING("nsRange::Create() failed");
5760 return NS_ERROR_FAILURE;
5763 IgnoredErrorResult error;
5764 // The `MOZ_KnownLive` annotation is only necessary because of a bug
5765 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
5766 // static analyzer.
5767 MOZ_KnownLive(savedRange.mSelection)
5768 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
5769 if (NS_WARN_IF(Destroyed())) {
5770 return NS_ERROR_EDITOR_DESTROYED;
5772 if (NS_WARN_IF(error.Failed())) {
5773 return error.StealNSResult();
5777 if (allowedTransactionsToChangeSelection) {
5778 // Editor wants us to set selection at join point.
5779 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(aContentToKeep);
5780 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
5781 NS_WARNING(
5782 "EditorBase::CollapseSelectionTo() caused destroying the editor");
5783 return NS_ERROR_EDITOR_DESTROYED;
5785 NS_WARNING_ASSERTION(
5786 NS_SUCCEEDED(rv),
5787 "EditorBases::CollapseSelectionTos() failed, but ignored");
5790 return NS_OK;
5793 Result<MoveNodeResult, nsresult> HTMLEditor::MoveNodeWithTransaction(
5794 nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert) {
5795 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
5797 EditorDOMPoint oldPoint(&aContentToMove);
5798 if (NS_WARN_IF(!oldPoint.IsSet())) {
5799 return Err(NS_ERROR_FAILURE);
5802 // Don't do anything if it's already in right place.
5803 if (aPointToInsert == oldPoint) {
5804 return MoveNodeResult::IgnoredResult(aPointToInsert.NextPoint());
5807 RefPtr<MoveNodeTransaction> moveNodeTransaction =
5808 MoveNodeTransaction::MaybeCreate(*this, aContentToMove, aPointToInsert);
5809 if (MOZ_UNLIKELY(!moveNodeTransaction)) {
5810 NS_WARNING("MoveNodeTransaction::MaybeCreate() failed");
5811 return Err(NS_ERROR_FAILURE);
5814 IgnoredErrorResult ignoredError;
5815 AutoEditSubActionNotifier startToHandleEditSubAction(
5816 *this, EditSubAction::eMoveNode, nsIEditor::eNext, ignoredError);
5817 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5818 return Err(ignoredError.StealNSResult());
5820 NS_WARNING_ASSERTION(
5821 !ignoredError.Failed(),
5822 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5824 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContentToMove);
5826 nsresult rv = DoTransactionInternal(moveNodeTransaction);
5827 if (NS_SUCCEEDED(rv)) {
5828 if (mTextServicesDocument) {
5829 const OwningNonNull<TextServicesDocument> textServicesDocument =
5830 *mTextServicesDocument;
5831 textServicesDocument->DidDeleteContent(aContentToMove);
5835 if (!mActionListeners.IsEmpty()) {
5836 for (auto& listener : mActionListeners.Clone()) {
5837 DebugOnly<nsresult> rvIgnored =
5838 listener->DidDeleteNode(&aContentToMove, rv);
5839 NS_WARNING_ASSERTION(
5840 NS_SUCCEEDED(rvIgnored),
5841 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
5845 if (MOZ_UNLIKELY(Destroyed())) {
5846 NS_WARNING(
5847 "MoveNodeTransaction::DoTransaction() caused destroying the editor");
5848 return Err(NS_ERROR_EDITOR_DESTROYED);
5851 if (NS_FAILED(rv)) {
5852 NS_WARNING("MoveNodeTransaction::DoTransaction() failed");
5853 return Err(rv);
5856 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToMove);
5858 return MoveNodeResult::HandledResult(
5859 moveNodeTransaction->SuggestNextInsertionPoint<EditorDOMPoint>(),
5860 moveNodeTransaction->SuggestPointToPutCaret<EditorDOMPoint>());
5863 Result<RefPtr<Element>, nsresult> HTMLEditor::DeleteSelectionAndCreateElement(
5864 nsAtom& aTag, const InitializeInsertingElement& aInitializer) {
5865 MOZ_ASSERT(IsEditActionDataAvailable());
5867 nsresult rv = DeleteSelectionAndPrepareToCreateNode();
5868 if (NS_FAILED(rv)) {
5869 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
5870 return Err(rv);
5873 EditorDOMPoint pointToInsert(SelectionRef().AnchorRef());
5874 if (!pointToInsert.IsSet()) {
5875 return Err(NS_ERROR_FAILURE);
5877 Result<CreateElementResult, nsresult> createNewElementResult =
5878 CreateAndInsertElement(WithTransaction::Yes, aTag, pointToInsert,
5879 aInitializer);
5880 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
5881 NS_WARNING(
5882 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
5883 return createNewElementResult.propagateErr();
5885 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
5887 // We want the selection to be just after the new node
5888 createNewElementResult.inspect().IgnoreCaretPointSuggestion();
5889 rv = CollapseSelectionTo(
5890 EditorRawDOMPoint::After(*createNewElementResult.inspect().GetNewNode()));
5891 if (NS_FAILED(rv)) {
5892 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5893 return Err(rv);
5895 return createNewElementResult.unwrap().UnwrapNewNode();
5898 nsresult HTMLEditor::DeleteSelectionAndPrepareToCreateNode() {
5899 MOZ_ASSERT(IsEditActionDataAvailable());
5901 if (NS_WARN_IF(!SelectionRef().GetAnchorFocusRange())) {
5902 return NS_OK;
5905 if (!SelectionRef().GetAnchorFocusRange()->Collapsed()) {
5906 nsresult rv =
5907 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
5908 if (NS_FAILED(rv)) {
5909 NS_WARNING("EditorBase::DeleteSelectionAsSubAction() failed");
5910 return rv;
5912 MOZ_ASSERT(SelectionRef().GetAnchorFocusRange() &&
5913 SelectionRef().GetAnchorFocusRange()->Collapsed(),
5914 "Selection not collapsed after delete");
5917 // If the selection is a chardata node, split it if necessary and compute
5918 // where to put the new node
5919 EditorDOMPoint atAnchor(SelectionRef().AnchorRef());
5920 if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
5921 return NS_OK;
5924 if (NS_WARN_IF(!atAnchor.GetContainerParent())) {
5925 return NS_ERROR_FAILURE;
5928 if (atAnchor.IsStartOfContainer()) {
5929 const EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
5930 if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
5931 return NS_ERROR_FAILURE;
5933 nsresult rv = CollapseSelectionTo(atAnchorContainer);
5934 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5935 "EditorBase::CollapseSelectionTo() failed");
5936 return rv;
5939 if (atAnchor.IsEndOfContainer()) {
5940 EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
5941 if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
5942 return NS_ERROR_FAILURE;
5944 nsresult rv = CollapseSelectionTo(afterAnchorContainer);
5945 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5946 "EditorBase::CollapseSelectionTo() failed");
5947 return rv;
5950 Result<SplitNodeResult, nsresult> splitAtAnchorResult =
5951 SplitNodeWithTransaction(atAnchor);
5952 if (MOZ_UNLIKELY(splitAtAnchorResult.isErr())) {
5953 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
5954 return splitAtAnchorResult.unwrapErr();
5957 splitAtAnchorResult.inspect().IgnoreCaretPointSuggestion();
5958 const auto atRightContent =
5959 splitAtAnchorResult.inspect().AtNextContent<EditorRawDOMPoint>();
5960 if (NS_WARN_IF(!atRightContent.IsSet())) {
5961 return NS_ERROR_FAILURE;
5963 MOZ_ASSERT(atRightContent.IsSetAndValid());
5964 nsresult rv = CollapseSelectionTo(atRightContent);
5965 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5966 "EditorBase::CollapseSelectionTo() failed");
5967 return rv;
5970 bool HTMLEditor::IsEmpty() const {
5971 if (mPaddingBRElementForEmptyEditor) {
5972 return true;
5975 const Element* activeElement =
5976 GetDocument() ? GetDocument()->GetActiveElement() : nullptr;
5977 const Element* editingHostOrBodyOrRootElement =
5978 activeElement && activeElement->IsEditable()
5979 ? ComputeEditingHost(*activeElement, LimitInBodyElement::No)
5980 : ComputeEditingHost(LimitInBodyElement::No);
5981 if (MOZ_UNLIKELY(!editingHostOrBodyOrRootElement)) {
5982 // If there is no active element nor no selection range in the document,
5983 // let's check entire the document as what we do traditionally.
5984 editingHostOrBodyOrRootElement = GetRoot();
5985 if (!editingHostOrBodyOrRootElement) {
5986 return true;
5990 for (nsIContent* childContent =
5991 editingHostOrBodyOrRootElement->GetFirstChild();
5992 childContent; childContent = childContent->GetNextSibling()) {
5993 if (!childContent->IsText() || childContent->Length()) {
5994 return false;
5997 return true;
6000 // add to aElement the CSS inline styles corresponding to the HTML attribute
6001 // aAttribute with its value aValue
6002 nsresult HTMLEditor::SetAttributeOrEquivalent(Element* aElement,
6003 nsAtom* aAttribute,
6004 const nsAString& aValue,
6005 bool aSuppressTransaction) {
6006 MOZ_ASSERT(aElement);
6007 MOZ_ASSERT(aAttribute);
6009 nsAutoScriptBlocker scriptBlocker;
6010 nsStyledElement* styledElement = nsStyledElement::FromNodeOrNull(aElement);
6011 if (!IsCSSEnabled()) {
6012 // we are not in an HTML+CSS editor; let's set the attribute the HTML way
6013 if (EditorElementStyle::IsHTMLStyle(aAttribute)) {
6014 const EditorElementStyle elementStyle =
6015 EditorElementStyle::Create(*aAttribute);
6016 if (styledElement && elementStyle.IsCSSRemovable(*styledElement)) {
6017 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6018 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6019 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle(
6020 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6021 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr);
6022 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6023 return NS_ERROR_EDITOR_DESTROYED;
6025 NS_WARNING_ASSERTION(
6026 NS_SUCCEEDED(rv),
6027 "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored");
6030 if (aSuppressTransaction) {
6031 nsresult rv =
6032 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
6033 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
6034 return rv;
6036 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
6037 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6038 "EditorBase::SetAttributeWithTransaction() failed");
6039 return rv;
6042 if (EditorElementStyle::IsHTMLStyle(aAttribute)) {
6043 const EditorElementStyle elementStyle =
6044 EditorElementStyle::Create(*aAttribute);
6045 if (styledElement && elementStyle.IsCSSSettable(*styledElement)) {
6046 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6047 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6048 Result<size_t, nsresult> count = CSSEditUtils::SetCSSEquivalentToStyle(
6049 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6050 *this, MOZ_KnownLive(*styledElement), elementStyle, &aValue);
6051 if (MOZ_UNLIKELY(count.isErr())) {
6052 if (NS_WARN_IF(count.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6053 return NS_ERROR_EDITOR_DESTROYED;
6055 NS_WARNING(
6056 "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
6058 if (count.inspect()) {
6059 // we found an equivalence ; let's remove the HTML attribute itself if
6060 // it is set
6061 nsAutoString existingValue;
6062 if (!aElement->GetAttr(aAttribute, existingValue)) {
6063 return NS_OK;
6066 if (aSuppressTransaction) {
6067 nsresult rv =
6068 aElement->UnsetAttr(kNameSpaceID_None, aAttribute, true);
6069 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
6070 return rv;
6072 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
6073 NS_WARNING_ASSERTION(
6074 NS_SUCCEEDED(rv),
6075 "EditorBase::RemoveAttributeWithTransaction() failed");
6076 return rv;
6081 // count is an integer that represents the number of CSS declarations
6082 // applied to the element. If it is zero, we found no equivalence in this
6083 // implementation for the attribute
6084 if (aAttribute == nsGkAtoms::style) {
6085 // if it is the style attribute, just add the new value to the existing
6086 // style attribute's value
6087 nsString existingValue; // Use nsString to avoid copying the string
6088 // buffer at setting the attribute below.
6089 aElement->GetAttr(nsGkAtoms::style, existingValue);
6090 if (!existingValue.IsEmpty()) {
6091 existingValue.Append(HTMLEditUtils::kSpace);
6093 existingValue.Append(aValue);
6094 if (aSuppressTransaction) {
6095 nsresult rv = aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
6096 existingValue, true);
6097 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6098 "Element::SetAttr(nsGkAtoms::style) failed");
6099 return rv;
6101 nsresult rv = SetAttributeWithTransaction(*aElement, *nsGkAtoms::style,
6102 existingValue);
6103 NS_WARNING_ASSERTION(
6104 NS_SUCCEEDED(rv),
6105 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::style) failed");
6106 return rv;
6109 // we have no CSS equivalence for this attribute and it is not the style
6110 // attribute; let's set it the good'n'old HTML way
6111 if (aSuppressTransaction) {
6112 nsresult rv =
6113 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
6114 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
6115 return rv;
6117 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
6118 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6119 "EditorBase::SetAttributeWithTransaction() failed");
6120 return rv;
6123 nsresult HTMLEditor::RemoveAttributeOrEquivalent(Element* aElement,
6124 nsAtom* aAttribute,
6125 bool aSuppressTransaction) {
6126 MOZ_ASSERT(aElement);
6127 MOZ_ASSERT(aAttribute);
6129 if (IsCSSEnabled() && EditorElementStyle::IsHTMLStyle(aAttribute)) {
6130 const EditorElementStyle elementStyle =
6131 EditorElementStyle::Create(*aAttribute);
6132 if (elementStyle.IsCSSRemovable(*aElement)) {
6133 // XXX It might be keep handling attribute even if aElement is not
6134 // an nsStyledElement instance.
6135 nsStyledElement* styledElement =
6136 nsStyledElement::FromNodeOrNull(aElement);
6137 if (NS_WARN_IF(!styledElement)) {
6138 return NS_ERROR_INVALID_ARG;
6140 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6141 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6142 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle(
6143 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6144 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr);
6145 if (NS_FAILED(rv)) {
6146 NS_WARNING("CSSEditUtils::RemoveCSSEquivalentToStyle() failed");
6147 return rv;
6152 if (!aElement->HasAttr(aAttribute)) {
6153 return NS_OK;
6156 if (aSuppressTransaction) {
6157 nsresult rv = aElement->UnsetAttr(kNameSpaceID_None, aAttribute,
6158 /* aNotify = */ true);
6159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
6160 return rv;
6162 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
6163 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6164 "EditorBase::RemoveAttributeWithTransaction() failed");
6165 return rv;
6168 NS_IMETHODIMP HTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) {
6169 AutoEditActionDataSetter editActionData(*this,
6170 EditAction::eEnableOrDisableCSS);
6171 if (NS_WARN_IF(!editActionData.CanHandle())) {
6172 return NS_ERROR_NOT_INITIALIZED;
6175 mIsCSSPrefChecked = aIsCSSPrefChecked;
6176 return NS_OK;
6179 // Set the block background color
6180 nsresult HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction(
6181 const nsAString& aColor) {
6182 MOZ_ASSERT(IsEditActionDataAvailable());
6184 // background-color change and committing composition should be undone
6185 // together
6186 AutoPlaceholderBatch treatAsOneTransaction(
6187 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6189 CommitComposition();
6191 // XXX Shouldn't we do this before calling `CommitComposition()`?
6192 if (IsPlaintextMailComposer()) {
6193 return NS_OK;
6197 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
6198 if (MOZ_UNLIKELY(result.isErr())) {
6199 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
6200 return result.unwrapErr();
6202 if (result.inspect().Canceled()) {
6203 return NS_OK;
6207 IgnoredErrorResult ignoredError;
6208 AutoEditSubActionNotifier startToHandleEditSubAction(
6209 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
6210 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6211 return ignoredError.StealNSResult();
6213 NS_WARNING_ASSERTION(!ignoredError.Failed(),
6214 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
6215 "failed, but ignored");
6217 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
6218 // cases, but removing this may cause the behavior with the legacy
6219 // mutation event listeners. We should try to delete this in a bug.
6220 AutoTransactionsConserveSelection dontChangeMySelection(*this);
6222 AutoRangeArray selectionRanges(SelectionRef());
6223 MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this));
6224 for (const OwningNonNull<nsRange>& domRange : selectionRanges.Ranges()) {
6225 EditorDOMRange range(domRange);
6226 if (NS_WARN_IF(!range.IsPositioned())) {
6227 continue;
6230 if (range.InSameContainer()) {
6231 // If the range is in a text node, set background color of its parent
6232 // block.
6233 if (range.StartRef().IsInTextNode()) {
6234 const RefPtr<nsStyledElement> editableBlockStyledElement =
6235 nsStyledElement::FromNodeOrNull(HTMLEditUtils::GetAncestorElement(
6236 *range.StartRef().ContainerAs<Text>(),
6237 HTMLEditUtils::ClosestEditableBlockElement,
6238 BlockInlineCheck::UseComputedDisplayOutsideStyle));
6239 if (!editableBlockStyledElement ||
6240 !EditorElementStyle::BGColor().IsCSSSettable(
6241 *editableBlockStyledElement)) {
6242 continue;
6244 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6245 WithTransaction::Yes, *this, *editableBlockStyledElement,
6246 EditorElementStyle::BGColor(), &aColor);
6247 if (MOZ_UNLIKELY(result.isErr())) {
6248 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6249 return NS_ERROR_EDITOR_DESTROYED;
6251 NS_WARNING(
6252 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6253 "BGColor()) failed, but ignored");
6255 continue;
6258 // If `Selection` is collapsed in a `<body>` element, set background
6259 // color of the `<body>` element.
6260 if (range.Collapsed() &&
6261 range.StartRef().IsContainerHTMLElement(nsGkAtoms::body)) {
6262 const RefPtr<nsStyledElement> styledElement =
6263 range.StartRef().GetContainerAs<nsStyledElement>();
6264 if (!styledElement ||
6265 !EditorElementStyle::BGColor().IsCSSSettable(*styledElement)) {
6266 continue;
6268 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6269 WithTransaction::Yes, *this, *styledElement,
6270 EditorElementStyle::BGColor(), &aColor);
6271 if (MOZ_UNLIKELY(result.isErr())) {
6272 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6273 return NS_ERROR_EDITOR_DESTROYED;
6275 NS_WARNING(
6276 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6277 "BGColor()) failed, but ignored");
6279 continue;
6282 // If one node is selected, set background color of it if it's a
6283 // block, or of its parent block otherwise.
6284 if ((range.StartRef().IsStartOfContainer() &&
6285 range.EndRef().IsStartOfContainer()) ||
6286 range.StartRef().Offset() + 1 == range.EndRef().Offset()) {
6287 if (NS_WARN_IF(range.StartRef().IsInDataNode())) {
6288 continue;
6290 const RefPtr<nsStyledElement> editableBlockStyledElement =
6291 nsStyledElement::FromNodeOrNull(
6292 HTMLEditUtils::GetInclusiveAncestorElement(
6293 *range.StartRef().GetChild(),
6294 HTMLEditUtils::ClosestEditableBlockElement,
6295 BlockInlineCheck::UseComputedDisplayOutsideStyle));
6296 if (!editableBlockStyledElement ||
6297 !EditorElementStyle::BGColor().IsCSSSettable(
6298 *editableBlockStyledElement)) {
6299 continue;
6301 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6302 WithTransaction::Yes, *this, *editableBlockStyledElement,
6303 EditorElementStyle::BGColor(), &aColor);
6304 if (MOZ_UNLIKELY(result.isErr())) {
6305 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6306 return NS_ERROR_EDITOR_DESTROYED;
6308 NS_WARNING(
6309 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6310 "BGColor()) failed, but ignored");
6312 continue;
6314 } // if (range.InSameContainer())
6316 // Collect editable nodes which are entirely contained in the range.
6317 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
6319 ContentSubtreeIterator subtreeIter;
6320 // If there is no node which is entirely in the range,
6321 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
6322 // don't warn it.
6323 nsresult rv = subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
6324 range.EndRef().ToRawRangeBoundary());
6325 NS_WARNING_ASSERTION(
6326 NS_SUCCEEDED(rv),
6327 "ContentSubtreeIterator::Init() failed, but ignored");
6328 if (NS_SUCCEEDED(rv)) {
6329 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
6330 nsINode* node = subtreeIter.GetCurrentNode();
6331 if (NS_WARN_IF(!node)) {
6332 return NS_ERROR_FAILURE;
6334 if (node->IsContent() && EditorUtils::IsEditableContent(
6335 *node->AsContent(), EditorType::HTML)) {
6336 arrayOfContents.AppendElement(*node->AsContent());
6342 // This caches block parent if we set its background color.
6343 RefPtr<Element> handledBlockParent;
6345 // If start node is a text node, set background color of its parent
6346 // block.
6347 if (range.StartRef().IsInTextNode() &&
6348 EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
6349 EditorType::HTML)) {
6350 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement(
6351 *range.StartRef().ContainerAs<Text>(),
6352 HTMLEditUtils::ClosestEditableBlockElement,
6353 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6354 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6355 handledBlockParent = editableBlockElement;
6356 nsStyledElement* const blockStyledElement =
6357 nsStyledElement::FromNode(handledBlockParent);
6358 if (blockStyledElement &&
6359 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6360 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent
6361 // whose type is RefPtr.
6362 Result<size_t, nsresult> result =
6363 CSSEditUtils::SetCSSEquivalentToStyle(
6364 WithTransaction::Yes, *this,
6365 MOZ_KnownLive(*blockStyledElement),
6366 EditorElementStyle::BGColor(), &aColor);
6367 if (MOZ_UNLIKELY(result.isErr())) {
6368 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6369 return NS_ERROR_EDITOR_DESTROYED;
6371 NS_WARNING(
6372 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6373 "BGColor()) failed, but ignored");
6379 // Then, set background color of each block or block parent of all nodes
6380 // in the range entirely.
6381 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
6382 Element* const editableBlockElement =
6383 HTMLEditUtils::GetInclusiveAncestorElement(
6384 content, HTMLEditUtils::ClosestEditableBlockElement,
6385 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6386 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6387 handledBlockParent = editableBlockElement;
6388 nsStyledElement* const blockStyledElement =
6389 nsStyledElement::FromNode(handledBlockParent);
6390 if (blockStyledElement &&
6391 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6392 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent whose
6393 // type is RefPtr.
6394 Result<size_t, nsresult> result =
6395 CSSEditUtils::SetCSSEquivalentToStyle(
6396 WithTransaction::Yes, *this,
6397 MOZ_KnownLive(*blockStyledElement),
6398 EditorElementStyle::BGColor(), &aColor);
6399 if (MOZ_UNLIKELY(result.isErr())) {
6400 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6401 return NS_ERROR_EDITOR_DESTROYED;
6403 NS_WARNING(
6404 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6405 "BGColor()) failed, but ignored");
6411 // Finally, if end node is a text node, set background color of its
6412 // parent block.
6413 if (range.EndRef().IsInTextNode() &&
6414 EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
6415 EditorType::HTML)) {
6416 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement(
6417 *range.EndRef().ContainerAs<Text>(),
6418 HTMLEditUtils::ClosestEditableBlockElement,
6419 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6420 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6421 const RefPtr<nsStyledElement> blockStyledElement =
6422 nsStyledElement::FromNode(editableBlockElement);
6423 if (blockStyledElement &&
6424 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6425 Result<size_t, nsresult> result =
6426 CSSEditUtils::SetCSSEquivalentToStyle(
6427 WithTransaction::Yes, *this, *blockStyledElement,
6428 EditorElementStyle::BGColor(), &aColor);
6429 if (MOZ_UNLIKELY(result.isErr())) {
6430 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6431 return NS_ERROR_EDITOR_DESTROYED;
6433 NS_WARNING(
6434 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6435 "BGColor()) failed, but ignored");
6440 } // for-loop of selectionRanges
6442 MOZ_ASSERT(selectionRanges.HasSavedRanges());
6443 selectionRanges.RestoreFromSavedRanges();
6444 nsresult rv = selectionRanges.ApplyTo(SelectionRef());
6445 if (NS_WARN_IF(Destroyed())) {
6446 return NS_ERROR_EDITOR_DESTROYED;
6448 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::ApplyTo() failed");
6449 return rv;
6452 NS_IMETHODIMP HTMLEditor::SetBackgroundColor(const nsAString& aColor) {
6453 nsresult rv = SetBackgroundColorAsAction(aColor);
6454 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6455 "HTMLEditor::SetBackgroundColorAsAction() failed");
6456 return rv;
6459 nsresult HTMLEditor::SetBackgroundColorAsAction(const nsAString& aColor,
6460 nsIPrincipal* aPrincipal) {
6461 AutoEditActionDataSetter editActionData(
6462 *this, EditAction::eSetBackgroundColor, aPrincipal);
6463 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
6464 if (NS_FAILED(rv)) {
6465 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
6466 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
6467 return EditorBase::ToGenericNSResult(rv);
6470 if (IsCSSEnabled()) {
6471 // if we are in CSS mode, we have to apply the background color to the
6472 // containing block (or the body if we have no block-level element in
6473 // the document)
6474 nsresult rv = SetBlockBackgroundColorWithCSSAsSubAction(aColor);
6475 NS_WARNING_ASSERTION(
6476 NS_SUCCEEDED(rv),
6477 "HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction() failed");
6478 return EditorBase::ToGenericNSResult(rv);
6481 // but in HTML mode, we can only set the document's background color
6482 rv = SetHTMLBackgroundColorWithTransaction(aColor);
6483 NS_WARNING_ASSERTION(
6484 NS_SUCCEEDED(rv),
6485 "HTMLEditor::SetHTMLBackgroundColorWithTransaction() failed");
6486 return EditorBase::ToGenericNSResult(rv);
6489 Result<EditorDOMPoint, nsresult>
6490 HTMLEditor::CopyLastEditableChildStylesWithTransaction(
6491 Element& aPreviousBlock, Element& aNewBlock, const Element& aEditingHost) {
6492 MOZ_ASSERT(IsEditActionDataAvailable());
6494 // First, clear out aNewBlock. Contract is that we want only the styles
6495 // from aPreviousBlock.
6496 AutoTArray<OwningNonNull<nsIContent>, 32> newBlockChildren;
6497 HTMLEditUtils::CollectAllChildren(aNewBlock, newBlockChildren);
6498 for (const OwningNonNull<nsIContent>& child : newBlockChildren) {
6499 // MOZ_KNownLive(child) because of bug 1622253
6500 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(child));
6501 if (NS_FAILED(rv)) {
6502 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6503 return Err(rv);
6506 if (MOZ_UNLIKELY(aNewBlock.GetFirstChild())) {
6507 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6510 // XXX aNewBlock may be moved or removed. Even in such case, we should
6511 // keep cloning the styles?
6513 // Look for the deepest last editable leaf node in aPreviousBlock.
6514 // Then, if found one is a <br> element, look for non-<br> element.
6515 nsIContent* deepestEditableContent = nullptr;
6516 for (nsCOMPtr<nsIContent> child = &aPreviousBlock; child;
6517 child = HTMLEditUtils::GetLastChild(
6518 *child, {WalkTreeOption::IgnoreNonEditableNode})) {
6519 deepestEditableContent = child;
6521 while (deepestEditableContent &&
6522 deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) {
6523 deepestEditableContent = HTMLEditUtils::GetPreviousContent(
6524 *deepestEditableContent, {WalkTreeOption::IgnoreNonEditableNode},
6525 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost);
6527 if (!deepestEditableContent) {
6528 return EditorDOMPoint(&aNewBlock, 0u);
6531 Element* deepestVisibleEditableElement =
6532 deepestEditableContent->GetAsElementOrParentElement();
6533 if (!deepestVisibleEditableElement) {
6534 return EditorDOMPoint(&aNewBlock, 0u);
6537 // Clone inline elements to keep current style in the new block.
6538 // XXX Looks like that this is really slow if lastEditableDescendant is
6539 // far from aPreviousBlock. Probably, we should clone inline containers
6540 // from ancestor to descendants without transactions, then, insert it
6541 // after that with transaction.
6542 RefPtr<Element> lastClonedElement, firstClonedElement;
6543 for (RefPtr<Element> elementInPreviousBlock = deepestVisibleEditableElement;
6544 elementInPreviousBlock && elementInPreviousBlock != &aPreviousBlock;
6545 elementInPreviousBlock = elementInPreviousBlock->GetParentElement()) {
6546 if (!HTMLEditUtils::IsInlineStyle(elementInPreviousBlock) &&
6547 !elementInPreviousBlock->IsHTMLElement(nsGkAtoms::span)) {
6548 continue;
6550 OwningNonNull<nsAtom> tagName =
6551 *elementInPreviousBlock->NodeInfo()->NameAtom();
6552 // At first time, just create the most descendant inline container
6553 // element.
6554 if (!firstClonedElement) {
6555 Result<CreateElementResult, nsresult> createNewElementResult =
6556 CreateAndInsertElement(
6557 WithTransaction::Yes, tagName, EditorDOMPoint(&aNewBlock, 0u),
6558 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
6559 [&elementInPreviousBlock](
6560 HTMLEditor& aHTMLEditor, Element& aNewElement,
6561 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
6562 // Clone all attributes. Note that despite the method name,
6563 // CloneAttributesWithTransaction does not create
6564 // transactions in this case because aNewElement has not
6565 // been connected yet.
6566 // XXX Looks like that this clones id attribute too.
6567 aHTMLEditor.CloneAttributesWithTransaction(
6568 aNewElement, *elementInPreviousBlock);
6569 return NS_OK;
6571 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
6572 NS_WARNING(
6573 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
6574 return createNewElementResult.propagateErr();
6576 CreateElementResult unwrappedCreateNewElementResult =
6577 createNewElementResult.unwrap();
6578 // We'll return with a point suggesting new caret position and the
6579 // following path does not require an update of selection here.
6580 // Therefore, we don't need to update selection here.
6581 unwrappedCreateNewElementResult.IgnoreCaretPointSuggestion();
6582 firstClonedElement = lastClonedElement =
6583 unwrappedCreateNewElementResult.UnwrapNewNode();
6584 continue;
6586 // Otherwise, inserts new parent inline container to the previous inserted
6587 // inline container.
6588 Result<CreateElementResult, nsresult> wrapClonedElementResult =
6589 InsertContainerWithTransaction(*lastClonedElement, tagName);
6590 if (MOZ_UNLIKELY(wrapClonedElementResult.isErr())) {
6591 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
6592 return wrapClonedElementResult.propagateErr();
6594 CreateElementResult unwrappedWrapClonedElementResult =
6595 wrapClonedElementResult.unwrap();
6596 // We'll return with a point suggesting new caret so that we don't need to
6597 // update selection here.
6598 unwrappedWrapClonedElementResult.IgnoreCaretPointSuggestion();
6599 MOZ_ASSERT(unwrappedWrapClonedElementResult.GetNewNode());
6600 lastClonedElement = unwrappedWrapClonedElementResult.UnwrapNewNode();
6601 CloneAttributesWithTransaction(*lastClonedElement, *elementInPreviousBlock);
6602 if (NS_WARN_IF(Destroyed())) {
6603 return Err(NS_ERROR_EDITOR_DESTROYED);
6607 if (!firstClonedElement) {
6608 // XXX Even if no inline elements are cloned, shouldn't we create new
6609 // <br> element for aNewBlock?
6610 return EditorDOMPoint(&aNewBlock, 0u);
6613 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
6614 WithTransaction::Yes, EditorDOMPoint(firstClonedElement, 0u));
6615 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
6616 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
6617 return insertBRElementResult.propagateErr();
6619 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
6620 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
6621 return EditorDOMPoint(insertBRElementResult.inspect().GetNewNode());
6624 nsresult HTMLEditor::GetElementOrigin(Element& aElement, int32_t& aX,
6625 int32_t& aY) {
6626 aX = 0;
6627 aY = 0;
6629 if (NS_WARN_IF(!IsInitialized())) {
6630 return NS_ERROR_NOT_INITIALIZED;
6632 PresShell* presShell = GetPresShell();
6633 if (NS_WARN_IF(!presShell)) {
6634 return NS_ERROR_NOT_INITIALIZED;
6637 nsIFrame* frame = aElement.GetPrimaryFrame();
6638 if (NS_WARN_IF(!frame)) {
6639 return NS_OK;
6642 nsIFrame* absoluteContainerBlockFrame =
6643 presShell->GetAbsoluteContainingBlock(frame);
6644 if (NS_WARN_IF(!absoluteContainerBlockFrame)) {
6645 return NS_OK;
6647 nsPoint off = frame->GetOffsetTo(absoluteContainerBlockFrame);
6648 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x);
6649 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y);
6651 return NS_OK;
6654 Element* HTMLEditor::GetSelectionContainerElement() const {
6655 MOZ_ASSERT(IsEditActionDataAvailable());
6657 nsINode* focusNode = nullptr;
6658 if (SelectionRef().IsCollapsed()) {
6659 focusNode = SelectionRef().GetFocusNode();
6660 if (NS_WARN_IF(!focusNode)) {
6661 return nullptr;
6663 } else {
6664 const uint32_t rangeCount = SelectionRef().RangeCount();
6665 MOZ_ASSERT(rangeCount, "If 0, Selection::IsCollapsed() should return true");
6667 if (rangeCount == 1) {
6668 const nsRange* range = SelectionRef().GetRangeAt(0);
6670 const RangeBoundary& startRef = range->StartRef();
6671 const RangeBoundary& endRef = range->EndRef();
6673 // This method called GetSelectedElement() to retrieve proper container
6674 // when only one node is selected. However, it simply returns start
6675 // node of Selection with additional cost. So, we do not need to call
6676 // it anymore.
6677 if (startRef.Container()->IsElement() &&
6678 startRef.Container() == endRef.Container() &&
6679 startRef.GetChildAtOffset() &&
6680 startRef.GetChildAtOffset()->GetNextSibling() ==
6681 endRef.GetChildAtOffset()) {
6682 focusNode = startRef.GetChildAtOffset();
6683 MOZ_ASSERT(focusNode, "Start container must not be nullptr");
6684 } else {
6685 focusNode = range->GetClosestCommonInclusiveAncestor();
6686 if (!focusNode) {
6687 NS_WARNING(
6688 "AbstractRange::GetClosestCommonInclusiveAncestor() returned "
6689 "nullptr");
6690 return nullptr;
6693 } else {
6694 for (const uint32_t i : IntegerRange(rangeCount)) {
6695 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
6696 const nsRange* range = SelectionRef().GetRangeAt(i);
6697 MOZ_ASSERT(range);
6698 nsINode* startContainer = range->GetStartContainer();
6699 if (!focusNode) {
6700 focusNode = startContainer;
6701 } else if (focusNode != startContainer) {
6702 // XXX Looks odd to use parent of startContainer because previous
6703 // range may not be in the parent node of current
6704 // startContainer.
6705 focusNode = startContainer->GetParentNode();
6706 // XXX Looks odd to break the for-loop here because we refer only
6707 // first range and another range which starts from different
6708 // container, and the latter range is preferred. Why?
6709 break;
6712 if (!focusNode) {
6713 NS_WARNING("Focused node of selection was not found");
6714 return nullptr;
6719 if (focusNode->IsText()) {
6720 focusNode = focusNode->GetParentNode();
6721 if (NS_WARN_IF(!focusNode)) {
6722 return nullptr;
6726 if (NS_WARN_IF(!focusNode->IsElement())) {
6727 return nullptr;
6729 return focusNode->AsElement();
6732 NS_IMETHODIMP HTMLEditor::IsAnonymousElement(Element* aElement, bool* aReturn) {
6733 if (NS_WARN_IF(!aElement)) {
6734 return NS_ERROR_INVALID_ARG;
6736 *aReturn = aElement->IsRootOfNativeAnonymousSubtree();
6737 return NS_OK;
6740 nsresult HTMLEditor::SetReturnInParagraphCreatesNewParagraph(
6741 bool aCreatesNewParagraph) {
6742 mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
6743 return NS_OK;
6746 bool HTMLEditor::GetReturnInParagraphCreatesNewParagraph() const {
6747 return mCRInParagraphCreatesParagraph;
6750 nsresult HTMLEditor::GetReturnInParagraphCreatesNewParagraph(
6751 bool* aCreatesNewParagraph) {
6752 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
6753 return NS_OK;
6756 NS_IMETHODIMP HTMLEditor::GetWrapWidth(int32_t* aWrapColumn) {
6757 if (NS_WARN_IF(!aWrapColumn)) {
6758 return NS_ERROR_INVALID_ARG;
6760 *aWrapColumn = WrapWidth();
6761 return NS_OK;
6765 // See if the style value includes this attribute, and if it does,
6766 // cut out everything from the attribute to the next semicolon.
6768 static void CutStyle(const char* stylename, nsString& styleValue) {
6769 // Find the current wrapping type:
6770 int32_t styleStart = styleValue.LowerCaseFindASCII(stylename);
6771 if (styleStart >= 0) {
6772 int32_t styleEnd = styleValue.Find(u";", styleStart);
6773 if (styleEnd > styleStart) {
6774 styleValue.Cut(styleStart, styleEnd - styleStart + 1);
6775 } else {
6776 styleValue.Cut(styleStart, styleValue.Length() - styleStart);
6781 NS_IMETHODIMP HTMLEditor::SetWrapWidth(int32_t aWrapColumn) {
6782 AutoEditActionDataSetter editActionData(*this, EditAction::eSetWrapWidth);
6783 if (NS_WARN_IF(!editActionData.CanHandle())) {
6784 return NS_ERROR_NOT_INITIALIZED;
6787 mWrapColumn = aWrapColumn;
6789 // Make sure we're a plaintext editor, otherwise we shouldn't
6790 // do the rest of this.
6791 if (!IsPlaintextMailComposer()) {
6792 return NS_OK;
6795 // Ought to set a style sheet here...
6796 RefPtr<Element> rootElement = GetRoot();
6797 if (NS_WARN_IF(!rootElement)) {
6798 return NS_ERROR_NOT_INITIALIZED;
6801 // Get the current style for this root element:
6802 nsAutoString styleValue;
6803 rootElement->GetAttr(nsGkAtoms::style, styleValue);
6805 // We'll replace styles for these values:
6806 CutStyle("white-space", styleValue);
6807 CutStyle("width", styleValue);
6808 CutStyle("font-family", styleValue);
6810 // If we have other style left, trim off any existing semicolons
6811 // or white-space, then add a known semicolon-space:
6812 if (!styleValue.IsEmpty()) {
6813 styleValue.Trim("; \t", false, true);
6814 styleValue.AppendLiteral("; ");
6817 // Make sure we have fixed-width font. This should be done for us,
6818 // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
6819 // Only do this if we're wrapping.
6820 if (IsWrapHackEnabled() && aWrapColumn >= 0) {
6821 styleValue.AppendLiteral("font-family: -moz-fixed; ");
6824 // and now we're ready to set the new white-space/wrapping style.
6825 if (aWrapColumn > 0) {
6826 // Wrap to a fixed column.
6827 styleValue.AppendLiteral("white-space: pre-wrap; width: ");
6828 styleValue.AppendInt(aWrapColumn);
6829 styleValue.AppendLiteral("ch;");
6830 } else if (!aWrapColumn) {
6831 styleValue.AppendLiteral("white-space: pre-wrap;");
6832 } else {
6833 styleValue.AppendLiteral("white-space: pre;");
6836 nsresult rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
6837 styleValue, true);
6838 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6839 "Element::SetAttr(nsGkAtoms::style) failed");
6840 return rv;
6843 Element* HTMLEditor::GetFocusedElement() const {
6844 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6845 if (NS_WARN_IF(!focusManager)) {
6846 return nullptr;
6849 Element* const focusedElement = focusManager->GetFocusedElement();
6851 Document* document = GetDocument();
6852 if (NS_WARN_IF(!document)) {
6853 return nullptr;
6855 const bool inDesignMode = IsInDesignMode();
6856 if (!focusedElement) {
6857 // in designMode, nobody gets focus in most cases.
6858 if (inDesignMode && OurWindowHasFocus()) {
6859 return document->GetRootElement();
6861 return nullptr;
6864 if (inDesignMode) {
6865 return OurWindowHasFocus() &&
6866 focusedElement->IsInclusiveDescendantOf(document)
6867 ? focusedElement
6868 : nullptr;
6871 // We're HTML editor for contenteditable
6873 // If the focused content isn't editable, or it has independent selection,
6874 // we don't have focus.
6875 if (!focusedElement->HasFlag(NODE_IS_EDITABLE) ||
6876 focusedElement->HasIndependentSelection()) {
6877 return nullptr;
6879 // If our window is focused, we're focused.
6880 return OurWindowHasFocus() ? focusedElement : nullptr;
6883 bool HTMLEditor::IsActiveInDOMWindow() const {
6884 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6885 if (NS_WARN_IF(!focusManager)) {
6886 return false;
6889 Document* document = GetDocument();
6890 if (NS_WARN_IF(!document)) {
6891 return false;
6893 const bool inDesignMode = IsInDesignMode();
6895 // If we're in designMode, we're always active in the DOM window.
6896 if (inDesignMode) {
6897 return true;
6900 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
6901 nsCOMPtr<nsPIDOMWindowOuter> win;
6902 nsIContent* content = nsFocusManager::GetFocusedDescendant(
6903 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win));
6904 if (!content) {
6905 return false;
6908 // We're HTML editor for contenteditable
6910 // If the active content isn't editable, or it has independent selection,
6911 // we're not active).
6912 if (!content->HasFlag(NODE_IS_EDITABLE) ||
6913 content->HasIndependentSelection()) {
6914 return false;
6916 return true;
6919 Element* HTMLEditor::ComputeEditingHostInternal(
6920 const nsIContent* aContent, LimitInBodyElement aLimitInBodyElement) const {
6921 Document* document = GetDocument();
6922 if (NS_WARN_IF(!document)) {
6923 return nullptr;
6926 auto MaybeLimitInBodyElement =
6927 [&](const Element* aCandidiateEditingHost) -> Element* {
6928 if (!aCandidiateEditingHost) {
6929 return nullptr;
6931 if (aLimitInBodyElement != LimitInBodyElement::Yes) {
6932 return const_cast<Element*>(aCandidiateEditingHost);
6934 // By default, we should limit editing host to the <body> element for
6935 // avoiding deleting or creating unexpected elements outside the <body>.
6936 // However, this is incompatible with Chrome so that we should stop
6937 // doing this with adding safety checks more.
6938 if (document->GetBodyElement() &&
6939 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6940 aCandidiateEditingHost, document->GetBodyElement())) {
6941 return const_cast<Element*>(aCandidiateEditingHost);
6943 // XXX If aContent is an editing host and has no parent node, we reach here,
6944 // but returing the <body> which is not connected to aContent is odd.
6945 return document->GetBodyElement();
6948 if (IsInDesignMode()) {
6949 // TODO: In this case, we need to compute editing host from aContent or the
6950 // focus node of selection, and it may be in an editing host in a
6951 // shadow DOM tree etc. We need to do more complicated things.
6952 // See also InDesignMode().
6953 return document->GetBodyElement();
6956 // We're HTML editor for contenteditable
6957 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6958 if (NS_WARN_IF(!editActionData.CanHandle())) {
6959 return nullptr;
6962 const nsIContent* const content =
6963 aContent ? aContent
6964 : nsIContent::FromNodeOrNull(SelectionRef().GetFocusNode());
6965 if (NS_WARN_IF(!content)) {
6966 return nullptr;
6969 // If the active content isn't editable, we're not active.
6970 if (!content->HasFlag(NODE_IS_EDITABLE)) {
6971 return nullptr;
6974 // Although the content shouldn't be in a native anonymous subtree, but
6975 // perhaps due to a bug of Selection or Range API, it may occur. HTMLEditor
6976 // shouldn't touch native anonymous subtree so that return nullptr in such
6977 // case.
6978 if (MOZ_UNLIKELY(content->IsInNativeAnonymousSubtree())) {
6979 return nullptr;
6982 // Note that `Selection` can be in <input> or <textarea>. In the case, we
6983 // need to look for an ancestor which does not have editable parent.
6984 return MaybeLimitInBodyElement(
6985 const_cast<nsIContent*>(content)->GetEditingHost());
6988 void HTMLEditor::NotifyEditingHostMaybeChanged() {
6989 // Note that even if the document is in design mode, a contenteditable element
6990 // in a shadow tree is focusable. Therefore, we may need to update editing
6991 // host even when the document is in design mode.
6992 if (MOZ_UNLIKELY(NS_WARN_IF(!GetDocument()))) {
6993 return;
6996 // We're HTML editor for contenteditable
6997 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6998 if (NS_WARN_IF(!editActionData.CanHandle())) {
6999 return;
7002 // Get selection ancestor limit which may be old editing host.
7003 nsIContent* ancestorLimiter = SelectionRef().GetAncestorLimiter();
7004 if (!ancestorLimiter) {
7005 // If we've not initialized selection ancestor limit, we should wait focus
7006 // event to set proper limiter.
7007 return;
7010 // Compute current editing host.
7011 nsIContent* editingHost = ComputeEditingHost();
7012 if (NS_WARN_IF(!editingHost)) {
7013 return;
7016 // Update selection ancestor limit if current editing host includes the
7017 // previous editing host.
7018 // Additionally, the editing host may be an element in shadow DOM and the
7019 // shadow host is in designMode. In this case, we need to set the editing
7020 // host as the new selection limiter.
7021 if (ancestorLimiter->IsInclusiveDescendantOf(editingHost) ||
7022 (ancestorLimiter->IsInDesignMode() != editingHost->IsInDesignMode())) {
7023 // Note that don't call HTMLEditor::InitializeSelectionAncestorLimit()
7024 // here because it may collapse selection to the first editable node.
7025 EditorBase::InitializeSelectionAncestorLimit(*editingHost);
7029 EventTarget* HTMLEditor::GetDOMEventTarget() const {
7030 // Don't use getDocument here, because we have no way of knowing
7031 // whether Init() was ever called. So we need to get the document
7032 // ourselves, if it exists.
7033 Document* doc = GetDocument();
7034 MOZ_ASSERT(doc, "The HTMLEditor has not been initialized yet");
7035 if (!doc) {
7036 return nullptr;
7039 // Register the EditorEventListener to the parent of window.
7041 // The advantage of this approach is HTMLEditor can still
7042 // receive events when shadow dom is involved.
7043 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
7044 return win->GetParentTarget();
7046 return nullptr;
7049 bool HTMLEditor::ShouldReplaceRootElement() const {
7050 if (!mRootElement) {
7051 // If we don't know what is our root element, we should find our root.
7052 return true;
7055 // If we temporary set document root element to mRootElement, but there is
7056 // body element now, we should replace the root element by the body element.
7057 return mRootElement != GetBodyElement();
7060 void HTMLEditor::NotifyRootChanged() {
7061 MOZ_ASSERT(mPendingRootElementUpdatedRunner,
7062 "HTMLEditor::NotifyRootChanged() should be called via a runner");
7063 mPendingRootElementUpdatedRunner = nullptr;
7065 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
7067 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
7068 if (NS_WARN_IF(!editActionData.CanHandle())) {
7069 return;
7072 RemoveEventListeners();
7073 nsresult rv = InstallEventListeners();
7074 if (NS_FAILED(rv)) {
7075 NS_WARNING("HTMLEditor::InstallEventListeners() failed, but ignored");
7076 return;
7079 UpdateRootElement();
7080 if (!mRootElement) {
7081 return;
7084 rv = MaybeCollapseSelectionAtFirstEditableNode(false);
7085 if (NS_FAILED(rv)) {
7086 NS_WARNING(
7087 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) "
7088 "failed, "
7089 "but ignored");
7090 return;
7093 // When this editor has focus, we need to reset the selection limiter to
7094 // new root. Otherwise, that is going to be done when this gets focus.
7095 nsCOMPtr<nsINode> node = GetFocusedNode();
7096 if (node) {
7097 DebugOnly<nsresult> rvIgnored = InitializeSelection(*node);
7098 NS_WARNING_ASSERTION(
7099 NS_SUCCEEDED(rvIgnored),
7100 "EditorBase::InitializeSelection() failed, but ignored");
7103 SyncRealTimeSpell();
7106 Element* HTMLEditor::GetBodyElement() const {
7107 Document* document = GetDocument();
7108 MOZ_ASSERT(document, "The HTMLEditor hasn't been initialized yet");
7109 if (NS_WARN_IF(!document)) {
7110 return nullptr;
7112 return document->GetBody();
7115 nsINode* HTMLEditor::GetFocusedNode() const {
7116 Element* focusedElement = GetFocusedElement();
7117 if (!focusedElement) {
7118 return nullptr;
7121 // focusedElement might be non-null even focusManager->GetFocusedElement()
7122 // is null. That's the designMode case, and in that case our
7123 // FocusedContent() returns the root element, but we want to return
7124 // the document.
7126 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
7127 NS_ASSERTION(focusManager, "Focus manager is null");
7128 if ((focusedElement = focusManager->GetFocusedElement())) {
7129 return focusedElement;
7132 return GetDocument();
7135 bool HTMLEditor::OurWindowHasFocus() const {
7136 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
7137 if (NS_WARN_IF(!focusManager)) {
7138 return false;
7140 nsPIDOMWindowOuter* focusedWindow = focusManager->GetFocusedWindow();
7141 if (!focusedWindow) {
7142 return false;
7144 Document* document = GetDocument();
7145 if (NS_WARN_IF(!document)) {
7146 return false;
7148 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
7149 return ourWindow == focusedWindow;
7152 bool HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const {
7153 if (!EditorBase::IsAcceptableInputEvent(aGUIEvent)) {
7154 return false;
7157 // While there is composition, all composition events in its top level
7158 // window are always fired on the composing editor. Therefore, if this
7159 // editor has composition, the composition events should be handled in this
7160 // editor.
7161 if (mComposition && aGUIEvent->AsCompositionEvent()) {
7162 return true;
7165 nsCOMPtr<nsINode> eventTargetNode =
7166 nsINode::FromEventTargetOrNull(aGUIEvent->GetOriginalDOMEventTarget());
7167 if (NS_WARN_IF(!eventTargetNode)) {
7168 return false;
7171 if (eventTargetNode->IsContent()) {
7172 eventTargetNode =
7173 eventTargetNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
7174 if (NS_WARN_IF(!eventTargetNode)) {
7175 return false;
7179 RefPtr<Document> document = GetDocument();
7180 if (NS_WARN_IF(!document)) {
7181 return false;
7184 if (IsInDesignMode()) {
7185 // If this editor is in designMode and the event target is the document,
7186 // the event is for this editor.
7187 if (eventTargetNode->IsDocument()) {
7188 return eventTargetNode == document;
7190 // Otherwise, check whether the event target is in this document or not.
7191 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
7192 return false;
7194 if (document == eventTargetNode->GetUncomposedDoc()) {
7195 return true;
7197 // If the event target is in a shadow tree, the content is not editable
7198 // by default, but if the focused content is an editing host, we need to
7199 // handle it as contenteditable mode.
7200 if (!eventTargetNode->IsInShadowTree()) {
7201 return false;
7205 // Space event for <button> and <summary> with contenteditable
7206 // should be handle by the themselves.
7207 if (aGUIEvent->mMessage == eKeyPress &&
7208 aGUIEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
7209 nsGenericHTMLElement* element =
7210 HTMLButtonElement::FromNode(eventTargetNode);
7211 if (!element) {
7212 element = HTMLSummaryElement::FromNode(eventTargetNode);
7215 if (element && element->IsContentEditable()) {
7216 return false;
7219 // This HTML editor is for contenteditable. We need to check the validity
7220 // of the target.
7221 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
7222 return false;
7225 // If the event is a mouse event, we need to check if the target content is
7226 // the focused editing host or its descendant.
7227 if (aGUIEvent->AsMouseEventBase()) {
7228 nsIContent* editingHost = ComputeEditingHost();
7229 // If there is no active editing host, we cannot handle the mouse event
7230 // correctly.
7231 if (!editingHost) {
7232 return false;
7234 // If clicked on non-editable root element but the body element is the
7235 // active editing host, we should assume that the click event is
7236 // targetted.
7237 if (eventTargetNode == document->GetRootElement() &&
7238 !eventTargetNode->HasFlag(NODE_IS_EDITABLE) &&
7239 editingHost == document->GetBodyElement()) {
7240 eventTargetNode = editingHost;
7242 // If the target element is neither the active editing host nor a
7243 // descendant of it, we may not be able to handle the event.
7244 if (!eventTargetNode->IsInclusiveDescendantOf(editingHost)) {
7245 return false;
7247 // If the clicked element has an independent selection, we shouldn't
7248 // handle this click event.
7249 if (eventTargetNode->AsContent()->HasIndependentSelection()) {
7250 return false;
7252 // If the target content is editable, we should handle this event.
7253 return eventTargetNode->HasFlag(NODE_IS_EDITABLE);
7256 // If the target of the other events which target focused element isn't
7257 // editable or has an independent selection, this editor shouldn't handle
7258 // the event.
7259 if (!eventTargetNode->HasFlag(NODE_IS_EDITABLE) ||
7260 eventTargetNode->AsContent()->HasIndependentSelection()) {
7261 return false;
7264 // Finally, check whether we're actually focused or not. When we're not
7265 // focused, we should ignore the dispatched event by script (or something)
7266 // because content editable element needs selection in itself for editing.
7267 // However, when we're not focused, it's not guaranteed.
7268 return IsActiveInDOMWindow();
7271 nsresult HTMLEditor::GetPreferredIMEState(IMEState* aState) {
7272 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
7273 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
7274 if (IsReadonly()) {
7275 aState->mEnabled = IMEEnabled::Disabled;
7276 } else {
7277 aState->mEnabled = IMEEnabled::Enabled;
7279 return NS_OK;
7282 already_AddRefed<Element> HTMLEditor::GetInputEventTargetElement() const {
7283 RefPtr<Element> target = ComputeEditingHost(LimitInBodyElement::No);
7284 if (target) {
7285 return target.forget();
7288 // When there is no active editing host due to focus node is a
7289 // non-editable node, we should look for its editable parent to
7290 // dispatch `beforeinput` event.
7291 nsIContent* focusContent =
7292 nsIContent::FromNodeOrNull(SelectionRef().GetFocusNode());
7293 if (!focusContent || focusContent->IsEditable()) {
7294 return nullptr;
7296 for (Element* element : focusContent->AncestorsOfType<Element>()) {
7297 if (element->IsEditable()) {
7298 target = element->GetEditingHost();
7299 return target.forget();
7302 return nullptr;
7305 nsresult HTMLEditor::OnModifyDocument() {
7306 MOZ_ASSERT(mPendingDocumentModifiedRunner,
7307 "HTMLEditor::OnModifyDocument() should be called via a runner");
7308 mPendingDocumentModifiedRunner = nullptr;
7310 if (IsEditActionDataAvailable()) {
7311 return OnModifyDocumentInternal();
7314 AutoEditActionDataSetter editActionData(
7315 *this, EditAction::eCreatePaddingBRElementForEmptyEditor);
7316 if (NS_WARN_IF(!editActionData.CanHandle())) {
7317 return NS_ERROR_NOT_AVAILABLE;
7320 nsresult rv = OnModifyDocumentInternal();
7321 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7322 "HTMLEditor::OnModifyDocumentInternal() failed");
7323 return rv;
7326 nsresult HTMLEditor::OnModifyDocumentInternal() {
7327 MOZ_ASSERT(IsEditActionDataAvailable());
7328 MOZ_ASSERT(!mPendingDocumentModifiedRunner);
7330 // EnsureNoPaddingBRElementForEmptyEditor() below may cause a flush, which
7331 // could destroy the editor
7332 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
7334 // Delete our padding <br> element for empty editor, if we have one, since
7335 // the document might not be empty any more.
7336 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
7337 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7338 return rv;
7340 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7341 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
7342 "failed, but ignored");
7344 // Try to recreate the padding <br> element for empty editor if needed.
7345 rv = MaybeCreatePaddingBRElementForEmptyEditor();
7346 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7347 return NS_ERROR_EDITOR_DESTROYED;
7349 NS_WARNING_ASSERTION(
7350 NS_SUCCEEDED(rv),
7351 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
7353 return rv;
7356 } // namespace mozilla