Bug 1883912: Enable Intl.ListFormat test for "unit" style. r=spidermonkey-reviewers...
[gecko.git] / editor / libeditor / HTMLEditor.cpp
blobe9ea8887b76c17d7ab1c2b1cbb3476e590bd9abc
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 nsAutoString href;
3777 anchor->GetHref(href);
3778 if (href.IsEmpty()) {
3779 return NS_OK;
3782 AutoPlaceholderBatch treatAsOneTransaction(
3783 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3785 // Set all attributes found on the supplied anchor element
3786 RefPtr<nsDOMAttributeMap> attributeMap = anchor->Attributes();
3787 if (NS_WARN_IF(!attributeMap)) {
3788 return NS_ERROR_FAILURE;
3791 // TODO: We should stop using this loop for adding attributes to newly created
3792 // `<a href="...">` elements. Then, we can avoid to increate the ref-
3793 // counter of attribute names since we can use nsStaticAtom if we don't
3794 // need to support unknown attributes.
3795 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
3796 stylesToSet.SetCapacity(attributeMap->Length());
3797 nsString value;
3798 for (uint32_t i : IntegerRange(attributeMap->Length())) {
3799 RefPtr<Attr> attribute = attributeMap->Item(i);
3800 if (!attribute) {
3801 continue;
3804 RefPtr<nsAtom> attributeName = attribute->NodeInfo()->NameAtom();
3806 MOZ_ASSERT(value.IsEmpty());
3807 attribute->GetValue(value);
3809 stylesToSet.AppendElement(EditorInlineStyleAndValue(
3810 *nsGkAtoms::a, std::move(attributeName), std::move(value)));
3812 rv = SetInlinePropertiesAsSubAction(stylesToSet);
3813 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3814 "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
3815 return rv;
3818 nsresult HTMLEditor::SetHTMLBackgroundColorWithTransaction(
3819 const nsAString& aColor) {
3820 MOZ_ASSERT(IsEditActionDataAvailable());
3822 // Find a selected or enclosing table element to set background on
3823 bool isCellSelected = false;
3824 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError =
3825 GetSelectedOrParentTableElement(&isCellSelected);
3826 if (cellOrRowOrTableElementOrError.isErr()) {
3827 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
3828 return cellOrRowOrTableElementOrError.unwrapErr();
3831 bool setColor = !aColor.IsEmpty();
3832 RefPtr<Element> rootElementOfBackgroundColor =
3833 cellOrRowOrTableElementOrError.unwrap();
3834 if (rootElementOfBackgroundColor) {
3835 // Needs to set or remove background color of each selected cell elements.
3836 // Therefore, just the cell contains selection range, we don't need to
3837 // do this. Note that users can select each cell, but with Selection API,
3838 // web apps can select <tr> and <td> at same time. With <table>, looks
3839 // odd, though.
3840 if (isCellSelected || rootElementOfBackgroundColor->IsAnyOfHTMLElements(
3841 nsGkAtoms::table, nsGkAtoms::tr)) {
3842 SelectedTableCellScanner scanner(SelectionRef());
3843 if (scanner.IsInTableCellSelectionMode()) {
3844 if (setColor) {
3845 for (const OwningNonNull<Element>& cellElement :
3846 scanner.ElementsRef()) {
3847 // `MOZ_KnownLive(cellElement)` is safe because of `scanner`
3848 // is stack only class and keeps grabbing it until it's destroyed.
3849 nsresult rv = SetAttributeWithTransaction(
3850 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor, aColor);
3851 if (NS_WARN_IF(Destroyed())) {
3852 return NS_ERROR_EDITOR_DESTROYED;
3854 if (NS_FAILED(rv)) {
3855 NS_WARNING(
3856 "EditorBase::::SetAttributeWithTransaction(nsGkAtoms::"
3857 "bgcolor) failed");
3858 return rv;
3861 return NS_OK;
3863 for (const OwningNonNull<Element>& cellElement :
3864 scanner.ElementsRef()) {
3865 // `MOZ_KnownLive(cellElement)` is safe because of `scanner`
3866 // is stack only class and keeps grabbing it until it's destroyed.
3867 nsresult rv = RemoveAttributeWithTransaction(
3868 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor);
3869 if (NS_FAILED(rv)) {
3870 NS_WARNING(
3871 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor)"
3872 " failed");
3873 return rv;
3876 return NS_OK;
3879 // If we failed to find a cell, fall through to use originally-found element
3880 } else {
3881 // No table element -- set the background color on the body tag
3882 rootElementOfBackgroundColor = GetRoot();
3883 if (NS_WARN_IF(!rootElementOfBackgroundColor)) {
3884 return NS_ERROR_FAILURE;
3887 // Use the editor method that goes through the transaction system
3888 if (setColor) {
3889 nsresult rv = SetAttributeWithTransaction(*rootElementOfBackgroundColor,
3890 *nsGkAtoms::bgcolor, aColor);
3891 NS_WARNING_ASSERTION(
3892 NS_SUCCEEDED(rv),
3893 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3894 return rv;
3896 nsresult rv = RemoveAttributeWithTransaction(*rootElementOfBackgroundColor,
3897 *nsGkAtoms::bgcolor);
3898 NS_WARNING_ASSERTION(
3899 NS_SUCCEEDED(rv),
3900 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3901 return rv;
3904 nsresult HTMLEditor::RemoveEmptyInclusiveAncestorInlineElements(
3905 nsIContent& aContent) {
3906 MOZ_ASSERT(IsEditActionDataAvailable());
3907 MOZ_ASSERT(!aContent.Length());
3909 Element* editingHost = aContent.GetEditingHost();
3910 if (NS_WARN_IF(!editingHost)) {
3911 return NS_ERROR_FAILURE;
3914 if (&aContent == editingHost ||
3915 HTMLEditUtils::IsBlockElement(
3916 aContent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
3917 !EditorUtils::IsEditableContent(aContent, EditorType::HTML) ||
3918 !aContent.GetParent()) {
3919 return NS_OK;
3922 // Don't strip wrappers if this is the only wrapper in the block. Then we'll
3923 // add a <br> later, so it won't be an empty wrapper in the end.
3924 // XXX This is different from Blink. We should delete empty inline element
3925 // even if it's only child of the block element.
3927 const Element* editableBlockElement = HTMLEditUtils::GetAncestorElement(
3928 aContent, HTMLEditUtils::ClosestEditableBlockElement,
3929 BlockInlineCheck::UseComputedDisplayOutsideStyle);
3930 if (!editableBlockElement ||
3931 HTMLEditUtils::IsEmptyNode(
3932 *editableBlockElement,
3933 {EmptyCheckOption::TreatSingleBRElementAsVisible,
3934 EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
3935 return NS_OK;
3939 OwningNonNull<nsIContent> content = aContent;
3940 for (nsIContent* parentContent : aContent.AncestorsOfType<nsIContent>()) {
3941 if (HTMLEditUtils::IsBlockElement(
3942 *parentContent, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
3943 parentContent->Length() != 1 ||
3944 !EditorUtils::IsEditableContent(*parentContent, EditorType::HTML) ||
3945 parentContent == editingHost) {
3946 break;
3948 content = *parentContent;
3951 nsresult rv = DeleteNodeWithTransaction(content);
3952 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3953 "EditorBase::DeleteNodeWithTransaction() failed");
3954 return rv;
3957 nsresult HTMLEditor::DeleteAllChildrenWithTransaction(Element& aElement) {
3958 MOZ_ASSERT(IsEditActionDataAvailable());
3960 // Prevent rules testing until we're done
3961 IgnoredErrorResult ignoredError;
3962 AutoEditSubActionNotifier startToHandleEditSubAction(
3963 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
3964 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3965 return ignoredError.StealNSResult();
3967 NS_WARNING_ASSERTION(
3968 !ignoredError.Failed(),
3969 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3971 while (nsCOMPtr<nsIContent> child = aElement.GetLastChild()) {
3972 nsresult rv = DeleteNodeWithTransaction(*child);
3973 if (NS_FAILED(rv)) {
3974 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3975 return rv;
3978 return NS_OK;
3981 NS_IMETHODIMP HTMLEditor::DeleteNode(nsINode* aNode, bool aPreserveSelection,
3982 uint8_t aOptionalArgCount) {
3983 if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aNode->IsContent())) {
3984 return NS_ERROR_INVALID_ARG;
3987 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveNode);
3988 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3989 if (NS_FAILED(rv)) {
3990 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3991 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3992 return EditorBase::ToGenericNSResult(rv);
3995 // Make dispatch `input` event after stopping preserving selection.
3996 AutoPlaceholderBatch treatAsOneTransaction(
3997 *this,
3998 ScrollSelectionIntoView::No, // not a user interaction
3999 __FUNCTION__);
4001 Maybe<AutoTransactionsConserveSelection> preserveSelection;
4002 if (aOptionalArgCount && aPreserveSelection) {
4003 preserveSelection.emplace(*this);
4006 rv = DeleteNodeWithTransaction(MOZ_KnownLive(*aNode->AsContent()));
4007 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4008 "EditorBase::DeleteNodeWithTransaction() failed");
4009 return rv;
4012 Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction(
4013 Text& aTextNode, uint32_t aOffset, uint32_t aLength) {
4014 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
4015 return Err(NS_ERROR_FAILURE);
4018 Result<CaretPoint, nsresult> caretPointOrError =
4019 EditorBase::DeleteTextWithTransaction(aTextNode, aOffset, aLength);
4020 NS_WARNING_ASSERTION(caretPointOrError.isOk(),
4021 "EditorBase::DeleteTextWithTransaction() failed");
4022 return caretPointOrError;
4025 Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
4026 Text& aTextNode, uint32_t aOffset, uint32_t aLength,
4027 const nsAString& aStringToInsert) {
4028 MOZ_ASSERT(IsEditActionDataAvailable());
4029 MOZ_ASSERT(aLength > 0 || !aStringToInsert.IsEmpty());
4031 if (aStringToInsert.IsEmpty()) {
4032 Result<CaretPoint, nsresult> caretPointOrError =
4033 DeleteTextWithTransaction(aTextNode, aOffset, aLength);
4034 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
4035 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed");
4036 return caretPointOrError.propagateErr();
4038 return InsertTextResult(EditorDOMPointInText(&aTextNode, aOffset),
4039 caretPointOrError.unwrap());
4042 if (!aLength) {
4043 RefPtr<Document> document = GetDocument();
4044 if (NS_WARN_IF(!document)) {
4045 return Err(NS_ERROR_NOT_INITIALIZED);
4047 Result<InsertTextResult, nsresult> insertTextResult =
4048 InsertTextWithTransaction(*document, aStringToInsert,
4049 EditorDOMPoint(&aTextNode, aOffset));
4050 NS_WARNING_ASSERTION(insertTextResult.isOk(),
4051 "HTMLEditor::InsertTextWithTransaction() failed");
4052 return insertTextResult;
4055 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
4056 return Err(NS_ERROR_FAILURE);
4059 // This should emulates inserting text for better undo/redo behavior.
4060 IgnoredErrorResult ignoredError;
4061 AutoEditSubActionNotifier startToHandleEditSubAction(
4062 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
4063 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4064 return Err(NS_ERROR_EDITOR_DESTROYED);
4066 NS_WARNING_ASSERTION(
4067 !ignoredError.Failed(),
4068 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4070 // FYI: Create the insertion point before changing the DOM tree because
4071 // the point may become invalid offset after that.
4072 EditorDOMPointInText pointToInsert(&aTextNode, aOffset);
4074 RefPtr<ReplaceTextTransaction> transaction = ReplaceTextTransaction::Create(
4075 *this, aStringToInsert, aTextNode, aOffset, aLength);
4076 MOZ_ASSERT(transaction);
4078 if (aLength && !mActionListeners.IsEmpty()) {
4079 for (auto& listener : mActionListeners.Clone()) {
4080 DebugOnly<nsresult> rvIgnored =
4081 listener->WillDeleteText(&aTextNode, aOffset, aLength);
4082 NS_WARNING_ASSERTION(
4083 NS_SUCCEEDED(rvIgnored),
4084 "nsIEditActionListener::WillDeleteText() failed, but ignored");
4088 nsresult rv = DoTransactionInternal(transaction);
4089 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4090 "EditorBase::DoTransactionInternal() failed");
4092 // Don't check whether we've been destroyed here because we need to notify
4093 // listeners and observers below even if we've already destroyed.
4095 EditorDOMPointInText endOfInsertedText(&aTextNode,
4096 aOffset + aStringToInsert.Length());
4098 if (pointToInsert.IsSet()) {
4099 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert);
4100 if (begin.IsSet() && end.IsSet()) {
4101 TopLevelEditSubActionDataRef().DidDeleteText(
4102 *this, begin.To<EditorRawDOMPoint>());
4103 TopLevelEditSubActionDataRef().DidInsertText(
4104 *this, begin.To<EditorRawDOMPoint>(), end.To<EditorRawDOMPoint>());
4107 // XXX Should we update endOfInsertedText here?
4110 if (!mActionListeners.IsEmpty()) {
4111 for (auto& listener : mActionListeners.Clone()) {
4112 DebugOnly<nsresult> rvIgnored =
4113 listener->DidInsertText(&aTextNode, aOffset, aStringToInsert, rv);
4114 NS_WARNING_ASSERTION(
4115 NS_SUCCEEDED(rvIgnored),
4116 "nsIEditActionListener::DidInsertText() failed, but ignored");
4120 if (NS_WARN_IF(Destroyed())) {
4121 return Err(NS_ERROR_EDITOR_DESTROYED);
4124 return InsertTextResult(
4125 std::move(endOfInsertedText),
4126 transaction->SuggestPointToPutCaret<EditorDOMPoint>());
4129 Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction(
4130 Document& aDocument, const nsAString& aStringToInsert,
4131 const EditorDOMPoint& aPointToInsert) {
4132 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
4133 return Err(NS_ERROR_INVALID_ARG);
4136 // Do nothing if the node is read-only
4137 if (MOZ_UNLIKELY(NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
4138 *aPointToInsert.GetContainer())))) {
4139 return Err(NS_ERROR_FAILURE);
4142 return EditorBase::InsertTextWithTransaction(aDocument, aStringToInsert,
4143 aPointToInsert);
4146 Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertBRElement(
4147 const EditorDOMPoint& aPointToInsert) {
4148 MOZ_ASSERT(IsEditActionDataAvailable());
4150 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
4151 return Err(NS_ERROR_FAILURE);
4154 if (!aPointToInsert.IsInTextNode()) {
4155 return aPointToInsert;
4158 if (aPointToInsert.IsStartOfContainer()) {
4159 // Insert before the text node.
4160 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
4161 if (!pointInContainer.IsSet()) {
4162 NS_WARNING("Failed to climb up the DOM tree from text node");
4163 return Err(NS_ERROR_FAILURE);
4165 return pointInContainer;
4168 if (aPointToInsert.IsEndOfContainer()) {
4169 // Insert after the text node.
4170 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
4171 if (NS_WARN_IF(!pointInContainer.IsSet())) {
4172 NS_WARNING("Failed to climb up the DOM tree from text node");
4173 return Err(NS_ERROR_FAILURE);
4175 MOZ_ALWAYS_TRUE(pointInContainer.AdvanceOffset());
4176 return pointInContainer;
4179 MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
4181 // Unfortunately, we need to split the text node at the offset.
4182 Result<SplitNodeResult, nsresult> splitTextNodeResult =
4183 SplitNodeWithTransaction(aPointToInsert);
4184 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
4185 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4186 return splitTextNodeResult.propagateErr();
4188 nsresult rv = splitTextNodeResult.inspect().SuggestCaretPointTo(
4189 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
4190 if (NS_FAILED(rv)) {
4191 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
4192 return Err(rv);
4195 // Insert new <br> before the right node.
4196 auto atNextContent =
4197 splitTextNodeResult.inspect().AtNextContent<EditorDOMPoint>();
4198 if (MOZ_UNLIKELY(!atNextContent.IsSet())) {
4199 NS_WARNING("The next node seems not in the DOM tree");
4200 return Err(NS_ERROR_FAILURE);
4202 return atNextContent;
4205 Result<CreateElementResult, nsresult> HTMLEditor::InsertBRElement(
4206 WithTransaction aWithTransaction, const EditorDOMPoint& aPointToInsert,
4207 EDirection aSelect /* = eNone */) {
4208 MOZ_ASSERT(IsEditActionDataAvailable());
4210 Result<EditorDOMPoint, nsresult> maybePointToInsert =
4211 PrepareToInsertBRElement(aPointToInsert);
4212 if (maybePointToInsert.isErr()) {
4213 NS_WARNING(
4214 nsPrintfCString("HTMLEditor::PrepareToInsertBRElement(%s) failed",
4215 ToString(aWithTransaction).c_str())
4216 .get());
4217 return maybePointToInsert.propagateErr();
4219 MOZ_ASSERT(maybePointToInsert.inspect().IsSetAndValid());
4221 Result<CreateElementResult, nsresult> createNewBRElementResult =
4222 CreateAndInsertElement(aWithTransaction, *nsGkAtoms::br,
4223 maybePointToInsert.inspect());
4224 if (MOZ_UNLIKELY(createNewBRElementResult.isErr())) {
4225 NS_WARNING(nsPrintfCString("HTMLEditor::CreateAndInsertElement(%s) failed",
4226 ToString(aWithTransaction).c_str())
4227 .get());
4228 return createNewBRElementResult.propagateErr();
4230 CreateElementResult unwrappedCreateNewBRElementResult =
4231 createNewBRElementResult.unwrap();
4232 RefPtr<Element> newBRElement =
4233 unwrappedCreateNewBRElementResult.UnwrapNewNode();
4234 MOZ_ASSERT(newBRElement);
4236 unwrappedCreateNewBRElementResult.IgnoreCaretPointSuggestion();
4237 switch (aSelect) {
4238 case eNext: {
4239 const auto pointToPutCaret = EditorDOMPoint::After(
4240 *newBRElement, Selection::InterlinePosition::StartOfNextLine);
4241 return CreateElementResult(std::move(newBRElement), pointToPutCaret);
4243 case ePrevious: {
4244 const auto pointToPutCaret = EditorDOMPoint(
4245 newBRElement, Selection::InterlinePosition::StartOfNextLine);
4246 return CreateElementResult(std::move(newBRElement), pointToPutCaret);
4248 default:
4249 NS_WARNING(
4250 "aSelect has invalid value, the caller need to set selection "
4251 "by itself");
4252 [[fallthrough]];
4253 case eNone:
4254 return CreateElementResult(
4255 std::move(newBRElement),
4256 unwrappedCreateNewBRElementResult.UnwrapCaretPoint());
4260 Result<CreateElementResult, nsresult>
4261 HTMLEditor::InsertContainerWithTransaction(
4262 nsIContent& aContentToBeWrapped, const nsAtom& aWrapperTagName,
4263 const InitializeInsertingElement& aInitializer) {
4264 EditorDOMPoint pointToInsertNewContainer(&aContentToBeWrapped);
4265 if (NS_WARN_IF(!pointToInsertNewContainer.IsSet())) {
4266 return Err(NS_ERROR_FAILURE);
4268 // aContentToBeWrapped will be moved to the new container before inserting the
4269 // new container. So, when we insert the container, the insertion point is
4270 // before the next sibling of aContentToBeWrapped.
4271 // XXX If pointerToInsertNewContainer stores offset here, the offset and
4272 // referring child node become mismatched. Although, currently this
4273 // is not a problem since InsertNodeTransaction refers only child node.
4274 MOZ_ALWAYS_TRUE(pointToInsertNewContainer.AdvanceOffset());
4276 // Create new container.
4277 RefPtr<Element> newContainer = CreateHTMLContent(&aWrapperTagName);
4278 if (NS_WARN_IF(!newContainer)) {
4279 return Err(NS_ERROR_FAILURE);
4282 if (&aInitializer != &HTMLEditor::DoNothingForNewElement) {
4283 nsresult rv = aInitializer(*this, *newContainer,
4284 EditorDOMPoint(&aContentToBeWrapped));
4285 if (NS_WARN_IF(Destroyed())) {
4286 return Err(NS_ERROR_EDITOR_DESTROYED);
4288 if (NS_FAILED(rv)) {
4289 NS_WARNING("aInitializer() failed");
4290 return Err(rv);
4294 // Notify our internal selection state listener
4295 AutoInsertContainerSelNotify selNotify(RangeUpdaterRef());
4297 // Put aNode in the new container, first.
4298 // XXX Perhaps, we should not remove the container if it's not editable.
4299 nsresult rv = DeleteNodeWithTransaction(aContentToBeWrapped);
4300 if (NS_FAILED(rv)) {
4301 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4302 return Err(rv);
4306 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
4307 // in normal cases. However, it may be required for nested edit
4308 // actions which may be caused by legacy mutation event listeners or
4309 // chrome script.
4310 AutoTransactionsConserveSelection conserveSelection(*this);
4311 Result<CreateContentResult, nsresult> insertContentNodeResult =
4312 InsertNodeWithTransaction(aContentToBeWrapped,
4313 EditorDOMPoint(newContainer, 0u));
4314 if (MOZ_UNLIKELY(insertContentNodeResult.isErr())) {
4315 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
4316 return insertContentNodeResult.propagateErr();
4318 insertContentNodeResult.inspect().IgnoreCaretPointSuggestion();
4321 // Put the new container where aNode was.
4322 Result<CreateElementResult, nsresult> insertNewContainerElementResult =
4323 InsertNodeWithTransaction<Element>(*newContainer,
4324 pointToInsertNewContainer);
4325 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(),
4326 "EditorBase::InsertNodeWithTransaction() failed");
4327 return insertNewContainerElementResult;
4330 Result<CreateElementResult, nsresult>
4331 HTMLEditor::ReplaceContainerWithTransactionInternal(
4332 Element& aOldContainer, const nsAtom& aTagName, const nsAtom& aAttribute,
4333 const nsAString& aAttributeValue, bool aCloneAllAttributes) {
4334 MOZ_ASSERT(IsEditActionDataAvailable());
4336 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aOldContainer)) ||
4337 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aOldContainer))) {
4338 return Err(NS_ERROR_FAILURE);
4341 // If we're replacing <dd> or <dt> with different type of element, we need to
4342 // split the parent <dl>.
4343 OwningNonNull<Element> containerElementToDelete = aOldContainer;
4344 if (aOldContainer.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt) &&
4345 &aTagName != nsGkAtoms::dt && &aTagName != nsGkAtoms::dd &&
4346 // aOldContainer always has a parent node because of removable.
4347 aOldContainer.GetParentNode()->IsHTMLElement(nsGkAtoms::dl)) {
4348 OwningNonNull<Element> const dlElement = *aOldContainer.GetParentElement();
4349 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(dlElement)) ||
4350 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(dlElement))) {
4351 return Err(NS_ERROR_FAILURE);
4353 Result<SplitRangeOffFromNodeResult, nsresult> splitDLElementResult =
4354 SplitRangeOffFromElement(dlElement, aOldContainer, aOldContainer);
4355 if (MOZ_UNLIKELY(splitDLElementResult.isErr())) {
4356 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed");
4357 return splitDLElementResult.propagateErr();
4359 splitDLElementResult.inspect().IgnoreCaretPointSuggestion();
4360 RefPtr<Element> middleDLElement = aOldContainer.GetParentElement();
4361 if (NS_WARN_IF(!middleDLElement) ||
4362 NS_WARN_IF(!middleDLElement->IsHTMLElement(nsGkAtoms::dl)) ||
4363 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleDLElement))) {
4364 NS_WARNING("The parent <dl> was lost at splitting it");
4365 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4367 containerElementToDelete = std::move(middleDLElement);
4370 const RefPtr<Element> newContainer = CreateHTMLContent(&aTagName);
4371 if (NS_WARN_IF(!newContainer)) {
4372 return Err(NS_ERROR_FAILURE);
4375 // Set or clone attribute if needed.
4376 // FIXME: What should we do attributes of <dl> elements if we removed it
4377 // above?
4378 if (aCloneAllAttributes) {
4379 MOZ_ASSERT(&aAttribute == nsGkAtoms::_empty);
4380 CloneAttributesWithTransaction(*newContainer, aOldContainer);
4381 } else if (&aAttribute != nsGkAtoms::_empty) {
4382 nsresult rv = newContainer->SetAttr(kNameSpaceID_None,
4383 const_cast<nsAtom*>(&aAttribute),
4384 aAttributeValue, true);
4385 if (NS_FAILED(rv)) {
4386 NS_WARNING("Element::SetAttr() failed");
4387 return Err(NS_ERROR_FAILURE);
4391 const OwningNonNull<nsINode> parentNode =
4392 *containerElementToDelete->GetParentNode();
4393 const nsCOMPtr<nsINode> referenceNode =
4394 containerElementToDelete->GetNextSibling();
4395 AutoReplaceContainerSelNotify selStateNotify(RangeUpdaterRef(), aOldContainer,
4396 *newContainer);
4398 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildren;
4399 HTMLEditUtils::CollectChildren(
4400 aOldContainer, arrayOfChildren, 0u,
4401 // Move non-editable children too because its container, aElement, is
4402 // editable so that all children must be removable node.
4403 {});
4404 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary
4405 // in normal cases. However, it may be required for nested edit
4406 // actions which may be caused by legacy mutation event listeners or
4407 // chrome script.
4408 AutoTransactionsConserveSelection conserveSelection(*this);
4409 // Move all children from the old container to the new container.
4410 // For making all MoveNodeTransactions have a reference node in the current
4411 // parent, move nodes from last one to preceding ones.
4412 for (const OwningNonNull<nsIContent>& child : Reversed(arrayOfChildren)) {
4413 Result<MoveNodeResult, nsresult> moveChildResult =
4414 MoveNodeWithTransaction(MOZ_KnownLive(child), // due to bug 1622253.
4415 EditorDOMPoint(newContainer, 0u));
4416 if (MOZ_UNLIKELY(moveChildResult.isErr())) {
4417 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4418 return moveChildResult.propagateErr();
4420 // We'll suggest new caret point which is suggested by new container
4421 // element insertion result. Therefore, we need to do nothing here.
4422 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4426 // Delete containerElementToDelete from the DOM tree to make it not referred
4427 // by InsertNodeTransaction.
4428 nsresult rv = DeleteNodeWithTransaction(containerElementToDelete);
4429 if (NS_FAILED(rv)) {
4430 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4431 return Err(rv);
4434 if (referenceNode && (!referenceNode->GetParentNode() ||
4435 parentNode != referenceNode->GetParentNode())) {
4436 NS_WARNING(
4437 "The reference node for insertion has been moved to different parent, "
4438 "so we got lost the insertion point");
4439 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4442 // Finally, insert the new node to where probably aOldContainer was.
4443 Result<CreateElementResult, nsresult> insertNewContainerElementResult =
4444 InsertNodeWithTransaction<Element>(
4445 *newContainer, referenceNode ? EditorDOMPoint(referenceNode)
4446 : EditorDOMPoint::AtEndOf(*parentNode));
4447 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(),
4448 "EditorBase::InsertNodeWithTransaction() failed");
4449 MOZ_ASSERT_IF(
4450 insertNewContainerElementResult.isOk(),
4451 insertNewContainerElementResult.inspect().GetNewNode() == newContainer);
4452 return insertNewContainerElementResult;
4455 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveContainerWithTransaction(
4456 Element& aElement) {
4457 MOZ_ASSERT(IsEditActionDataAvailable());
4459 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aElement)) ||
4460 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aElement))) {
4461 return Err(NS_ERROR_FAILURE);
4464 // Notify our internal selection state listener.
4465 AutoRemoveContainerSelNotify selNotify(RangeUpdaterRef(),
4466 EditorRawDOMPoint(&aElement));
4468 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildren;
4469 HTMLEditUtils::CollectChildren(
4470 aElement, arrayOfChildren, 0u,
4471 // Move non-editable children too because its container, aElement, is
4472 // editable so that all children must be removable node.
4473 {});
4474 const OwningNonNull<nsINode> parentNode = *aElement.GetParentNode();
4475 nsCOMPtr<nsIContent> previousChild = aElement.GetPreviousSibling();
4476 // For making all MoveNodeTransactions have a referenc node in the current
4477 // parent, move nodes from last one to preceding ones.
4478 for (const OwningNonNull<nsIContent>& child : Reversed(arrayOfChildren)) {
4479 if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(child))) {
4480 continue;
4482 Result<MoveNodeResult, nsresult> moveChildResult = MoveNodeWithTransaction(
4483 MOZ_KnownLive(child), // due to bug 1622253.
4484 previousChild ? EditorDOMPoint::After(previousChild)
4485 : EditorDOMPoint(parentNode, 0u));
4486 if (MOZ_UNLIKELY(moveChildResult.isErr())) {
4487 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4488 return moveChildResult.propagateErr();
4490 // If the reference node was moved to different container, try to recover
4491 // the original position.
4492 if (previousChild &&
4493 MOZ_UNLIKELY(previousChild->GetParentNode() != parentNode)) {
4494 if (MOZ_UNLIKELY(child->GetParentNode() != parentNode)) {
4495 NS_WARNING(
4496 "Neither the reference (previous) sibling nor the moved child was "
4497 "in the expected parent node");
4498 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4499 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4501 previousChild = child->GetPreviousSibling();
4503 // We'll need to put caret at next sibling of aElement if nobody moves
4504 // content nodes under the parent node except us.
4505 moveChildResult.inspect().IgnoreCaretPointSuggestion();
4508 if (aElement.GetParentNode() && aElement.GetParentNode() != parentNode) {
4509 NS_WARNING(
4510 "The removing element has already been moved to another element");
4511 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4514 NS_WARNING_ASSERTION(!aElement.GetFirstChild(),
4515 "The removing container still has some children, but "
4516 "they are removed by removing the container");
4518 auto GetNextSiblingOf =
4519 [](const nsTArray<OwningNonNull<nsIContent>>& aArrayOfMovedContent,
4520 const nsINode& aExpectedParentNode) -> nsIContent* {
4521 for (const OwningNonNull<nsIContent>& movedChild :
4522 Reversed(aArrayOfMovedContent)) {
4523 if (movedChild != &aExpectedParentNode) {
4524 continue; // Ignore moved node which was moved to different place
4526 return movedChild->GetNextSibling();
4528 // XXX If all nodes were moved by web apps, we cannot suggest "collect"
4529 // position without computing the index of aElement. However, I
4530 // don't think that it's necessary for the web apps in the wild.
4531 return nullptr;
4534 nsCOMPtr<nsIContent> nextSibling =
4535 aElement.GetParentNode() ? aElement.GetNextSibling()
4536 : GetNextSiblingOf(arrayOfChildren, *parentNode);
4538 nsresult rv = DeleteNodeWithTransaction(aElement);
4539 if (NS_FAILED(rv)) {
4540 NS_WARNING("HTMLEditor::DeleteNodeTransaction() failed");
4541 return Err(rv);
4544 if (nextSibling && nextSibling->GetParentNode() != parentNode) {
4545 nextSibling = GetNextSiblingOf(arrayOfChildren, *parentNode);
4547 return nextSibling ? EditorDOMPoint(nextSibling)
4548 : EditorDOMPoint::AtEndOf(*parentNode);
4551 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentAppended(
4552 nsIContent* aFirstNewContent) {
4553 DoContentInserted(aFirstNewContent, ContentNodeIs::Appended);
4556 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentInserted(
4557 nsIContent* aChild) {
4558 DoContentInserted(aChild, ContentNodeIs::Inserted);
4561 bool HTMLEditor::IsInObservedSubtree(nsIContent* aChild) {
4562 if (!aChild) {
4563 return false;
4566 // FIXME(emilio, bug 1596856): This should probably work if the root is in the
4567 // same shadow tree as the child, probably? I don't know what the
4568 // contenteditable-in-shadow-dom situation is.
4569 if (Element* root = GetRoot()) {
4570 // To be super safe here, check both ChromeOnlyAccess and NAC / Shadow DOM.
4571 // That catches (also unbound) native anonymous content and ShadowDOM.
4572 if (root->ChromeOnlyAccess() != aChild->ChromeOnlyAccess() ||
4573 root->IsInNativeAnonymousSubtree() !=
4574 aChild->IsInNativeAnonymousSubtree() ||
4575 root->IsInShadowTree() != aChild->IsInShadowTree()) {
4576 return false;
4580 return !aChild->ChromeOnlyAccess() && !aChild->IsInShadowTree() &&
4581 !aChild->IsInNativeAnonymousSubtree();
4584 void HTMLEditor::DoContentInserted(nsIContent* aChild,
4585 ContentNodeIs aContentNodeIs) {
4586 MOZ_ASSERT(aChild);
4587 nsINode* container = aChild->GetParentNode();
4588 MOZ_ASSERT(container);
4590 if (!IsInObservedSubtree(aChild)) {
4591 return;
4594 // XXX Why do we need this? This method is a helper of mutation observer.
4595 // So, the callers of mutation observer should guarantee that this won't
4596 // be deleted at least during the call.
4597 RefPtr<HTMLEditor> kungFuDeathGrip(this);
4599 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
4600 if (NS_WARN_IF(!editActionData.CanHandle())) {
4601 return;
4604 if (ShouldReplaceRootElement()) {
4605 UpdateRootElement();
4606 if (mPendingRootElementUpdatedRunner) {
4607 return;
4609 mPendingRootElementUpdatedRunner = NewRunnableMethod(
4610 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
4611 nsContentUtils::AddScriptRunner(
4612 do_AddRef(mPendingRootElementUpdatedRunner));
4613 return;
4616 // We don't need to handle our own modifications
4617 if (!GetTopLevelEditSubAction() && container->IsEditable()) {
4618 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
4619 // Ignore insertion of the padding <br> element.
4620 return;
4622 nsresult rv = OnDocumentModified();
4623 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
4624 return;
4626 NS_WARNING_ASSERTION(
4627 NS_SUCCEEDED(rv),
4628 "HTMLEditor::OnDocumentModified() failed, but ignored");
4630 // Update spellcheck for only the newly-inserted node (bug 743819)
4631 if (mInlineSpellChecker) {
4632 nsIContent* endContent = aChild;
4633 if (aContentNodeIs == ContentNodeIs::Appended) {
4634 nsIContent* child = nullptr;
4635 for (child = aChild; child; child = child->GetNextSibling()) {
4636 if (child->InclusiveDescendantMayNeedSpellchecking(this)) {
4637 break;
4640 if (!child) {
4641 // No child needed spellchecking, return.
4642 return;
4645 // Maybe more than 1 child was appended.
4646 endContent = container->GetLastChild();
4647 } else if (!aChild->InclusiveDescendantMayNeedSpellchecking(this)) {
4648 return;
4651 RefPtr<nsRange> range = nsRange::Create(aChild);
4652 range->SelectNodesInContainer(container, aChild, endContent);
4653 DebugOnly<nsresult> rvIgnored =
4654 mInlineSpellChecker->SpellCheckRange(range);
4655 NS_WARNING_ASSERTION(
4656 rvIgnored == NS_ERROR_NOT_INITIALIZED || NS_SUCCEEDED(rvIgnored),
4657 "mozInlineSpellChecker::SpellCheckRange() failed, but ignored");
4662 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentRemoved(
4663 nsIContent* aChild, nsIContent* aPreviousSibling) {
4664 if (!IsInObservedSubtree(aChild)) {
4665 return;
4668 // XXX Why do we need to do this? This method is a mutation observer's
4669 // method. Therefore, the caller should guarantee that this won't be
4670 // deleted during the call.
4671 RefPtr<HTMLEditor> kungFuDeathGrip(this);
4673 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
4674 if (NS_WARN_IF(!editActionData.CanHandle())) {
4675 return;
4678 if (SameCOMIdentity(aChild, mRootElement)) {
4679 mRootElement = nullptr;
4680 if (mPendingRootElementUpdatedRunner) {
4681 return;
4683 mPendingRootElementUpdatedRunner = NewRunnableMethod(
4684 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
4685 nsContentUtils::AddScriptRunner(
4686 do_AddRef(mPendingRootElementUpdatedRunner));
4687 return;
4690 // We don't need to handle our own modifications
4691 if (!GetTopLevelEditSubAction() && aChild->GetParentNode()->IsEditable()) {
4692 if (aChild && EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
4693 // Ignore removal of the padding <br> element for empty editor.
4694 return;
4697 nsresult rv = OnDocumentModified();
4698 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
4699 return;
4701 NS_WARNING_ASSERTION(
4702 NS_SUCCEEDED(rv),
4703 "HTMLEditor::OnDocumentModified() failed, but ignored");
4707 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::CharacterDataChanged(
4708 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
4709 if (!mInlineSpellChecker || !aContent->IsEditable() ||
4710 !IsInObservedSubtree(aContent) ||
4711 GetTopLevelEditSubAction() != EditSubAction::eNone) {
4712 return;
4715 nsIContent* parent = aContent->GetParent();
4716 if (!parent || !parent->InclusiveDescendantMayNeedSpellchecking(this)) {
4717 return;
4720 RefPtr<nsRange> range = nsRange::Create(aContent);
4721 range->SelectNodesInContainer(parent, aContent, aContent);
4722 DebugOnly<nsresult> rvIgnored = mInlineSpellChecker->SpellCheckRange(range);
4725 nsresult HTMLEditor::SelectEntireDocument() {
4726 MOZ_ASSERT(IsEditActionDataAvailable());
4728 if (!mInitSucceeded) {
4729 return NS_ERROR_NOT_INITIALIZED;
4732 // XXX It's odd to select all of the document body if an contenteditable
4733 // element has focus.
4734 RefPtr<Element> bodyOrDocumentElement = GetRoot();
4735 if (NS_WARN_IF(!bodyOrDocumentElement)) {
4736 return NS_ERROR_NOT_INITIALIZED;
4739 // If we're empty, don't select all children because that would select the
4740 // padding <br> element for empty editor.
4741 if (IsEmpty()) {
4742 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
4743 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4744 "EditorBase::CollapseSelectionToStartOf() failed");
4745 return rv;
4748 // Otherwise, select all children.
4749 ErrorResult error;
4750 SelectionRef().SelectAllChildren(*bodyOrDocumentElement, error);
4751 if (NS_WARN_IF(Destroyed())) {
4752 error.SuppressException();
4753 return NS_ERROR_EDITOR_DESTROYED;
4755 NS_WARNING_ASSERTION(!error.Failed(),
4756 "Selection::SelectAllChildren() failed");
4757 return error.StealNSResult();
4760 nsresult HTMLEditor::SelectAllInternal() {
4761 MOZ_ASSERT(IsEditActionDataAvailable());
4763 CommitComposition();
4764 if (NS_WARN_IF(Destroyed())) {
4765 return NS_ERROR_EDITOR_DESTROYED;
4768 auto GetBodyElementIfElementIsParentOfHTMLBody =
4769 [](const Element& aElement) -> Element* {
4770 if (!aElement.OwnerDoc()->IsHTMLDocument()) {
4771 return const_cast<Element*>(&aElement);
4773 HTMLBodyElement* bodyElement = aElement.OwnerDoc()->GetBodyElement();
4774 return bodyElement && nsContentUtils::ContentIsFlattenedTreeDescendantOf(
4775 bodyElement, &aElement)
4776 ? bodyElement
4777 : const_cast<Element*>(&aElement);
4780 nsCOMPtr<nsIContent> selectionRootContent =
4781 [&]() MOZ_CAN_RUN_SCRIPT -> nsIContent* {
4782 RefPtr<Element> elementToBeSelected = [&]() -> Element* {
4783 // If there is at least one selection range, we should compute the
4784 // selection root from the anchor node.
4785 if (SelectionRef().RangeCount()) {
4786 if (nsIContent* content =
4787 nsIContent::FromNodeOrNull(SelectionRef().GetAnchorNode())) {
4788 if (content->IsElement()) {
4789 return content->AsElement();
4791 if (Element* parentElement =
4792 content->GetParentElementCrossingShadowRoot()) {
4793 return parentElement;
4797 // If no element contains a selection range, we should select all children
4798 // of the focused element at least.
4799 if (Element* focusedElement = GetFocusedElement()) {
4800 return focusedElement;
4802 // of the body or document element.
4803 Element* bodyOrDocumentElement = GetRoot();
4804 NS_WARNING_ASSERTION(bodyOrDocumentElement,
4805 "There was no element in the document");
4806 return bodyOrDocumentElement;
4807 }();
4809 // If the element to be selected is <input type="text"> or <textarea>,
4810 // GetSelectionRootContent() returns its anonymous <div> element, but we
4811 // want to select all of the document or selection limiter. Therefore,
4812 // we should use its parent to compute the selection root.
4813 if (elementToBeSelected->HasIndependentSelection()) {
4814 Element* parentElement = elementToBeSelected->GetParentElement();
4815 if (MOZ_LIKELY(parentElement)) {
4816 elementToBeSelected = parentElement;
4820 // Then, compute the selection root content to select all including
4821 // elementToBeSelected.
4822 RefPtr<PresShell> presShell = GetPresShell();
4823 nsIContent* computedSelectionRootContent =
4824 elementToBeSelected->GetSelectionRootContent(presShell);
4825 if (NS_WARN_IF(!computedSelectionRootContent)) {
4826 return nullptr;
4828 if (MOZ_UNLIKELY(!computedSelectionRootContent->IsElement())) {
4829 return computedSelectionRootContent;
4831 return GetBodyElementIfElementIsParentOfHTMLBody(
4832 *computedSelectionRootContent->AsElement());
4833 }();
4834 if (NS_WARN_IF(!selectionRootContent)) {
4835 return NS_ERROR_FAILURE;
4838 Maybe<Selection::AutoUserInitiated> userSelection;
4839 // XXX Do we need to mark it as "user initiated" for
4840 // `Document.execCommand("selectAll")`?
4841 if (!selectionRootContent->IsEditable()) {
4842 userSelection.emplace(SelectionRef());
4844 ErrorResult error;
4845 SelectionRef().SelectAllChildren(*selectionRootContent, error);
4846 NS_WARNING_ASSERTION(!error.Failed(),
4847 "Selection::SelectAllChildren() failed");
4848 return error.StealNSResult();
4851 bool HTMLEditor::SetCaretInTableCell(Element* aElement) {
4852 MOZ_ASSERT(IsEditActionDataAvailable());
4854 if (!aElement || !aElement->IsHTMLElement() ||
4855 !HTMLEditUtils::IsAnyTableElement(aElement)) {
4856 return false;
4858 const RefPtr<Element> editingHost = ComputeEditingHost();
4859 if (!editingHost || !aElement->IsInclusiveDescendantOf(editingHost)) {
4860 return false;
4863 nsCOMPtr<nsIContent> deepestFirstChild = aElement;
4864 while (deepestFirstChild->HasChildren()) {
4865 deepestFirstChild = deepestFirstChild->GetFirstChild();
4868 // Set selection at beginning of the found node
4869 nsresult rv = CollapseSelectionToStartOf(*deepestFirstChild);
4870 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4871 "EditorBase::CollapseSelectionToStartOf() failed");
4872 return NS_SUCCEEDED(rv);
4876 * This method scans the selection for adjacent text nodes
4877 * and collapses them into a single text node.
4878 * "adjacent" means literally adjacent siblings of the same parent.
4879 * Uses HTMLEditor::JoinNodesWithTransaction() so action is undoable.
4880 * Should be called within the context of a batch transaction.
4882 nsresult HTMLEditor::CollapseAdjacentTextNodes(nsRange& aInRange) {
4883 AutoTransactionsConserveSelection dontChangeMySelection(*this);
4885 // we can't actually do anything during iteration, so store the text nodes in
4886 // an array first.
4887 DOMSubtreeIterator subtreeIter;
4888 if (NS_FAILED(subtreeIter.Init(aInRange))) {
4889 NS_WARNING("DOMSubtreeIterator::Init() failed");
4890 return NS_ERROR_FAILURE;
4892 AutoTArray<OwningNonNull<Text>, 8> textNodes;
4893 subtreeIter.AppendNodesToArray(
4894 +[](nsINode& aNode, void*) -> bool {
4895 return EditorUtils::IsEditableContent(*aNode.AsText(),
4896 EditorType::HTML);
4898 textNodes);
4900 // now that I have a list of text nodes, collapse adjacent text nodes
4901 while (textNodes.Length() > 1u) {
4902 OwningNonNull<Text>& leftTextNode = textNodes[0u];
4903 OwningNonNull<Text>& rightTextNode = textNodes[1u];
4905 // If the text nodes are not direct siblings, we shouldn't join them, and
4906 // we don't need to handle the left one anymore.
4907 if (rightTextNode->GetPreviousSibling() != leftTextNode) {
4908 textNodes.RemoveElementAt(0u);
4909 continue;
4912 Result<JoinNodesResult, nsresult> joinNodesResult =
4913 JoinNodesWithTransaction(MOZ_KnownLive(*leftTextNode),
4914 MOZ_KnownLive(*rightTextNode));
4915 if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
4916 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
4917 return joinNodesResult.unwrapErr();
4919 if (MOZ_LIKELY(joinNodesResult.inspect().RemovedContent() ==
4920 leftTextNode)) {
4921 textNodes.RemoveElementAt(0u);
4922 } else if (MOZ_LIKELY(joinNodesResult.inspect().RemovedContent() ==
4923 rightTextNode)) {
4924 textNodes.RemoveElementAt(1u);
4925 } else {
4926 MOZ_ASSERT_UNREACHABLE(
4927 "HTMLEditor::JoinNodesWithTransaction() removed unexpected node");
4928 return NS_ERROR_UNEXPECTED;
4932 return NS_OK;
4935 nsresult HTMLEditor::SetSelectionAtDocumentStart() {
4936 MOZ_ASSERT(IsEditActionDataAvailable());
4938 RefPtr<Element> rootElement = GetRoot();
4939 if (NS_WARN_IF(!rootElement)) {
4940 return NS_ERROR_FAILURE;
4943 nsresult rv = CollapseSelectionToStartOf(*rootElement);
4944 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4945 "EditorBase::CollapseSelectionToStartOf() failed");
4946 return rv;
4950 * Remove aNode, reparenting any children into the parent of aNode. In
4951 * addition, insert any br's needed to preserve identity of removed block.
4953 Result<EditorDOMPoint, nsresult>
4954 HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
4955 MOZ_ASSERT(IsEditActionDataAvailable());
4957 // Two possibilities: the container could be empty of editable content. If
4958 // that is the case, we need to compare what is before and after aNode to
4959 // determine if we need a br.
4961 // Or it could be not empty, in which case we have to compare previous
4962 // sibling and first child to determine if we need a leading br, and compare
4963 // following sibling and last child to determine if we need a trailing br.
4965 EditorDOMPoint pointToPutCaret;
4966 if (nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild(
4967 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
4968 // The case of aNode not being empty. We need a br at start unless:
4969 // 1) previous sibling of aNode is a block, OR
4970 // 2) previous sibling of aNode is a br, OR
4971 // 3) first child of aNode is a block OR
4972 // 4) either is null
4974 if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling(
4975 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
4976 if (!HTMLEditUtils::IsBlockElement(
4977 *previousSibling,
4978 BlockInlineCheck::UseComputedDisplayOutsideStyle) &&
4979 !previousSibling->IsHTMLElement(nsGkAtoms::br) &&
4980 !HTMLEditUtils::IsBlockElement(
4981 *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
4982 // Insert br node
4983 Result<CreateElementResult, nsresult> insertBRElementResult =
4984 InsertBRElement(WithTransaction::Yes,
4985 EditorDOMPoint(&aElement, 0u));
4986 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
4987 NS_WARNING(
4988 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
4989 return insertBRElementResult.propagateErr();
4991 CreateElementResult unwrappedInsertBRElementResult =
4992 insertBRElementResult.unwrap();
4993 unwrappedInsertBRElementResult.MoveCaretPointTo(
4994 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
4995 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
4999 // We need a br at end unless:
5000 // 1) following sibling of aNode is a block, OR
5001 // 2) last child of aNode is a block, OR
5002 // 3) last child of aNode is a br OR
5003 // 4) either is null
5005 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
5006 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5007 if (nextSibling &&
5008 !HTMLEditUtils::IsBlockElement(
5009 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle)) {
5010 if (nsIContent* lastChild = HTMLEditUtils::GetLastChild(
5011 aElement, {WalkTreeOption::IgnoreNonEditableNode},
5012 BlockInlineCheck::Unused)) {
5013 if (!HTMLEditUtils::IsBlockElement(
5014 *lastChild, BlockInlineCheck::UseComputedDisplayStyle) &&
5015 !lastChild->IsHTMLElement(nsGkAtoms::br)) {
5016 Result<CreateElementResult, nsresult> insertBRElementResult =
5017 InsertBRElement(WithTransaction::Yes,
5018 EditorDOMPoint::AtEndOf(aElement));
5019 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
5020 NS_WARNING(
5021 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
5022 return insertBRElementResult.propagateErr();
5024 CreateElementResult unwrappedInsertBRElementResult =
5025 insertBRElementResult.unwrap();
5026 unwrappedInsertBRElementResult.MoveCaretPointTo(
5027 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
5028 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
5033 } else if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling(
5034 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5035 // The case of aNode being empty. We need a br at start unless:
5036 // 1) previous sibling of aNode is a block, OR
5037 // 2) previous sibling of aNode is a br, OR
5038 // 3) following sibling of aNode is a block, OR
5039 // 4) following sibling of aNode is a br OR
5040 // 5) either is null
5041 if (!HTMLEditUtils::IsBlockElement(
5042 *previousSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
5043 !previousSibling->IsHTMLElement(nsGkAtoms::br)) {
5044 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling(
5045 aElement, {WalkTreeOption::IgnoreNonEditableNode})) {
5046 if (!HTMLEditUtils::IsBlockElement(
5047 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle) &&
5048 !nextSibling->IsHTMLElement(nsGkAtoms::br)) {
5049 Result<CreateElementResult, nsresult> insertBRElementResult =
5050 InsertBRElement(WithTransaction::Yes,
5051 EditorDOMPoint(&aElement, 0u));
5052 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
5053 NS_WARNING(
5054 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
5055 return insertBRElementResult.propagateErr();
5057 CreateElementResult unwrappedInsertBRElementResult =
5058 insertBRElementResult.unwrap();
5059 unwrappedInsertBRElementResult.MoveCaretPointTo(
5060 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
5061 MOZ_ASSERT(unwrappedInsertBRElementResult.GetNewNode());
5067 // Now remove container
5068 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult =
5069 RemoveContainerWithTransaction(aElement);
5070 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) {
5071 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
5072 return unwrapBlockElementResult;
5074 if (AllowsTransactionsToChangeSelection() &&
5075 unwrapBlockElementResult.inspect().IsSet()) {
5076 pointToPutCaret = unwrapBlockElementResult.unwrap();
5078 return pointToPutCaret; // May be unset
5081 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeWithTransaction(
5082 const EditorDOMPoint& aStartOfRightNode) {
5083 MOZ_ASSERT(IsEditActionDataAvailable());
5085 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) {
5086 return Err(NS_ERROR_INVALID_ARG);
5088 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
5090 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(
5091 *aStartOfRightNode.ContainerAs<nsIContent>()))) {
5092 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5095 IgnoredErrorResult ignoredError;
5096 AutoEditSubActionNotifier startToHandleEditSubAction(
5097 *this, EditSubAction::eSplitNode, nsIEditor::eNext, ignoredError);
5098 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5099 return Err(NS_ERROR_EDITOR_DESTROYED);
5101 NS_WARNING_ASSERTION(
5102 !ignoredError.Failed(),
5103 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5105 RefPtr<SplitNodeTransaction> transaction =
5106 SplitNodeTransaction::Create(*this, aStartOfRightNode);
5107 nsresult rv = DoTransactionInternal(transaction);
5108 if (NS_WARN_IF(Destroyed())) {
5109 NS_WARNING(
5110 "EditorBase::DoTransactionInternal() caused destroying the editor");
5111 return Err(NS_ERROR_EDITOR_DESTROYED);
5113 if (NS_FAILED(rv)) {
5114 NS_WARNING("EditorBase::DoTransactionInternal() failed");
5115 return Err(rv);
5118 nsIContent* newContent = transaction->GetNewContent();
5119 nsIContent* splitContent = transaction->GetSplitContent();
5120 if (NS_WARN_IF(!newContent) || NS_WARN_IF(!splitContent)) {
5121 return Err(NS_ERROR_FAILURE);
5123 TopLevelEditSubActionDataRef().DidSplitContent(*this, *splitContent,
5124 *newContent);
5125 if (NS_WARN_IF(!newContent->IsInComposedDoc()) ||
5126 NS_WARN_IF(!splitContent->IsInComposedDoc())) {
5127 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5130 return SplitNodeResult(*newContent, *splitContent);
5133 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeDeepWithTransaction(
5134 nsIContent& aMostAncestorToSplit,
5135 const EditorDOMPoint& aDeepestStartOfRightNode,
5136 SplitAtEdges aSplitAtEdges) {
5137 MOZ_ASSERT(aDeepestStartOfRightNode.IsSetAndValidInComposedDoc());
5138 MOZ_ASSERT(
5139 aDeepestStartOfRightNode.GetContainer() == &aMostAncestorToSplit ||
5140 EditorUtils::IsDescendantOf(*aDeepestStartOfRightNode.GetContainer(),
5141 aMostAncestorToSplit));
5143 if (NS_WARN_IF(!aDeepestStartOfRightNode.IsInComposedDoc())) {
5144 return Err(NS_ERROR_INVALID_ARG);
5147 nsCOMPtr<nsIContent> newLeftNodeOfMostAncestor;
5148 EditorDOMPoint atStartOfRightNode(aDeepestStartOfRightNode);
5149 // lastResult is as explained by its name, the last result which may not be
5150 // split a node actually.
5151 SplitNodeResult lastResult = SplitNodeResult::NotHandled(atStartOfRightNode);
5152 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5153 .IsSetAndValidInComposedDoc());
5155 while (true) {
5156 // Need to insert rules code call here to do things like not split a list
5157 // if you are after the last <li> or before the first, etc. For now we
5158 // just have some smarts about unnecessarily splitting text nodes, which
5159 // should be universal enough to put straight in this EditorBase routine.
5160 auto* splittingContent = atStartOfRightNode.GetContainerAs<nsIContent>();
5161 if (NS_WARN_IF(!splittingContent)) {
5162 lastResult.IgnoreCaretPointSuggestion();
5163 return Err(NS_ERROR_FAILURE);
5165 // If we meet an orphan node before meeting aMostAncestorToSplit, we need
5166 // to stop splitting. This is a bug of the caller.
5167 if (NS_WARN_IF(splittingContent != &aMostAncestorToSplit &&
5168 !atStartOfRightNode.GetContainerParentAs<nsIContent>())) {
5169 lastResult.IgnoreCaretPointSuggestion();
5170 return Err(NS_ERROR_FAILURE);
5172 // If the container is not splitable node such as comment node, atomic
5173 // element, etc, we should keep it as-is, and try to split its parents.
5174 if (!HTMLEditUtils::IsSplittableNode(*splittingContent)) {
5175 if (splittingContent == &aMostAncestorToSplit) {
5176 return lastResult;
5178 atStartOfRightNode.Set(splittingContent);
5179 continue;
5182 // If the split point is middle of the node or the node is not a text node
5183 // and we're allowed to create empty element node, split it.
5184 if ((aSplitAtEdges == SplitAtEdges::eAllowToCreateEmptyContainer &&
5185 !atStartOfRightNode.IsInTextNode()) ||
5186 (!atStartOfRightNode.IsStartOfContainer() &&
5187 !atStartOfRightNode.IsEndOfContainer())) {
5188 Result<SplitNodeResult, nsresult> splitNodeResult =
5189 SplitNodeWithTransaction(atStartOfRightNode);
5190 if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
5191 lastResult.IgnoreCaretPointSuggestion();
5192 return splitNodeResult;
5194 lastResult = SplitNodeResult::MergeWithDeeperSplitNodeResult(
5195 splitNodeResult.unwrap(), lastResult);
5196 if (NS_WARN_IF(!lastResult.AtSplitPoint<EditorRawDOMPoint>()
5197 .IsInComposedDoc())) {
5198 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5200 MOZ_ASSERT(lastResult.HasCaretPointSuggestion());
5201 MOZ_ASSERT(lastResult.GetOriginalContent() == splittingContent);
5202 if (splittingContent == &aMostAncestorToSplit) {
5203 // Actually, we split aMostAncestorToSplit.
5204 return lastResult;
5207 // Then, try to split its parent before current node.
5208 atStartOfRightNode = lastResult.AtNextContent<EditorDOMPoint>();
5210 // If the split point is end of the node and it is a text node or we're not
5211 // allowed to create empty container node, try to split its parent after it.
5212 else if (!atStartOfRightNode.IsStartOfContainer()) {
5213 lastResult = SplitNodeResult::HandledButDidNotSplitDueToEndOfContainer(
5214 *splittingContent, &lastResult);
5215 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5216 .IsSetAndValidInComposedDoc());
5217 if (splittingContent == &aMostAncestorToSplit) {
5218 return lastResult;
5221 // Try to split its parent after current node.
5222 atStartOfRightNode.SetAfter(splittingContent);
5224 // If the split point is start of the node and it is a text node or we're
5225 // not allowed to create empty container node, try to split its parent.
5226 else {
5227 if (splittingContent == &aMostAncestorToSplit) {
5228 return SplitNodeResult::HandledButDidNotSplitDueToStartOfContainer(
5229 *splittingContent, &lastResult);
5232 // Try to split its parent before current node.
5233 // XXX This is logically wrong. If we've already split something but
5234 // this is the last splitable content node in the limiter, this
5235 // method will return "not handled".
5236 lastResult = SplitNodeResult::NotHandled(atStartOfRightNode, &lastResult);
5237 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>()
5238 .IsSetAndValidInComposedDoc());
5239 atStartOfRightNode.Set(splittingContent);
5240 MOZ_ASSERT(atStartOfRightNode.IsSetAndValidInComposedDoc());
5244 // Not reached because while (true) loop never breaks.
5247 Result<SplitNodeResult, nsresult> HTMLEditor::DoSplitNode(
5248 const EditorDOMPoint& aStartOfRightNode, nsIContent& aNewNode) {
5249 // Ensure computing the offset if it's initialized with a child content node.
5250 Unused << aStartOfRightNode.Offset();
5252 // XXX Perhaps, aStartOfRightNode may be invalid if this is a redo
5253 // operation after modifying DOM node with JS.
5254 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) {
5255 return Err(NS_ERROR_INVALID_ARG);
5257 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
5259 // Remember all selection points.
5260 AutoTArray<SavedRange, 10> savedRanges;
5261 for (SelectionType selectionType : kPresentSelectionTypes) {
5262 SavedRange savingRange;
5263 savingRange.mSelection = GetSelection(selectionType);
5264 if (NS_WARN_IF(!savingRange.mSelection &&
5265 selectionType == SelectionType::eNormal)) {
5266 return Err(NS_ERROR_FAILURE);
5268 if (!savingRange.mSelection) {
5269 // For non-normal selections, skip over the non-existing ones.
5270 continue;
5273 for (uint32_t j : IntegerRange(savingRange.mSelection->RangeCount())) {
5274 const nsRange* r = savingRange.mSelection->GetRangeAt(j);
5275 MOZ_ASSERT(r);
5276 MOZ_ASSERT(r->IsPositioned());
5277 // XXX Looks like that SavedRange should have mStart and mEnd which
5278 // are RangeBoundary. Then, we can avoid to compute offset here.
5279 savingRange.mStartContainer = r->GetStartContainer();
5280 savingRange.mStartOffset = r->StartOffset();
5281 savingRange.mEndContainer = r->GetEndContainer();
5282 savingRange.mEndOffset = r->EndOffset();
5284 savedRanges.AppendElement(savingRange);
5288 nsCOMPtr<nsINode> parent = aStartOfRightNode.GetContainerParent();
5289 if (NS_WARN_IF(!parent)) {
5290 return Err(NS_ERROR_FAILURE);
5293 // Fix the child before mutation observer may touch the DOM tree.
5294 nsIContent* firstChildOfRightNode = aStartOfRightNode.GetChild();
5295 IgnoredErrorResult error;
5296 parent->InsertBefore(
5297 aNewNode, aStartOfRightNode.GetContainer()->GetNextSibling(), error);
5298 if (MOZ_UNLIKELY(error.Failed())) {
5299 NS_WARNING("nsINode::InsertBefore() failed");
5300 return Err(error.StealNSResult());
5303 MOZ_DIAGNOSTIC_ASSERT_IF(aStartOfRightNode.IsInTextNode(), aNewNode.IsText());
5304 MOZ_DIAGNOSTIC_ASSERT_IF(!aStartOfRightNode.IsInTextNode(),
5305 !aNewNode.IsText());
5307 // If we are splitting a text node, we need to move its some data to the
5308 // new text node.
5309 if (aStartOfRightNode.IsInTextNode()) {
5310 if (!aStartOfRightNode.IsEndOfContainer()) {
5311 Text* originalTextNode = aStartOfRightNode.ContainerAs<Text>();
5312 Text* newTextNode = aNewNode.AsText();
5313 nsAutoString movingText;
5314 const uint32_t cutStartOffset = aStartOfRightNode.Offset();
5315 const uint32_t cutLength =
5316 originalTextNode->Length() - aStartOfRightNode.Offset();
5317 IgnoredErrorResult error;
5318 originalTextNode->SubstringData(cutStartOffset, cutLength, movingText,
5319 error);
5320 NS_WARNING_ASSERTION(!error.Failed(),
5321 "Text::SubstringData() failed, but ignored");
5322 error.SuppressException();
5324 // XXX This call may destroy us.
5325 DoDeleteText(MOZ_KnownLive(*originalTextNode), cutStartOffset, cutLength,
5326 error);
5327 NS_WARNING_ASSERTION(!error.Failed(),
5328 "EditorBase::DoDeleteText() failed, but ignored");
5329 error.SuppressException();
5331 // XXX This call may destroy us.
5332 DoSetText(MOZ_KnownLive(*newTextNode), movingText, error);
5333 NS_WARNING_ASSERTION(!error.Failed(),
5334 "EditorBase::DoSetText() failed, but ignored");
5337 // If the node has been moved to different parent, we should do nothing
5338 // since web apps should handle everything in such case.
5339 else if (firstChildOfRightNode &&
5340 aStartOfRightNode.GetContainer() !=
5341 firstChildOfRightNode->GetParentNode()) {
5342 NS_WARNING(
5343 "The web app interrupted us and touched the DOM tree, we stopped "
5344 "splitting anything");
5345 } else {
5346 // If the right node is new one and there is no children or splitting at
5347 // end of the node, we need to do nothing.
5348 if (!firstChildOfRightNode) {
5349 // Do nothing.
5351 // If the right node is new one and splitting at start of the container,
5352 // we need to move all children to the new right node.
5353 else if (!firstChildOfRightNode->GetPreviousSibling()) {
5354 // XXX Why do we ignore an error while moving nodes from the right
5355 // node to the left node?
5356 nsresult rv = MoveAllChildren(*aStartOfRightNode.GetContainer(),
5357 EditorRawDOMPoint(&aNewNode, 0u));
5358 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5359 return Err(NS_ERROR_EDITOR_DESTROYED);
5361 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5362 "HTMLEditor::MoveAllChildren() failed, but ignored");
5364 // If the right node is new one and splitting at middle of the node, we need
5365 // to move inclusive next siblings of the split point to the new right node.
5366 else {
5367 // XXX Why do we ignore an error while moving nodes from the right node
5368 // to the left node?
5369 nsresult rv = MoveInclusiveNextSiblings(*firstChildOfRightNode,
5370 EditorRawDOMPoint(&aNewNode, 0u));
5371 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
5372 return Err(NS_ERROR_EDITOR_DESTROYED);
5374 NS_WARNING_ASSERTION(
5375 NS_SUCCEEDED(rv),
5376 "HTMLEditor::MoveInclusiveNextSiblings() failed, but ignored");
5380 // Handle selection
5381 // TODO: Stop doing this, this shouldn't be necessary to update selection.
5382 if (RefPtr<PresShell> presShell = GetPresShell()) {
5383 presShell->FlushPendingNotifications(FlushType::Frames);
5385 NS_WARNING_ASSERTION(!Destroyed(),
5386 "The editor is destroyed during splitting a node");
5388 const bool allowedTransactionsToChangeSelection =
5389 AllowsTransactionsToChangeSelection();
5391 RefPtr<Selection> previousSelection;
5392 for (SavedRange& savedRange : savedRanges) {
5393 // If we have not seen the selection yet, clear all of its ranges.
5394 if (savedRange.mSelection != previousSelection) {
5395 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error);
5396 if (MOZ_UNLIKELY(error.Failed())) {
5397 NS_WARNING("Selection::RemoveAllRanges() failed");
5398 return Err(error.StealNSResult());
5400 previousSelection = savedRange.mSelection;
5403 // XXX Looks like that we don't need to modify normal selection here
5404 // because selection will be modified by the caller if
5405 // AllowsTransactionsToChangeSelection() will return true.
5406 if (allowedTransactionsToChangeSelection &&
5407 savedRange.mSelection->Type() == SelectionType::eNormal) {
5408 // If the editor should adjust the selection, don't bother restoring
5409 // the ranges for the normal selection here.
5410 continue;
5413 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5414 uint32_t& aOffset) {
5415 if (aContainer != aStartOfRightNode.GetContainer()) {
5416 return;
5419 // If the container is the left node and offset is after the split
5420 // point, the content was moved from the right node to aNewNode.
5421 // So, we need to change the container to aNewNode and decrease the
5422 // offset.
5423 if (aOffset >= aStartOfRightNode.Offset()) {
5424 aContainer = &aNewNode;
5425 aOffset -= aStartOfRightNode.Offset();
5428 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset);
5429 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset);
5431 RefPtr<nsRange> newRange =
5432 nsRange::Create(savedRange.mStartContainer, savedRange.mStartOffset,
5433 savedRange.mEndContainer, savedRange.mEndOffset, error);
5434 if (MOZ_UNLIKELY(error.Failed())) {
5435 NS_WARNING("nsRange::Create() failed");
5436 return Err(error.StealNSResult());
5438 // The `MOZ_KnownLive` annotation is only necessary because of a bug
5439 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
5440 // static analyzer.
5441 MOZ_KnownLive(savedRange.mSelection)
5442 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
5443 if (MOZ_UNLIKELY(error.Failed())) {
5444 NS_WARNING(
5445 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
5446 return Err(error.StealNSResult());
5450 // We don't need to set selection here because the caller should do that
5451 // in any case.
5453 // If splitting the node causes running mutation event listener and we've
5454 // got unexpected result, we should return error because callers will
5455 // continue to do their work without complicated DOM tree result.
5456 // NOTE: Perhaps, we shouldn't do this immediately after each DOM tree change
5457 // because stopping handling it causes some data loss. E.g., user
5458 // may loose the text which is moved to the new text node.
5459 // XXX We cannot check all descendants in the right node and the new left
5460 // node for performance reason. I think that if caller needs to access
5461 // some of the descendants, they should check by themselves.
5462 if (NS_WARN_IF(parent != aStartOfRightNode.GetContainer()->GetParentNode()) ||
5463 NS_WARN_IF(parent != aNewNode.GetParentNode()) ||
5464 NS_WARN_IF(aNewNode.GetPreviousSibling() !=
5465 aStartOfRightNode.GetContainer())) {
5466 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5469 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjSplitNode(
5470 *aStartOfRightNode.ContainerAs<nsIContent>(), aStartOfRightNode.Offset(),
5471 aNewNode);
5472 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5473 "RangeUpdater::SelAdjSplitNode() failed, but ignored");
5475 return SplitNodeResult(aNewNode,
5476 *aStartOfRightNode.ContainerAs<nsIContent>());
5479 Result<JoinNodesResult, nsresult> HTMLEditor::JoinNodesWithTransaction(
5480 nsIContent& aLeftContent, nsIContent& aRightContent) {
5481 MOZ_ASSERT(IsEditActionDataAvailable());
5482 MOZ_ASSERT(&aLeftContent != &aRightContent);
5483 MOZ_ASSERT(aLeftContent.GetParentNode());
5484 MOZ_ASSERT(aRightContent.GetParentNode());
5485 MOZ_ASSERT(aLeftContent.GetParentNode() == aRightContent.GetParentNode());
5487 IgnoredErrorResult ignoredError;
5488 AutoEditSubActionNotifier startToHandleEditSubAction(
5489 *this, EditSubAction::eJoinNodes, nsIEditor::ePrevious, ignoredError);
5490 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5491 return Err(ignoredError.StealNSResult());
5493 NS_WARNING_ASSERTION(
5494 !ignoredError.Failed(),
5495 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5497 if (NS_WARN_IF(!aRightContent.GetParentNode())) {
5498 return Err(NS_ERROR_FAILURE);
5501 RefPtr<JoinNodesTransaction> transaction =
5502 JoinNodesTransaction::MaybeCreate(*this, aLeftContent, aRightContent);
5503 if (MOZ_UNLIKELY(!transaction)) {
5504 NS_WARNING("JoinNodesTransaction::MaybeCreate() failed");
5505 return Err(NS_ERROR_FAILURE);
5508 const nsresult rv = DoTransactionInternal(transaction);
5509 // FYI: Now, DidJoinNodesTransaction() must have been run if succeeded.
5510 if (NS_WARN_IF(Destroyed())) {
5511 return Err(NS_ERROR_EDITOR_DESTROYED);
5514 // This shouldn't occur unless the cycle collector runs by chrome script
5515 // forcibly.
5516 if (NS_WARN_IF(!transaction->GetRemovedContent()) ||
5517 NS_WARN_IF(!transaction->GetExistingContent())) {
5518 return Err(NS_ERROR_UNEXPECTED);
5521 // If joined node is moved to different place, offset may not have any
5522 // meaning. In this case, the web app modified the DOM tree takes on the
5523 // responsibility for the remaning things.
5524 if (NS_WARN_IF(transaction->GetExistingContent()->GetParent() !=
5525 transaction->GetParentNode())) {
5526 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
5529 if (NS_FAILED(rv)) {
5530 NS_WARNING("EditorBase::DoTransactionInternal() failed");
5531 return Err(rv);
5534 return JoinNodesResult(transaction->CreateJoinedPoint<EditorDOMPoint>(),
5535 *transaction->GetRemovedContent());
5538 void HTMLEditor::DidJoinNodesTransaction(
5539 const JoinNodesTransaction& aTransaction, nsresult aDoJoinNodesResult) {
5540 // This shouldn't occur unless the cycle collector runs by chrome script
5541 // forcibly.
5542 if (MOZ_UNLIKELY(NS_WARN_IF(!aTransaction.GetRemovedContent()) ||
5543 NS_WARN_IF(!aTransaction.GetExistingContent()))) {
5544 return;
5547 // If joined node is moved to different place, offset may not have any
5548 // meaning. In this case, the web app modified the DOM tree takes on the
5549 // responsibility for the remaning things.
5550 if (MOZ_UNLIKELY(aTransaction.GetExistingContent()->GetParentNode() !=
5551 aTransaction.GetParentNode())) {
5552 return;
5555 // Be aware, the joined point should be created for each call because
5556 // they may refer the child node, but some of them may change the DOM tree
5557 // after that, thus we need to avoid invalid point (Although it shouldn't
5558 // occur).
5559 TopLevelEditSubActionDataRef().DidJoinContents(
5560 *this, aTransaction.CreateJoinedPoint<EditorRawDOMPoint>());
5562 if (NS_SUCCEEDED(aDoJoinNodesResult)) {
5563 if (RefPtr<TextServicesDocument> textServicesDocument =
5564 mTextServicesDocument) {
5565 textServicesDocument->DidJoinContents(
5566 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(),
5567 *aTransaction.GetRemovedContent());
5571 if (!mActionListeners.IsEmpty()) {
5572 for (auto& listener : mActionListeners.Clone()) {
5573 DebugOnly<nsresult> rvIgnored = listener->DidJoinContents(
5574 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(),
5575 aTransaction.GetRemovedContent());
5576 NS_WARNING_ASSERTION(
5577 NS_SUCCEEDED(rvIgnored),
5578 "nsIEditActionListener::DidJoinContents() failed, but ignored");
5583 nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
5584 nsIContent& aContentToRemove) {
5585 MOZ_ASSERT(IsEditActionDataAvailable());
5587 const uint32_t keepingContentLength = aContentToKeep.Length();
5588 const EditorDOMPoint oldPointAtRightContent(&aContentToRemove);
5589 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) {
5590 Unused << oldPointAtRightContent.Offset(); // Fix the offset
5593 // Remember all selection points.
5594 // XXX Do we need to restore all types of selections by ourselves? Normal
5595 // selection should be modified later as result of handling edit action.
5596 // IME selections shouldn't be there when nodes are joined. Spellcheck
5597 // selections should be recreated with newer text. URL selections
5598 // shouldn't be there because of used only by the URL bar.
5599 AutoTArray<SavedRange, 10> savedRanges;
5601 EditorRawDOMPoint atRemovingNode(&aContentToRemove);
5602 EditorRawDOMPoint atNodeToKeep(&aContentToKeep);
5603 for (SelectionType selectionType : kPresentSelectionTypes) {
5604 SavedRange savingRange;
5605 savingRange.mSelection = GetSelection(selectionType);
5606 if (selectionType == SelectionType::eNormal) {
5607 if (NS_WARN_IF(!savingRange.mSelection)) {
5608 return NS_ERROR_FAILURE;
5610 } else if (!savingRange.mSelection) {
5611 // For non-normal selections, skip over the non-existing ones.
5612 continue;
5615 const uint32_t rangeCount = savingRange.mSelection->RangeCount();
5616 for (const uint32_t j : IntegerRange(rangeCount)) {
5617 MOZ_ASSERT(savingRange.mSelection->RangeCount() == rangeCount);
5618 const RefPtr<nsRange> r = savingRange.mSelection->GetRangeAt(j);
5619 MOZ_ASSERT(r);
5620 MOZ_ASSERT(r->IsPositioned());
5621 savingRange.mStartContainer = r->GetStartContainer();
5622 savingRange.mStartOffset = r->StartOffset();
5623 savingRange.mEndContainer = r->GetEndContainer();
5624 savingRange.mEndOffset = r->EndOffset();
5626 // If selection endpoint is between the nodes, remember it as being
5627 // in the one that is going away instead. This simplifies later
5628 // selection adjustment logic at end of this method.
5629 if (savingRange.mStartContainer) {
5630 MOZ_ASSERT(savingRange.mEndContainer);
5631 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5632 uint32_t& aOffset) {
5633 // If range boundary points aContentToRemove and aContentToKeep is
5634 // its left node, remember it as being at end of aContentToKeep.
5635 // Then, it will point start of the first content of moved content
5636 // from aContentToRemove.
5637 if (aContainer == atRemovingNode.GetContainer() &&
5638 atNodeToKeep.Offset() < aOffset &&
5639 aOffset <= atRemovingNode.Offset()) {
5640 aContainer = &aContentToKeep;
5641 aOffset = keepingContentLength;
5644 AdjustDOMPoint(savingRange.mStartContainer, savingRange.mStartOffset);
5645 AdjustDOMPoint(savingRange.mEndContainer, savingRange.mEndOffset);
5648 savedRanges.AppendElement(savingRange);
5653 // OK, ready to do join now.
5654 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT {
5655 // If it's a text node, just shuffle around some text.
5656 if (aContentToKeep.IsText() && aContentToRemove.IsText()) {
5657 nsAutoString rightText;
5658 nsAutoString leftText;
5659 aContentToRemove.AsText()->GetData(rightText);
5660 aContentToKeep.AsText()->GetData(leftText);
5661 leftText += rightText;
5662 IgnoredErrorResult ignoredError;
5663 DoSetText(MOZ_KnownLive(*aContentToKeep.AsText()), leftText,
5664 ignoredError);
5665 if (NS_WARN_IF(Destroyed())) {
5666 return NS_ERROR_EDITOR_DESTROYED;
5668 NS_WARNING_ASSERTION(!ignoredError.Failed(),
5669 "EditorBase::DoSetText() failed, but ignored");
5670 return NS_OK;
5672 // Otherwise it's an interior node, so shuffle around the children.
5673 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfChildContents;
5674 HTMLEditUtils::CollectAllChildren(aContentToRemove, arrayOfChildContents);
5676 for (const OwningNonNull<nsIContent>& child : arrayOfChildContents) {
5677 IgnoredErrorResult error;
5678 aContentToKeep.AppendChild(child, error);
5679 if (NS_WARN_IF(Destroyed())) {
5680 return NS_ERROR_EDITOR_DESTROYED;
5682 if (error.Failed()) {
5683 NS_WARNING("nsINode::AppendChild() failed");
5684 return error.StealNSResult();
5687 return NS_OK;
5688 }();
5690 // Delete the extra node.
5691 if (NS_SUCCEEDED(rv)) {
5692 aContentToRemove.Remove();
5693 if (NS_WARN_IF(Destroyed())) {
5694 return NS_ERROR_EDITOR_DESTROYED;
5698 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) {
5699 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjJoinNodes(
5700 EditorRawDOMPoint(&aContentToKeep, std::min(keepingContentLength,
5701 aContentToKeep.Length())),
5702 aContentToRemove, oldPointAtRightContent);
5703 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
5704 "RangeUpdater::SelAdjJoinNodes() failed, but ignored");
5706 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
5707 return rv;
5710 const bool allowedTransactionsToChangeSelection =
5711 AllowsTransactionsToChangeSelection();
5713 // And adjust the selection if needed.
5714 RefPtr<Selection> previousSelection;
5715 for (SavedRange& savedRange : savedRanges) {
5716 // If we have not seen the selection yet, clear all of its ranges.
5717 if (savedRange.mSelection != previousSelection) {
5718 IgnoredErrorResult error;
5719 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error);
5720 if (NS_WARN_IF(Destroyed())) {
5721 return NS_ERROR_EDITOR_DESTROYED;
5723 if (error.Failed()) {
5724 NS_WARNING("Selection::RemoveAllRanges() failed");
5725 return error.StealNSResult();
5727 previousSelection = savedRange.mSelection;
5730 if (allowedTransactionsToChangeSelection &&
5731 savedRange.mSelection->Type() == SelectionType::eNormal) {
5732 // If the editor should adjust the selection, don't bother restoring
5733 // the ranges for the normal selection here.
5734 continue;
5737 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer,
5738 uint32_t& aOffset) {
5739 // Now, all content of aContentToRemove are moved to end of
5740 // aContentToKeep. Therefore, if a range boundary was in
5741 // aContentToRemove, we need to change the container to aContentToKeep and
5742 // adjust the offset to after the original content of aContentToKeep.
5743 if (aContainer == &aContentToRemove) {
5744 aContainer = &aContentToKeep;
5745 aOffset += keepingContentLength;
5748 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset);
5749 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset);
5751 const RefPtr<nsRange> newRange = nsRange::Create(
5752 savedRange.mStartContainer, savedRange.mStartOffset,
5753 savedRange.mEndContainer, savedRange.mEndOffset, IgnoreErrors());
5754 if (!newRange) {
5755 NS_WARNING("nsRange::Create() failed");
5756 return NS_ERROR_FAILURE;
5759 IgnoredErrorResult error;
5760 // The `MOZ_KnownLive` annotation is only necessary because of a bug
5761 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
5762 // static analyzer.
5763 MOZ_KnownLive(savedRange.mSelection)
5764 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
5765 if (NS_WARN_IF(Destroyed())) {
5766 return NS_ERROR_EDITOR_DESTROYED;
5768 if (NS_WARN_IF(error.Failed())) {
5769 return error.StealNSResult();
5773 if (allowedTransactionsToChangeSelection) {
5774 // Editor wants us to set selection at join point.
5775 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(aContentToKeep);
5776 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
5777 NS_WARNING(
5778 "EditorBase::CollapseSelectionTo() caused destroying the editor");
5779 return NS_ERROR_EDITOR_DESTROYED;
5781 NS_WARNING_ASSERTION(
5782 NS_SUCCEEDED(rv),
5783 "EditorBases::CollapseSelectionTos() failed, but ignored");
5786 return NS_OK;
5789 Result<MoveNodeResult, nsresult> HTMLEditor::MoveNodeWithTransaction(
5790 nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert) {
5791 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
5793 EditorDOMPoint oldPoint(&aContentToMove);
5794 if (NS_WARN_IF(!oldPoint.IsSet())) {
5795 return Err(NS_ERROR_FAILURE);
5798 // Don't do anything if it's already in right place.
5799 if (aPointToInsert == oldPoint) {
5800 return MoveNodeResult::IgnoredResult(aPointToInsert.NextPoint());
5803 RefPtr<MoveNodeTransaction> moveNodeTransaction =
5804 MoveNodeTransaction::MaybeCreate(*this, aContentToMove, aPointToInsert);
5805 if (MOZ_UNLIKELY(!moveNodeTransaction)) {
5806 NS_WARNING("MoveNodeTransaction::MaybeCreate() failed");
5807 return Err(NS_ERROR_FAILURE);
5810 IgnoredErrorResult ignoredError;
5811 AutoEditSubActionNotifier startToHandleEditSubAction(
5812 *this, EditSubAction::eMoveNode, nsIEditor::eNext, ignoredError);
5813 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5814 return Err(ignoredError.StealNSResult());
5816 NS_WARNING_ASSERTION(
5817 !ignoredError.Failed(),
5818 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
5820 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContentToMove);
5822 nsresult rv = DoTransactionInternal(moveNodeTransaction);
5823 if (NS_SUCCEEDED(rv)) {
5824 if (mTextServicesDocument) {
5825 const OwningNonNull<TextServicesDocument> textServicesDocument =
5826 *mTextServicesDocument;
5827 textServicesDocument->DidDeleteContent(aContentToMove);
5831 if (!mActionListeners.IsEmpty()) {
5832 for (auto& listener : mActionListeners.Clone()) {
5833 DebugOnly<nsresult> rvIgnored =
5834 listener->DidDeleteNode(&aContentToMove, rv);
5835 NS_WARNING_ASSERTION(
5836 NS_SUCCEEDED(rvIgnored),
5837 "nsIEditActionListener::DidDeleteNode() failed, but ignored");
5841 if (MOZ_UNLIKELY(Destroyed())) {
5842 NS_WARNING(
5843 "MoveNodeTransaction::DoTransaction() caused destroying the editor");
5844 return Err(NS_ERROR_EDITOR_DESTROYED);
5847 if (NS_FAILED(rv)) {
5848 NS_WARNING("MoveNodeTransaction::DoTransaction() failed");
5849 return Err(rv);
5852 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToMove);
5854 return MoveNodeResult::HandledResult(
5855 moveNodeTransaction->SuggestNextInsertionPoint<EditorDOMPoint>(),
5856 moveNodeTransaction->SuggestPointToPutCaret<EditorDOMPoint>());
5859 Result<RefPtr<Element>, nsresult> HTMLEditor::DeleteSelectionAndCreateElement(
5860 nsAtom& aTag, const InitializeInsertingElement& aInitializer) {
5861 MOZ_ASSERT(IsEditActionDataAvailable());
5863 nsresult rv = DeleteSelectionAndPrepareToCreateNode();
5864 if (NS_FAILED(rv)) {
5865 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
5866 return Err(rv);
5869 EditorDOMPoint pointToInsert(SelectionRef().AnchorRef());
5870 if (!pointToInsert.IsSet()) {
5871 return Err(NS_ERROR_FAILURE);
5873 Result<CreateElementResult, nsresult> createNewElementResult =
5874 CreateAndInsertElement(WithTransaction::Yes, aTag, pointToInsert,
5875 aInitializer);
5876 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
5877 NS_WARNING(
5878 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
5879 return createNewElementResult.propagateErr();
5881 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode());
5883 // We want the selection to be just after the new node
5884 createNewElementResult.inspect().IgnoreCaretPointSuggestion();
5885 rv = CollapseSelectionTo(
5886 EditorRawDOMPoint::After(*createNewElementResult.inspect().GetNewNode()));
5887 if (NS_FAILED(rv)) {
5888 NS_WARNING("EditorBase::CollapseSelectionTo() failed");
5889 return Err(rv);
5891 return createNewElementResult.unwrap().UnwrapNewNode();
5894 nsresult HTMLEditor::DeleteSelectionAndPrepareToCreateNode() {
5895 MOZ_ASSERT(IsEditActionDataAvailable());
5897 if (NS_WARN_IF(!SelectionRef().GetAnchorFocusRange())) {
5898 return NS_OK;
5901 if (!SelectionRef().GetAnchorFocusRange()->Collapsed()) {
5902 nsresult rv =
5903 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
5904 if (NS_FAILED(rv)) {
5905 NS_WARNING("EditorBase::DeleteSelectionAsSubAction() failed");
5906 return rv;
5908 MOZ_ASSERT(SelectionRef().GetAnchorFocusRange() &&
5909 SelectionRef().GetAnchorFocusRange()->Collapsed(),
5910 "Selection not collapsed after delete");
5913 // If the selection is a chardata node, split it if necessary and compute
5914 // where to put the new node
5915 EditorDOMPoint atAnchor(SelectionRef().AnchorRef());
5916 if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
5917 return NS_OK;
5920 if (NS_WARN_IF(!atAnchor.GetContainerParent())) {
5921 return NS_ERROR_FAILURE;
5924 if (atAnchor.IsStartOfContainer()) {
5925 const EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
5926 if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
5927 return NS_ERROR_FAILURE;
5929 nsresult rv = CollapseSelectionTo(atAnchorContainer);
5930 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5931 "EditorBase::CollapseSelectionTo() failed");
5932 return rv;
5935 if (atAnchor.IsEndOfContainer()) {
5936 EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
5937 if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
5938 return NS_ERROR_FAILURE;
5940 nsresult rv = CollapseSelectionTo(afterAnchorContainer);
5941 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5942 "EditorBase::CollapseSelectionTo() failed");
5943 return rv;
5946 Result<SplitNodeResult, nsresult> splitAtAnchorResult =
5947 SplitNodeWithTransaction(atAnchor);
5948 if (MOZ_UNLIKELY(splitAtAnchorResult.isErr())) {
5949 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
5950 return splitAtAnchorResult.unwrapErr();
5953 splitAtAnchorResult.inspect().IgnoreCaretPointSuggestion();
5954 const auto atRightContent =
5955 splitAtAnchorResult.inspect().AtNextContent<EditorRawDOMPoint>();
5956 if (NS_WARN_IF(!atRightContent.IsSet())) {
5957 return NS_ERROR_FAILURE;
5959 MOZ_ASSERT(atRightContent.IsSetAndValid());
5960 nsresult rv = CollapseSelectionTo(atRightContent);
5961 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5962 "EditorBase::CollapseSelectionTo() failed");
5963 return rv;
5966 bool HTMLEditor::IsEmpty() const {
5967 if (mPaddingBRElementForEmptyEditor) {
5968 return true;
5971 const Element* activeElement =
5972 GetDocument() ? GetDocument()->GetActiveElement() : nullptr;
5973 const Element* editingHostOrBodyOrRootElement =
5974 activeElement && activeElement->IsEditable()
5975 ? ComputeEditingHost(*activeElement, LimitInBodyElement::No)
5976 : ComputeEditingHost(LimitInBodyElement::No);
5977 if (MOZ_UNLIKELY(!editingHostOrBodyOrRootElement)) {
5978 // If there is no active element nor no selection range in the document,
5979 // let's check entire the document as what we do traditionally.
5980 editingHostOrBodyOrRootElement = GetRoot();
5981 if (!editingHostOrBodyOrRootElement) {
5982 return true;
5986 for (nsIContent* childContent =
5987 editingHostOrBodyOrRootElement->GetFirstChild();
5988 childContent; childContent = childContent->GetNextSibling()) {
5989 if (!childContent->IsText() || childContent->Length()) {
5990 return false;
5993 return true;
5996 // add to aElement the CSS inline styles corresponding to the HTML attribute
5997 // aAttribute with its value aValue
5998 nsresult HTMLEditor::SetAttributeOrEquivalent(Element* aElement,
5999 nsAtom* aAttribute,
6000 const nsAString& aValue,
6001 bool aSuppressTransaction) {
6002 MOZ_ASSERT(aElement);
6003 MOZ_ASSERT(aAttribute);
6005 nsAutoScriptBlocker scriptBlocker;
6006 nsStyledElement* styledElement = nsStyledElement::FromNodeOrNull(aElement);
6007 if (!IsCSSEnabled()) {
6008 // we are not in an HTML+CSS editor; let's set the attribute the HTML way
6009 if (EditorElementStyle::IsHTMLStyle(aAttribute)) {
6010 const EditorElementStyle elementStyle =
6011 EditorElementStyle::Create(*aAttribute);
6012 if (styledElement && elementStyle.IsCSSRemovable(*styledElement)) {
6013 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6014 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6015 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle(
6016 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6017 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr);
6018 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6019 return NS_ERROR_EDITOR_DESTROYED;
6021 NS_WARNING_ASSERTION(
6022 NS_SUCCEEDED(rv),
6023 "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored");
6026 if (aSuppressTransaction) {
6027 nsresult rv =
6028 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
6029 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
6030 return rv;
6032 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
6033 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6034 "EditorBase::SetAttributeWithTransaction() failed");
6035 return rv;
6038 if (EditorElementStyle::IsHTMLStyle(aAttribute)) {
6039 const EditorElementStyle elementStyle =
6040 EditorElementStyle::Create(*aAttribute);
6041 if (styledElement && elementStyle.IsCSSSettable(*styledElement)) {
6042 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6043 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6044 Result<size_t, nsresult> count = CSSEditUtils::SetCSSEquivalentToStyle(
6045 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6046 *this, MOZ_KnownLive(*styledElement), elementStyle, &aValue);
6047 if (MOZ_UNLIKELY(count.isErr())) {
6048 if (NS_WARN_IF(count.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6049 return NS_ERROR_EDITOR_DESTROYED;
6051 NS_WARNING(
6052 "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
6054 if (count.inspect()) {
6055 // we found an equivalence ; let's remove the HTML attribute itself if
6056 // it is set
6057 nsAutoString existingValue;
6058 if (!aElement->GetAttr(aAttribute, existingValue)) {
6059 return NS_OK;
6062 if (aSuppressTransaction) {
6063 nsresult rv =
6064 aElement->UnsetAttr(kNameSpaceID_None, aAttribute, true);
6065 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
6066 return rv;
6068 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
6069 NS_WARNING_ASSERTION(
6070 NS_SUCCEEDED(rv),
6071 "EditorBase::RemoveAttributeWithTransaction() failed");
6072 return rv;
6077 // count is an integer that represents the number of CSS declarations
6078 // applied to the element. If it is zero, we found no equivalence in this
6079 // implementation for the attribute
6080 if (aAttribute == nsGkAtoms::style) {
6081 // if it is the style attribute, just add the new value to the existing
6082 // style attribute's value
6083 nsString existingValue; // Use nsString to avoid copying the string
6084 // buffer at setting the attribute below.
6085 aElement->GetAttr(nsGkAtoms::style, existingValue);
6086 if (!existingValue.IsEmpty()) {
6087 existingValue.Append(HTMLEditUtils::kSpace);
6089 existingValue.Append(aValue);
6090 if (aSuppressTransaction) {
6091 nsresult rv = aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
6092 existingValue, true);
6093 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6094 "Element::SetAttr(nsGkAtoms::style) failed");
6095 return rv;
6097 nsresult rv = SetAttributeWithTransaction(*aElement, *nsGkAtoms::style,
6098 existingValue);
6099 NS_WARNING_ASSERTION(
6100 NS_SUCCEEDED(rv),
6101 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::style) failed");
6102 return rv;
6105 // we have no CSS equivalence for this attribute and it is not the style
6106 // attribute; let's set it the good'n'old HTML way
6107 if (aSuppressTransaction) {
6108 nsresult rv =
6109 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
6110 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
6111 return rv;
6113 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
6114 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6115 "EditorBase::SetAttributeWithTransaction() failed");
6116 return rv;
6119 nsresult HTMLEditor::RemoveAttributeOrEquivalent(Element* aElement,
6120 nsAtom* aAttribute,
6121 bool aSuppressTransaction) {
6122 MOZ_ASSERT(aElement);
6123 MOZ_ASSERT(aAttribute);
6125 if (IsCSSEnabled() && EditorElementStyle::IsHTMLStyle(aAttribute)) {
6126 const EditorElementStyle elementStyle =
6127 EditorElementStyle::Create(*aAttribute);
6128 if (elementStyle.IsCSSRemovable(*aElement)) {
6129 // XXX It might be keep handling attribute even if aElement is not
6130 // an nsStyledElement instance.
6131 nsStyledElement* styledElement =
6132 nsStyledElement::FromNodeOrNull(aElement);
6133 if (NS_WARN_IF(!styledElement)) {
6134 return NS_ERROR_INVALID_ARG;
6136 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
6137 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
6138 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle(
6139 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes,
6140 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr);
6141 if (NS_FAILED(rv)) {
6142 NS_WARNING("CSSEditUtils::RemoveCSSEquivalentToStyle() failed");
6143 return rv;
6148 if (!aElement->HasAttr(aAttribute)) {
6149 return NS_OK;
6152 if (aSuppressTransaction) {
6153 nsresult rv = aElement->UnsetAttr(kNameSpaceID_None, aAttribute,
6154 /* aNotify = */ true);
6155 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
6156 return rv;
6158 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
6159 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6160 "EditorBase::RemoveAttributeWithTransaction() failed");
6161 return rv;
6164 NS_IMETHODIMP HTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) {
6165 AutoEditActionDataSetter editActionData(*this,
6166 EditAction::eEnableOrDisableCSS);
6167 if (NS_WARN_IF(!editActionData.CanHandle())) {
6168 return NS_ERROR_NOT_INITIALIZED;
6171 mIsCSSPrefChecked = aIsCSSPrefChecked;
6172 return NS_OK;
6175 // Set the block background color
6176 nsresult HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction(
6177 const nsAString& aColor) {
6178 MOZ_ASSERT(IsEditActionDataAvailable());
6180 // background-color change and committing composition should be undone
6181 // together
6182 AutoPlaceholderBatch treatAsOneTransaction(
6183 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
6185 CommitComposition();
6187 // XXX Shouldn't we do this before calling `CommitComposition()`?
6188 if (IsPlaintextMailComposer()) {
6189 return NS_OK;
6193 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction();
6194 if (MOZ_UNLIKELY(result.isErr())) {
6195 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
6196 return result.unwrapErr();
6198 if (result.inspect().Canceled()) {
6199 return NS_OK;
6203 IgnoredErrorResult ignoredError;
6204 AutoEditSubActionNotifier startToHandleEditSubAction(
6205 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
6206 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
6207 return ignoredError.StealNSResult();
6209 NS_WARNING_ASSERTION(!ignoredError.Failed(),
6210 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
6211 "failed, but ignored");
6213 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
6214 // cases, but removing this may cause the behavior with the legacy
6215 // mutation event listeners. We should try to delete this in a bug.
6216 AutoTransactionsConserveSelection dontChangeMySelection(*this);
6218 AutoRangeArray selectionRanges(SelectionRef());
6219 MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this));
6220 for (const OwningNonNull<nsRange>& domRange : selectionRanges.Ranges()) {
6221 EditorDOMRange range(domRange);
6222 if (NS_WARN_IF(!range.IsPositioned())) {
6223 continue;
6226 if (range.InSameContainer()) {
6227 // If the range is in a text node, set background color of its parent
6228 // block.
6229 if (range.StartRef().IsInTextNode()) {
6230 const RefPtr<nsStyledElement> editableBlockStyledElement =
6231 nsStyledElement::FromNodeOrNull(HTMLEditUtils::GetAncestorElement(
6232 *range.StartRef().ContainerAs<Text>(),
6233 HTMLEditUtils::ClosestEditableBlockElement,
6234 BlockInlineCheck::UseComputedDisplayOutsideStyle));
6235 if (!editableBlockStyledElement ||
6236 !EditorElementStyle::BGColor().IsCSSSettable(
6237 *editableBlockStyledElement)) {
6238 continue;
6240 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6241 WithTransaction::Yes, *this, *editableBlockStyledElement,
6242 EditorElementStyle::BGColor(), &aColor);
6243 if (MOZ_UNLIKELY(result.isErr())) {
6244 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6245 return NS_ERROR_EDITOR_DESTROYED;
6247 NS_WARNING(
6248 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6249 "BGColor()) failed, but ignored");
6251 continue;
6254 // If `Selection` is collapsed in a `<body>` element, set background
6255 // color of the `<body>` element.
6256 if (range.Collapsed() &&
6257 range.StartRef().IsContainerHTMLElement(nsGkAtoms::body)) {
6258 const RefPtr<nsStyledElement> styledElement =
6259 range.StartRef().GetContainerAs<nsStyledElement>();
6260 if (!styledElement ||
6261 !EditorElementStyle::BGColor().IsCSSSettable(*styledElement)) {
6262 continue;
6264 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6265 WithTransaction::Yes, *this, *styledElement,
6266 EditorElementStyle::BGColor(), &aColor);
6267 if (MOZ_UNLIKELY(result.isErr())) {
6268 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6269 return NS_ERROR_EDITOR_DESTROYED;
6271 NS_WARNING(
6272 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6273 "BGColor()) failed, but ignored");
6275 continue;
6278 // If one node is selected, set background color of it if it's a
6279 // block, or of its parent block otherwise.
6280 if ((range.StartRef().IsStartOfContainer() &&
6281 range.EndRef().IsStartOfContainer()) ||
6282 range.StartRef().Offset() + 1 == range.EndRef().Offset()) {
6283 if (NS_WARN_IF(range.StartRef().IsInDataNode())) {
6284 continue;
6286 const RefPtr<nsStyledElement> editableBlockStyledElement =
6287 nsStyledElement::FromNodeOrNull(
6288 HTMLEditUtils::GetInclusiveAncestorElement(
6289 *range.StartRef().GetChild(),
6290 HTMLEditUtils::ClosestEditableBlockElement,
6291 BlockInlineCheck::UseComputedDisplayOutsideStyle));
6292 if (!editableBlockStyledElement ||
6293 !EditorElementStyle::BGColor().IsCSSSettable(
6294 *editableBlockStyledElement)) {
6295 continue;
6297 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
6298 WithTransaction::Yes, *this, *editableBlockStyledElement,
6299 EditorElementStyle::BGColor(), &aColor);
6300 if (MOZ_UNLIKELY(result.isErr())) {
6301 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6302 return NS_ERROR_EDITOR_DESTROYED;
6304 NS_WARNING(
6305 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6306 "BGColor()) failed, but ignored");
6308 continue;
6310 } // if (range.InSameContainer())
6312 // Collect editable nodes which are entirely contained in the range.
6313 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
6315 ContentSubtreeIterator subtreeIter;
6316 // If there is no node which is entirely in the range,
6317 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
6318 // don't warn it.
6319 nsresult rv = subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
6320 range.EndRef().ToRawRangeBoundary());
6321 NS_WARNING_ASSERTION(
6322 NS_SUCCEEDED(rv),
6323 "ContentSubtreeIterator::Init() failed, but ignored");
6324 if (NS_SUCCEEDED(rv)) {
6325 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
6326 nsINode* node = subtreeIter.GetCurrentNode();
6327 if (NS_WARN_IF(!node)) {
6328 return NS_ERROR_FAILURE;
6330 if (node->IsContent() && EditorUtils::IsEditableContent(
6331 *node->AsContent(), EditorType::HTML)) {
6332 arrayOfContents.AppendElement(*node->AsContent());
6338 // This caches block parent if we set its background color.
6339 RefPtr<Element> handledBlockParent;
6341 // If start node is a text node, set background color of its parent
6342 // block.
6343 if (range.StartRef().IsInTextNode() &&
6344 EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
6345 EditorType::HTML)) {
6346 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement(
6347 *range.StartRef().ContainerAs<Text>(),
6348 HTMLEditUtils::ClosestEditableBlockElement,
6349 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6350 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6351 handledBlockParent = editableBlockElement;
6352 nsStyledElement* const blockStyledElement =
6353 nsStyledElement::FromNode(handledBlockParent);
6354 if (blockStyledElement &&
6355 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6356 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent
6357 // whose type is RefPtr.
6358 Result<size_t, nsresult> result =
6359 CSSEditUtils::SetCSSEquivalentToStyle(
6360 WithTransaction::Yes, *this,
6361 MOZ_KnownLive(*blockStyledElement),
6362 EditorElementStyle::BGColor(), &aColor);
6363 if (MOZ_UNLIKELY(result.isErr())) {
6364 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6365 return NS_ERROR_EDITOR_DESTROYED;
6367 NS_WARNING(
6368 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6369 "BGColor()) failed, but ignored");
6375 // Then, set background color of each block or block parent of all nodes
6376 // in the range entirely.
6377 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
6378 Element* const editableBlockElement =
6379 HTMLEditUtils::GetInclusiveAncestorElement(
6380 content, HTMLEditUtils::ClosestEditableBlockElement,
6381 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6382 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6383 handledBlockParent = editableBlockElement;
6384 nsStyledElement* const blockStyledElement =
6385 nsStyledElement::FromNode(handledBlockParent);
6386 if (blockStyledElement &&
6387 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6388 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent whose
6389 // type is RefPtr.
6390 Result<size_t, nsresult> result =
6391 CSSEditUtils::SetCSSEquivalentToStyle(
6392 WithTransaction::Yes, *this,
6393 MOZ_KnownLive(*blockStyledElement),
6394 EditorElementStyle::BGColor(), &aColor);
6395 if (MOZ_UNLIKELY(result.isErr())) {
6396 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6397 return NS_ERROR_EDITOR_DESTROYED;
6399 NS_WARNING(
6400 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6401 "BGColor()) failed, but ignored");
6407 // Finally, if end node is a text node, set background color of its
6408 // parent block.
6409 if (range.EndRef().IsInTextNode() &&
6410 EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
6411 EditorType::HTML)) {
6412 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement(
6413 *range.EndRef().ContainerAs<Text>(),
6414 HTMLEditUtils::ClosestEditableBlockElement,
6415 BlockInlineCheck::UseComputedDisplayOutsideStyle);
6416 if (editableBlockElement && handledBlockParent != editableBlockElement) {
6417 const RefPtr<nsStyledElement> blockStyledElement =
6418 nsStyledElement::FromNode(editableBlockElement);
6419 if (blockStyledElement &&
6420 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) {
6421 Result<size_t, nsresult> result =
6422 CSSEditUtils::SetCSSEquivalentToStyle(
6423 WithTransaction::Yes, *this, *blockStyledElement,
6424 EditorElementStyle::BGColor(), &aColor);
6425 if (MOZ_UNLIKELY(result.isErr())) {
6426 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
6427 return NS_ERROR_EDITOR_DESTROYED;
6429 NS_WARNING(
6430 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::"
6431 "BGColor()) failed, but ignored");
6436 } // for-loop of selectionRanges
6438 MOZ_ASSERT(selectionRanges.HasSavedRanges());
6439 selectionRanges.RestoreFromSavedRanges();
6440 nsresult rv = selectionRanges.ApplyTo(SelectionRef());
6441 if (NS_WARN_IF(Destroyed())) {
6442 return NS_ERROR_EDITOR_DESTROYED;
6444 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::ApplyTo() failed");
6445 return rv;
6448 NS_IMETHODIMP HTMLEditor::SetBackgroundColor(const nsAString& aColor) {
6449 nsresult rv = SetBackgroundColorAsAction(aColor);
6450 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6451 "HTMLEditor::SetBackgroundColorAsAction() failed");
6452 return rv;
6455 nsresult HTMLEditor::SetBackgroundColorAsAction(const nsAString& aColor,
6456 nsIPrincipal* aPrincipal) {
6457 AutoEditActionDataSetter editActionData(
6458 *this, EditAction::eSetBackgroundColor, aPrincipal);
6459 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
6460 if (NS_FAILED(rv)) {
6461 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
6462 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
6463 return EditorBase::ToGenericNSResult(rv);
6466 if (IsCSSEnabled()) {
6467 // if we are in CSS mode, we have to apply the background color to the
6468 // containing block (or the body if we have no block-level element in
6469 // the document)
6470 nsresult rv = SetBlockBackgroundColorWithCSSAsSubAction(aColor);
6471 NS_WARNING_ASSERTION(
6472 NS_SUCCEEDED(rv),
6473 "HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction() failed");
6474 return EditorBase::ToGenericNSResult(rv);
6477 // but in HTML mode, we can only set the document's background color
6478 rv = SetHTMLBackgroundColorWithTransaction(aColor);
6479 NS_WARNING_ASSERTION(
6480 NS_SUCCEEDED(rv),
6481 "HTMLEditor::SetHTMLBackgroundColorWithTransaction() failed");
6482 return EditorBase::ToGenericNSResult(rv);
6485 Result<EditorDOMPoint, nsresult>
6486 HTMLEditor::CopyLastEditableChildStylesWithTransaction(
6487 Element& aPreviousBlock, Element& aNewBlock, const Element& aEditingHost) {
6488 MOZ_ASSERT(IsEditActionDataAvailable());
6490 // First, clear out aNewBlock. Contract is that we want only the styles
6491 // from aPreviousBlock.
6492 AutoTArray<OwningNonNull<nsIContent>, 32> newBlockChildren;
6493 HTMLEditUtils::CollectAllChildren(aNewBlock, newBlockChildren);
6494 for (const OwningNonNull<nsIContent>& child : newBlockChildren) {
6495 // MOZ_KNownLive(child) because of bug 1622253
6496 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(child));
6497 if (NS_FAILED(rv)) {
6498 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
6499 return Err(rv);
6502 if (MOZ_UNLIKELY(aNewBlock.GetFirstChild())) {
6503 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
6506 // XXX aNewBlock may be moved or removed. Even in such case, we should
6507 // keep cloning the styles?
6509 // Look for the deepest last editable leaf node in aPreviousBlock.
6510 // Then, if found one is a <br> element, look for non-<br> element.
6511 nsIContent* deepestEditableContent = nullptr;
6512 for (nsCOMPtr<nsIContent> child = &aPreviousBlock; child;
6513 child = HTMLEditUtils::GetLastChild(
6514 *child, {WalkTreeOption::IgnoreNonEditableNode})) {
6515 deepestEditableContent = child;
6517 while (deepestEditableContent &&
6518 deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) {
6519 deepestEditableContent = HTMLEditUtils::GetPreviousContent(
6520 *deepestEditableContent, {WalkTreeOption::IgnoreNonEditableNode},
6521 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost);
6523 if (!deepestEditableContent) {
6524 return EditorDOMPoint(&aNewBlock, 0u);
6527 Element* deepestVisibleEditableElement =
6528 deepestEditableContent->GetAsElementOrParentElement();
6529 if (!deepestVisibleEditableElement) {
6530 return EditorDOMPoint(&aNewBlock, 0u);
6533 // Clone inline elements to keep current style in the new block.
6534 // XXX Looks like that this is really slow if lastEditableDescendant is
6535 // far from aPreviousBlock. Probably, we should clone inline containers
6536 // from ancestor to descendants without transactions, then, insert it
6537 // after that with transaction.
6538 RefPtr<Element> lastClonedElement, firstClonedElement;
6539 for (RefPtr<Element> elementInPreviousBlock = deepestVisibleEditableElement;
6540 elementInPreviousBlock && elementInPreviousBlock != &aPreviousBlock;
6541 elementInPreviousBlock = elementInPreviousBlock->GetParentElement()) {
6542 if (!HTMLEditUtils::IsInlineStyle(elementInPreviousBlock) &&
6543 !elementInPreviousBlock->IsHTMLElement(nsGkAtoms::span)) {
6544 continue;
6546 OwningNonNull<nsAtom> tagName =
6547 *elementInPreviousBlock->NodeInfo()->NameAtom();
6548 // At first time, just create the most descendant inline container
6549 // element.
6550 if (!firstClonedElement) {
6551 Result<CreateElementResult, nsresult> createNewElementResult =
6552 CreateAndInsertElement(
6553 WithTransaction::Yes, tagName, EditorDOMPoint(&aNewBlock, 0u),
6554 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
6555 [&elementInPreviousBlock](
6556 HTMLEditor& aHTMLEditor, Element& aNewElement,
6557 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
6558 // Clone all attributes. Note that despite the method name,
6559 // CloneAttributesWithTransaction does not create
6560 // transactions in this case because aNewElement has not
6561 // been connected yet.
6562 // XXX Looks like that this clones id attribute too.
6563 aHTMLEditor.CloneAttributesWithTransaction(
6564 aNewElement, *elementInPreviousBlock);
6565 return NS_OK;
6567 if (MOZ_UNLIKELY(createNewElementResult.isErr())) {
6568 NS_WARNING(
6569 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed");
6570 return createNewElementResult.propagateErr();
6572 CreateElementResult unwrappedCreateNewElementResult =
6573 createNewElementResult.unwrap();
6574 // We'll return with a point suggesting new caret position and the
6575 // following path does not require an update of selection here.
6576 // Therefore, we don't need to update selection here.
6577 unwrappedCreateNewElementResult.IgnoreCaretPointSuggestion();
6578 firstClonedElement = lastClonedElement =
6579 unwrappedCreateNewElementResult.UnwrapNewNode();
6580 continue;
6582 // Otherwise, inserts new parent inline container to the previous inserted
6583 // inline container.
6584 Result<CreateElementResult, nsresult> wrapClonedElementResult =
6585 InsertContainerWithTransaction(*lastClonedElement, tagName);
6586 if (MOZ_UNLIKELY(wrapClonedElementResult.isErr())) {
6587 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
6588 return wrapClonedElementResult.propagateErr();
6590 CreateElementResult unwrappedWrapClonedElementResult =
6591 wrapClonedElementResult.unwrap();
6592 // We'll return with a point suggesting new caret so that we don't need to
6593 // update selection here.
6594 unwrappedWrapClonedElementResult.IgnoreCaretPointSuggestion();
6595 MOZ_ASSERT(unwrappedWrapClonedElementResult.GetNewNode());
6596 lastClonedElement = unwrappedWrapClonedElementResult.UnwrapNewNode();
6597 CloneAttributesWithTransaction(*lastClonedElement, *elementInPreviousBlock);
6598 if (NS_WARN_IF(Destroyed())) {
6599 return Err(NS_ERROR_EDITOR_DESTROYED);
6603 if (!firstClonedElement) {
6604 // XXX Even if no inline elements are cloned, shouldn't we create new
6605 // <br> element for aNewBlock?
6606 return EditorDOMPoint(&aNewBlock, 0u);
6609 Result<CreateElementResult, nsresult> insertBRElementResult = InsertBRElement(
6610 WithTransaction::Yes, EditorDOMPoint(firstClonedElement, 0u));
6611 if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
6612 NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
6613 return insertBRElementResult.propagateErr();
6615 insertBRElementResult.inspect().IgnoreCaretPointSuggestion();
6616 MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode());
6617 return EditorDOMPoint(insertBRElementResult.inspect().GetNewNode());
6620 nsresult HTMLEditor::GetElementOrigin(Element& aElement, int32_t& aX,
6621 int32_t& aY) {
6622 aX = 0;
6623 aY = 0;
6625 if (NS_WARN_IF(!IsInitialized())) {
6626 return NS_ERROR_NOT_INITIALIZED;
6628 PresShell* presShell = GetPresShell();
6629 if (NS_WARN_IF(!presShell)) {
6630 return NS_ERROR_NOT_INITIALIZED;
6633 nsIFrame* frame = aElement.GetPrimaryFrame();
6634 if (NS_WARN_IF(!frame)) {
6635 return NS_OK;
6638 nsIFrame* absoluteContainerBlockFrame =
6639 presShell->GetAbsoluteContainingBlock(frame);
6640 if (NS_WARN_IF(!absoluteContainerBlockFrame)) {
6641 return NS_OK;
6643 nsPoint off = frame->GetOffsetTo(absoluteContainerBlockFrame);
6644 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x);
6645 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y);
6647 return NS_OK;
6650 Element* HTMLEditor::GetSelectionContainerElement() const {
6651 MOZ_ASSERT(IsEditActionDataAvailable());
6653 nsINode* focusNode = nullptr;
6654 if (SelectionRef().IsCollapsed()) {
6655 focusNode = SelectionRef().GetFocusNode();
6656 if (NS_WARN_IF(!focusNode)) {
6657 return nullptr;
6659 } else {
6660 const uint32_t rangeCount = SelectionRef().RangeCount();
6661 MOZ_ASSERT(rangeCount, "If 0, Selection::IsCollapsed() should return true");
6663 if (rangeCount == 1) {
6664 const nsRange* range = SelectionRef().GetRangeAt(0);
6666 const RangeBoundary& startRef = range->StartRef();
6667 const RangeBoundary& endRef = range->EndRef();
6669 // This method called GetSelectedElement() to retrieve proper container
6670 // when only one node is selected. However, it simply returns start
6671 // node of Selection with additional cost. So, we do not need to call
6672 // it anymore.
6673 if (startRef.Container()->IsElement() &&
6674 startRef.Container() == endRef.Container() &&
6675 startRef.GetChildAtOffset() &&
6676 startRef.GetChildAtOffset()->GetNextSibling() ==
6677 endRef.GetChildAtOffset()) {
6678 focusNode = startRef.GetChildAtOffset();
6679 MOZ_ASSERT(focusNode, "Start container must not be nullptr");
6680 } else {
6681 focusNode = range->GetClosestCommonInclusiveAncestor();
6682 if (!focusNode) {
6683 NS_WARNING(
6684 "AbstractRange::GetClosestCommonInclusiveAncestor() returned "
6685 "nullptr");
6686 return nullptr;
6689 } else {
6690 for (const uint32_t i : IntegerRange(rangeCount)) {
6691 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount);
6692 const nsRange* range = SelectionRef().GetRangeAt(i);
6693 MOZ_ASSERT(range);
6694 nsINode* startContainer = range->GetStartContainer();
6695 if (!focusNode) {
6696 focusNode = startContainer;
6697 } else if (focusNode != startContainer) {
6698 // XXX Looks odd to use parent of startContainer because previous
6699 // range may not be in the parent node of current
6700 // startContainer.
6701 focusNode = startContainer->GetParentNode();
6702 // XXX Looks odd to break the for-loop here because we refer only
6703 // first range and another range which starts from different
6704 // container, and the latter range is preferred. Why?
6705 break;
6708 if (!focusNode) {
6709 NS_WARNING("Focused node of selection was not found");
6710 return nullptr;
6715 if (focusNode->IsText()) {
6716 focusNode = focusNode->GetParentNode();
6717 if (NS_WARN_IF(!focusNode)) {
6718 return nullptr;
6722 if (NS_WARN_IF(!focusNode->IsElement())) {
6723 return nullptr;
6725 return focusNode->AsElement();
6728 NS_IMETHODIMP HTMLEditor::IsAnonymousElement(Element* aElement, bool* aReturn) {
6729 if (NS_WARN_IF(!aElement)) {
6730 return NS_ERROR_INVALID_ARG;
6732 *aReturn = aElement->IsRootOfNativeAnonymousSubtree();
6733 return NS_OK;
6736 nsresult HTMLEditor::SetReturnInParagraphCreatesNewParagraph(
6737 bool aCreatesNewParagraph) {
6738 mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
6739 return NS_OK;
6742 bool HTMLEditor::GetReturnInParagraphCreatesNewParagraph() const {
6743 return mCRInParagraphCreatesParagraph;
6746 nsresult HTMLEditor::GetReturnInParagraphCreatesNewParagraph(
6747 bool* aCreatesNewParagraph) {
6748 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
6749 return NS_OK;
6752 NS_IMETHODIMP HTMLEditor::GetWrapWidth(int32_t* aWrapColumn) {
6753 if (NS_WARN_IF(!aWrapColumn)) {
6754 return NS_ERROR_INVALID_ARG;
6756 *aWrapColumn = WrapWidth();
6757 return NS_OK;
6761 // See if the style value includes this attribute, and if it does,
6762 // cut out everything from the attribute to the next semicolon.
6764 static void CutStyle(const char* stylename, nsString& styleValue) {
6765 // Find the current wrapping type:
6766 int32_t styleStart = styleValue.LowerCaseFindASCII(stylename);
6767 if (styleStart >= 0) {
6768 int32_t styleEnd = styleValue.Find(u";", styleStart);
6769 if (styleEnd > styleStart) {
6770 styleValue.Cut(styleStart, styleEnd - styleStart + 1);
6771 } else {
6772 styleValue.Cut(styleStart, styleValue.Length() - styleStart);
6777 NS_IMETHODIMP HTMLEditor::SetWrapWidth(int32_t aWrapColumn) {
6778 AutoEditActionDataSetter editActionData(*this, EditAction::eSetWrapWidth);
6779 if (NS_WARN_IF(!editActionData.CanHandle())) {
6780 return NS_ERROR_NOT_INITIALIZED;
6783 mWrapColumn = aWrapColumn;
6785 // Make sure we're a plaintext editor, otherwise we shouldn't
6786 // do the rest of this.
6787 if (!IsPlaintextMailComposer()) {
6788 return NS_OK;
6791 // Ought to set a style sheet here...
6792 RefPtr<Element> rootElement = GetRoot();
6793 if (NS_WARN_IF(!rootElement)) {
6794 return NS_ERROR_NOT_INITIALIZED;
6797 // Get the current style for this root element:
6798 nsAutoString styleValue;
6799 rootElement->GetAttr(nsGkAtoms::style, styleValue);
6801 // We'll replace styles for these values:
6802 CutStyle("white-space", styleValue);
6803 CutStyle("width", styleValue);
6804 CutStyle("font-family", styleValue);
6806 // If we have other style left, trim off any existing semicolons
6807 // or white-space, then add a known semicolon-space:
6808 if (!styleValue.IsEmpty()) {
6809 styleValue.Trim("; \t", false, true);
6810 styleValue.AppendLiteral("; ");
6813 // Make sure we have fixed-width font. This should be done for us,
6814 // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
6815 // Only do this if we're wrapping.
6816 if (IsWrapHackEnabled() && aWrapColumn >= 0) {
6817 styleValue.AppendLiteral("font-family: -moz-fixed; ");
6820 // and now we're ready to set the new white-space/wrapping style.
6821 if (aWrapColumn > 0) {
6822 // Wrap to a fixed column.
6823 styleValue.AppendLiteral("white-space: pre-wrap; width: ");
6824 styleValue.AppendInt(aWrapColumn);
6825 styleValue.AppendLiteral("ch;");
6826 } else if (!aWrapColumn) {
6827 styleValue.AppendLiteral("white-space: pre-wrap;");
6828 } else {
6829 styleValue.AppendLiteral("white-space: pre;");
6832 nsresult rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
6833 styleValue, true);
6834 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6835 "Element::SetAttr(nsGkAtoms::style) failed");
6836 return rv;
6839 Element* HTMLEditor::GetFocusedElement() const {
6840 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6841 if (NS_WARN_IF(!focusManager)) {
6842 return nullptr;
6845 Element* const focusedElement = focusManager->GetFocusedElement();
6847 Document* document = GetDocument();
6848 if (NS_WARN_IF(!document)) {
6849 return nullptr;
6851 const bool inDesignMode = IsInDesignMode();
6852 if (!focusedElement) {
6853 // in designMode, nobody gets focus in most cases.
6854 if (inDesignMode && OurWindowHasFocus()) {
6855 return document->GetRootElement();
6857 return nullptr;
6860 if (inDesignMode) {
6861 return OurWindowHasFocus() &&
6862 focusedElement->IsInclusiveDescendantOf(document)
6863 ? focusedElement
6864 : nullptr;
6867 // We're HTML editor for contenteditable
6869 // If the focused content isn't editable, or it has independent selection,
6870 // we don't have focus.
6871 if (!focusedElement->HasFlag(NODE_IS_EDITABLE) ||
6872 focusedElement->HasIndependentSelection()) {
6873 return nullptr;
6875 // If our window is focused, we're focused.
6876 return OurWindowHasFocus() ? focusedElement : nullptr;
6879 bool HTMLEditor::IsActiveInDOMWindow() const {
6880 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6881 if (NS_WARN_IF(!focusManager)) {
6882 return false;
6885 Document* document = GetDocument();
6886 if (NS_WARN_IF(!document)) {
6887 return false;
6889 const bool inDesignMode = IsInDesignMode();
6891 // If we're in designMode, we're always active in the DOM window.
6892 if (inDesignMode) {
6893 return true;
6896 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
6897 nsCOMPtr<nsPIDOMWindowOuter> win;
6898 nsIContent* content = nsFocusManager::GetFocusedDescendant(
6899 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win));
6900 if (!content) {
6901 return false;
6904 // We're HTML editor for contenteditable
6906 // If the active content isn't editable, or it has independent selection,
6907 // we're not active).
6908 if (!content->HasFlag(NODE_IS_EDITABLE) ||
6909 content->HasIndependentSelection()) {
6910 return false;
6912 return true;
6915 Element* HTMLEditor::ComputeEditingHostInternal(
6916 const nsIContent* aContent, LimitInBodyElement aLimitInBodyElement) const {
6917 Document* document = GetDocument();
6918 if (NS_WARN_IF(!document)) {
6919 return nullptr;
6922 auto MaybeLimitInBodyElement =
6923 [&](const Element* aCandidiateEditingHost) -> Element* {
6924 if (!aCandidiateEditingHost) {
6925 return nullptr;
6927 if (aLimitInBodyElement != LimitInBodyElement::Yes) {
6928 return const_cast<Element*>(aCandidiateEditingHost);
6930 // By default, we should limit editing host to the <body> element for
6931 // avoiding deleting or creating unexpected elements outside the <body>.
6932 // However, this is incompatible with Chrome so that we should stop
6933 // doing this with adding safety checks more.
6934 if (document->GetBodyElement() &&
6935 nsContentUtils::ContentIsFlattenedTreeDescendantOf(
6936 aCandidiateEditingHost, document->GetBodyElement())) {
6937 return const_cast<Element*>(aCandidiateEditingHost);
6939 // XXX If aContent is an editing host and has no parent node, we reach here,
6940 // but returing the <body> which is not connected to aContent is odd.
6941 return document->GetBodyElement();
6944 if (IsInDesignMode()) {
6945 // TODO: In this case, we need to compute editing host from aContent or the
6946 // focus node of selection, and it may be in an editing host in a
6947 // shadow DOM tree etc. We need to do more complicated things.
6948 // See also InDesignMode().
6949 return document->GetBodyElement();
6952 // We're HTML editor for contenteditable
6953 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6954 if (NS_WARN_IF(!editActionData.CanHandle())) {
6955 return nullptr;
6958 const nsIContent* const content =
6959 aContent ? aContent
6960 : nsIContent::FromNodeOrNull(SelectionRef().GetFocusNode());
6961 if (NS_WARN_IF(!content)) {
6962 return nullptr;
6965 // If the active content isn't editable, we're not active.
6966 if (!content->HasFlag(NODE_IS_EDITABLE)) {
6967 return nullptr;
6970 // Although the content shouldn't be in a native anonymous subtree, but
6971 // perhaps due to a bug of Selection or Range API, it may occur. HTMLEditor
6972 // shouldn't touch native anonymous subtree so that return nullptr in such
6973 // case.
6974 if (MOZ_UNLIKELY(content->IsInNativeAnonymousSubtree())) {
6975 return nullptr;
6978 // Note that `Selection` can be in <input> or <textarea>. In the case, we
6979 // need to look for an ancestor which does not have editable parent.
6980 return MaybeLimitInBodyElement(
6981 const_cast<nsIContent*>(content)->GetEditingHost());
6984 void HTMLEditor::NotifyEditingHostMaybeChanged() {
6985 // Note that even if the document is in design mode, a contenteditable element
6986 // in a shadow tree is focusable. Therefore, we may need to update editing
6987 // host even when the document is in design mode.
6988 if (MOZ_UNLIKELY(NS_WARN_IF(!GetDocument()))) {
6989 return;
6992 // We're HTML editor for contenteditable
6993 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6994 if (NS_WARN_IF(!editActionData.CanHandle())) {
6995 return;
6998 // Get selection ancestor limit which may be old editing host.
6999 nsIContent* ancestorLimiter = SelectionRef().GetAncestorLimiter();
7000 if (!ancestorLimiter) {
7001 // If we've not initialized selection ancestor limit, we should wait focus
7002 // event to set proper limiter.
7003 return;
7006 // Compute current editing host.
7007 nsIContent* editingHost = ComputeEditingHost();
7008 if (NS_WARN_IF(!editingHost)) {
7009 return;
7012 // Update selection ancestor limit if current editing host includes the
7013 // previous editing host.
7014 // Additionally, the editing host may be an element in shadow DOM and the
7015 // shadow host is in designMode. In this case, we need to set the editing
7016 // host as the new selection limiter.
7017 if (ancestorLimiter->IsInclusiveDescendantOf(editingHost) ||
7018 (ancestorLimiter->IsInDesignMode() != editingHost->IsInDesignMode())) {
7019 // Note that don't call HTMLEditor::InitializeSelectionAncestorLimit()
7020 // here because it may collapse selection to the first editable node.
7021 EditorBase::InitializeSelectionAncestorLimit(*editingHost);
7025 EventTarget* HTMLEditor::GetDOMEventTarget() const {
7026 // Don't use getDocument here, because we have no way of knowing
7027 // whether Init() was ever called. So we need to get the document
7028 // ourselves, if it exists.
7029 Document* doc = GetDocument();
7030 MOZ_ASSERT(doc, "The HTMLEditor has not been initialized yet");
7031 if (!doc) {
7032 return nullptr;
7035 // Register the EditorEventListener to the parent of window.
7037 // The advantage of this approach is HTMLEditor can still
7038 // receive events when shadow dom is involved.
7039 if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
7040 return win->GetParentTarget();
7042 return nullptr;
7045 bool HTMLEditor::ShouldReplaceRootElement() const {
7046 if (!mRootElement) {
7047 // If we don't know what is our root element, we should find our root.
7048 return true;
7051 // If we temporary set document root element to mRootElement, but there is
7052 // body element now, we should replace the root element by the body element.
7053 return mRootElement != GetBodyElement();
7056 void HTMLEditor::NotifyRootChanged() {
7057 MOZ_ASSERT(mPendingRootElementUpdatedRunner,
7058 "HTMLEditor::NotifyRootChanged() should be called via a runner");
7059 mPendingRootElementUpdatedRunner = nullptr;
7061 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
7063 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
7064 if (NS_WARN_IF(!editActionData.CanHandle())) {
7065 return;
7068 RemoveEventListeners();
7069 nsresult rv = InstallEventListeners();
7070 if (NS_FAILED(rv)) {
7071 NS_WARNING("HTMLEditor::InstallEventListeners() failed, but ignored");
7072 return;
7075 UpdateRootElement();
7076 if (!mRootElement) {
7077 return;
7080 rv = MaybeCollapseSelectionAtFirstEditableNode(false);
7081 if (NS_FAILED(rv)) {
7082 NS_WARNING(
7083 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) "
7084 "failed, "
7085 "but ignored");
7086 return;
7089 // When this editor has focus, we need to reset the selection limiter to
7090 // new root. Otherwise, that is going to be done when this gets focus.
7091 nsCOMPtr<nsINode> node = GetFocusedNode();
7092 if (node) {
7093 DebugOnly<nsresult> rvIgnored = InitializeSelection(*node);
7094 NS_WARNING_ASSERTION(
7095 NS_SUCCEEDED(rvIgnored),
7096 "EditorBase::InitializeSelection() failed, but ignored");
7099 SyncRealTimeSpell();
7102 Element* HTMLEditor::GetBodyElement() const {
7103 Document* document = GetDocument();
7104 MOZ_ASSERT(document, "The HTMLEditor hasn't been initialized yet");
7105 if (NS_WARN_IF(!document)) {
7106 return nullptr;
7108 return document->GetBody();
7111 nsINode* HTMLEditor::GetFocusedNode() const {
7112 Element* focusedElement = GetFocusedElement();
7113 if (!focusedElement) {
7114 return nullptr;
7117 // focusedElement might be non-null even focusManager->GetFocusedElement()
7118 // is null. That's the designMode case, and in that case our
7119 // FocusedContent() returns the root element, but we want to return
7120 // the document.
7122 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
7123 NS_ASSERTION(focusManager, "Focus manager is null");
7124 if ((focusedElement = focusManager->GetFocusedElement())) {
7125 return focusedElement;
7128 return GetDocument();
7131 bool HTMLEditor::OurWindowHasFocus() const {
7132 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
7133 if (NS_WARN_IF(!focusManager)) {
7134 return false;
7136 nsPIDOMWindowOuter* focusedWindow = focusManager->GetFocusedWindow();
7137 if (!focusedWindow) {
7138 return false;
7140 Document* document = GetDocument();
7141 if (NS_WARN_IF(!document)) {
7142 return false;
7144 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
7145 return ourWindow == focusedWindow;
7148 bool HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const {
7149 if (!EditorBase::IsAcceptableInputEvent(aGUIEvent)) {
7150 return false;
7153 // While there is composition, all composition events in its top level
7154 // window are always fired on the composing editor. Therefore, if this
7155 // editor has composition, the composition events should be handled in this
7156 // editor.
7157 if (mComposition && aGUIEvent->AsCompositionEvent()) {
7158 return true;
7161 nsCOMPtr<nsINode> eventTargetNode =
7162 nsINode::FromEventTargetOrNull(aGUIEvent->GetOriginalDOMEventTarget());
7163 if (NS_WARN_IF(!eventTargetNode)) {
7164 return false;
7167 if (eventTargetNode->IsContent()) {
7168 eventTargetNode =
7169 eventTargetNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
7170 if (NS_WARN_IF(!eventTargetNode)) {
7171 return false;
7175 RefPtr<Document> document = GetDocument();
7176 if (NS_WARN_IF(!document)) {
7177 return false;
7180 if (IsInDesignMode()) {
7181 // If this editor is in designMode and the event target is the document,
7182 // the event is for this editor.
7183 if (eventTargetNode->IsDocument()) {
7184 return eventTargetNode == document;
7186 // Otherwise, check whether the event target is in this document or not.
7187 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
7188 return false;
7190 if (document == eventTargetNode->GetUncomposedDoc()) {
7191 return true;
7193 // If the event target is in a shadow tree, the content is not editable
7194 // by default, but if the focused content is an editing host, we need to
7195 // handle it as contenteditable mode.
7196 if (!eventTargetNode->IsInShadowTree()) {
7197 return false;
7201 // Space event for <button> and <summary> with contenteditable
7202 // should be handle by the themselves.
7203 if (aGUIEvent->mMessage == eKeyPress &&
7204 aGUIEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) {
7205 nsGenericHTMLElement* element =
7206 HTMLButtonElement::FromNode(eventTargetNode);
7207 if (!element) {
7208 element = HTMLSummaryElement::FromNode(eventTargetNode);
7211 if (element && element->IsContentEditable()) {
7212 return false;
7215 // This HTML editor is for contenteditable. We need to check the validity
7216 // of the target.
7217 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
7218 return false;
7221 // If the event is a mouse event, we need to check if the target content is
7222 // the focused editing host or its descendant.
7223 if (aGUIEvent->AsMouseEventBase()) {
7224 nsIContent* editingHost = ComputeEditingHost();
7225 // If there is no active editing host, we cannot handle the mouse event
7226 // correctly.
7227 if (!editingHost) {
7228 return false;
7230 // If clicked on non-editable root element but the body element is the
7231 // active editing host, we should assume that the click event is
7232 // targetted.
7233 if (eventTargetNode == document->GetRootElement() &&
7234 !eventTargetNode->HasFlag(NODE_IS_EDITABLE) &&
7235 editingHost == document->GetBodyElement()) {
7236 eventTargetNode = editingHost;
7238 // If the target element is neither the active editing host nor a
7239 // descendant of it, we may not be able to handle the event.
7240 if (!eventTargetNode->IsInclusiveDescendantOf(editingHost)) {
7241 return false;
7243 // If the clicked element has an independent selection, we shouldn't
7244 // handle this click event.
7245 if (eventTargetNode->AsContent()->HasIndependentSelection()) {
7246 return false;
7248 // If the target content is editable, we should handle this event.
7249 return eventTargetNode->HasFlag(NODE_IS_EDITABLE);
7252 // If the target of the other events which target focused element isn't
7253 // editable or has an independent selection, this editor shouldn't handle
7254 // the event.
7255 if (!eventTargetNode->HasFlag(NODE_IS_EDITABLE) ||
7256 eventTargetNode->AsContent()->HasIndependentSelection()) {
7257 return false;
7260 // Finally, check whether we're actually focused or not. When we're not
7261 // focused, we should ignore the dispatched event by script (or something)
7262 // because content editable element needs selection in itself for editing.
7263 // However, when we're not focused, it's not guaranteed.
7264 return IsActiveInDOMWindow();
7267 nsresult HTMLEditor::GetPreferredIMEState(IMEState* aState) {
7268 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
7269 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
7270 if (IsReadonly()) {
7271 aState->mEnabled = IMEEnabled::Disabled;
7272 } else {
7273 aState->mEnabled = IMEEnabled::Enabled;
7275 return NS_OK;
7278 already_AddRefed<Element> HTMLEditor::GetInputEventTargetElement() const {
7279 RefPtr<Element> target = ComputeEditingHost(LimitInBodyElement::No);
7280 if (target) {
7281 return target.forget();
7284 // When there is no active editing host due to focus node is a
7285 // non-editable node, we should look for its editable parent to
7286 // dispatch `beforeinput` event.
7287 nsIContent* focusContent =
7288 nsIContent::FromNodeOrNull(SelectionRef().GetFocusNode());
7289 if (!focusContent || focusContent->IsEditable()) {
7290 return nullptr;
7292 for (Element* element : focusContent->AncestorsOfType<Element>()) {
7293 if (element->IsEditable()) {
7294 target = element->GetEditingHost();
7295 return target.forget();
7298 return nullptr;
7301 nsresult HTMLEditor::OnModifyDocument() {
7302 MOZ_ASSERT(mPendingDocumentModifiedRunner,
7303 "HTMLEditor::OnModifyDocument() should be called via a runner");
7304 mPendingDocumentModifiedRunner = nullptr;
7306 if (IsEditActionDataAvailable()) {
7307 return OnModifyDocumentInternal();
7310 AutoEditActionDataSetter editActionData(
7311 *this, EditAction::eCreatePaddingBRElementForEmptyEditor);
7312 if (NS_WARN_IF(!editActionData.CanHandle())) {
7313 return NS_ERROR_NOT_AVAILABLE;
7316 nsresult rv = OnModifyDocumentInternal();
7317 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7318 "HTMLEditor::OnModifyDocumentInternal() failed");
7319 return rv;
7322 nsresult HTMLEditor::OnModifyDocumentInternal() {
7323 MOZ_ASSERT(IsEditActionDataAvailable());
7324 MOZ_ASSERT(!mPendingDocumentModifiedRunner);
7326 // EnsureNoPaddingBRElementForEmptyEditor() below may cause a flush, which
7327 // could destroy the editor
7328 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
7330 // Delete our padding <br> element for empty editor, if we have one, since
7331 // the document might not be empty any more.
7332 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
7333 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7334 return rv;
7336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
7337 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
7338 "failed, but ignored");
7340 // Try to recreate the padding <br> element for empty editor if needed.
7341 rv = MaybeCreatePaddingBRElementForEmptyEditor();
7342 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
7343 return NS_ERROR_EDITOR_DESTROYED;
7345 NS_WARNING_ASSERTION(
7346 NS_SUCCEEDED(rv),
7347 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
7349 return rv;
7352 } // namespace mozilla