summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/HTMLEditSubActionHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/HTMLEditSubActionHandler.cpp')
-rw-r--r--editor/libeditor/HTMLEditSubActionHandler.cpp9603
1 files changed, 9603 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp
new file mode 100644
index 0000000000..f6463e87c2
--- /dev/null
+++ b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -0,0 +1,9603 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLEditor.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HTMLEditUtils.h"
+#include "WSRunObject.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CSSEditUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/EditAction.h"
+#include "mozilla/EditorDOMPoint.h"
+#include "mozilla/EditorUtils.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
+#include "mozilla/TextComposition.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBRElement.h"
+#include "mozilla/dom/RangeBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/StaticRange.h"
+#include "mozilla/mozalloc.h"
+#include "nsAString.h"
+#include "nsAlgorithm.h"
+#include "nsAtom.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsFrameSelection.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLDocument.h"
+#include "nsIContent.h"
+#include "nsID.h"
+#include "nsIFrame.h"
+#include "nsINode.h"
+#include "nsLiteralString.h"
+#include "nsRange.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsStyledElement.h"
+#include "nsTArray.h"
+#include "nsTextNode.h"
+#include "nsThreadUtils.h"
+#include "nsUnicharUtils.h"
+
+// Workaround for windows headers
+#ifdef SetProp
+# undef SetProp
+#endif
+
+class nsISupports;
+
+namespace mozilla {
+
+using namespace dom;
+using ChildBlockBoundary = HTMLEditUtils::ChildBlockBoundary;
+using StyleDifference = HTMLEditUtils::StyleDifference;
+
+/********************************************************
+ * first some helpful functors we will use
+ ********************************************************/
+
+static bool IsStyleCachePreservingSubAction(EditSubAction aEditSubAction) {
+ switch (aEditSubAction) {
+ case EditSubAction::eDeleteSelectedContent:
+ case EditSubAction::eInsertLineBreak:
+ case EditSubAction::eInsertParagraphSeparator:
+ case EditSubAction::eCreateOrChangeList:
+ case EditSubAction::eIndent:
+ case EditSubAction::eOutdent:
+ case EditSubAction::eSetOrClearAlignment:
+ case EditSubAction::eCreateOrRemoveBlock:
+ case EditSubAction::eMergeBlockContents:
+ case EditSubAction::eRemoveList:
+ case EditSubAction::eCreateOrChangeDefinitionListItem:
+ case EditSubAction::eInsertElement:
+ case EditSubAction::eInsertQuotation:
+ case EditSubAction::eInsertQuotedText:
+ return true;
+ default:
+ return false;
+ }
+}
+
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+ RangeBoundary& aStartRef, RangeBoundary& aEndRef) const;
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+ RawRangeBoundary& aStartRef, RangeBoundary& aEndRef) const;
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+ RangeBoundary& aStartRef, RawRangeBoundary& aEndRef) const;
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+ RawRangeBoundary& aStartRef, RawRangeBoundary& aEndRef) const;
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const RangeBoundary& aStartRef, const RangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const RangeBoundary& aStartRef, const RangeBoundary& aEndRef,
+ EditSubAction aEditSubAction) const;
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef,
+ EditSubAction aEditSubAction) const;
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
+ EditSubAction aEditSubAction) const;
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
+ EditSubAction aEditSubAction) const;
+template EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+ const RangeBoundary& aPoint, ScanDirection aScanDirection);
+template EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+ const RawRangeBoundary& aPoint, ScanDirection aScanDirection);
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+ const RangeBoundary& aPoint, EditSubAction aEditSubAction) const;
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+ const RawRangeBoundary& aPoint, EditSubAction aEditSubAction) const;
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+ const RangeBoundary& aPoint) const;
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+ const RawRangeBoundary& aPoint) const;
+template nsIContent* HTMLEditor::FindNearEditableContent(
+ const EditorDOMPoint& aPoint, nsIEditor::EDirection aDirection);
+template nsIContent* HTMLEditor::FindNearEditableContent(
+ const EditorRawDOMPoint& aPoint, nsIEditor::EDirection aDirection);
+
+nsresult HTMLEditor::InitEditorContentAndSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsresult rv = TextEditor::InitEditorContentAndSelection();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("TextEditor::InitEditorContentAndSelection() failed");
+ return rv;
+ }
+
+ Element* bodyOrDocumentElement = GetRoot();
+ if (NS_WARN_IF(!bodyOrDocumentElement && !GetDocument())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!bodyOrDocumentElement) {
+ return NS_OK;
+ }
+
+ rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
+ RawRangeBoundary(bodyOrDocumentElement, 0u),
+ RawRangeBoundary(bodyOrDocumentElement,
+ bodyOrDocumentElement->GetChildCount()));
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange() "
+ "failed, but ignored");
+ return NS_OK;
+}
+
+void HTMLEditor::OnStartToHandleTopLevelEditSubAction(
+ EditSubAction aTopLevelEditSubAction,
+ nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!aRv.Failed());
+
+ EditorBase::OnStartToHandleTopLevelEditSubAction(
+ aTopLevelEditSubAction, aDirectionOfTopLevelEditSubAction, aRv);
+
+ MOZ_ASSERT(GetTopLevelEditSubAction() == aTopLevelEditSubAction);
+ MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() ==
+ aDirectionOfTopLevelEditSubAction);
+
+ if (NS_WARN_IF(Destroyed())) {
+ aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
+ return;
+ }
+
+ if (!mInitSucceeded) {
+ return; // We should do nothing if we're being initialized.
+ }
+
+ NS_WARNING_ASSERTION(
+ !aRv.Failed(),
+ "EditorBase::OnStartToHandleTopLevelEditSubAction() failed");
+
+ // Remember where our selection was before edit action took place:
+ if (GetCompositionStartPoint().IsSet()) {
+ // If there is composition string, let's remember current composition
+ // range.
+ TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(
+ GetCompositionStartPoint(), GetCompositionEndPoint());
+ } else {
+ // Get the selection location
+ // XXX This may occur so that I think that we shouldn't throw exception
+ // in this case.
+ if (NS_WARN_IF(!SelectionRefPtr()->RangeCount())) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ if (const nsRange* range = SelectionRefPtr()->GetRangeAt(0)) {
+ TopLevelEditSubActionDataRef().mSelectedRange->StoreRange(*range);
+ }
+ }
+
+ // Register with range updater to track this as we perturb the doc
+ RangeUpdaterRef().RegisterRangeItem(
+ *TopLevelEditSubActionDataRef().mSelectedRange);
+
+ // Remember current inline styles for deletion and normal insertion ops
+ bool cacheInlineStyles;
+ switch (aTopLevelEditSubAction) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eDeleteSelectedContent:
+ cacheInlineStyles = true;
+ break;
+ default:
+ cacheInlineStyles =
+ IsStyleCachePreservingSubAction(aTopLevelEditSubAction);
+ break;
+ }
+ if (cacheInlineStyles) {
+ nsCOMPtr<nsIContent> containerContent = nsIContent::FromNodeOrNull(
+ aDirectionOfTopLevelEditSubAction == nsIEditor::eNext
+ ? TopLevelEditSubActionDataRef().mSelectedRange->mEndContainer
+ : TopLevelEditSubActionDataRef().mSelectedRange->mStartContainer);
+ if (NS_WARN_IF(!containerContent)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ nsresult rv = CacheInlineStyles(*containerContent);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::CacheInlineStyles() failed");
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ // Stabilize the document against contenteditable count changes
+ Document* document = GetDocument();
+ if (NS_WARN_IF(!document)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ if (document->GetEditingState() == Document::EditingState::eContentEditable) {
+ document->ChangeContentEditableCount(nullptr, +1);
+ TopLevelEditSubActionDataRef().mRestoreContentEditableCount = true;
+ }
+
+ // Check that selection is in subtree defined by body node
+ nsresult rv = EnsureSelectionInBodyOrDocumentElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
+ return;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
+ "failed, but ignored");
+}
+
+nsresult HTMLEditor::OnEndHandlingTopLevelEditSubAction() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ nsresult rv;
+ while (true) {
+ if (NS_WARN_IF(Destroyed())) {
+ rv = NS_ERROR_EDITOR_DESTROYED;
+ break;
+ }
+
+ if (!mInitSucceeded) {
+ rv = NS_OK; // We should do nothing if we're being initialized.
+ break;
+ }
+
+ // Do all the tricky stuff
+ rv = OnEndHandlingTopLevelEditSubActionInternal();
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() failied");
+ // Perhaps, we need to do the following jobs even if the editor has been
+ // destroyed since they adjust some states of HTML document but don't
+ // modify the DOM tree nor Selection.
+
+ // Free up selectionState range item
+ if (TopLevelEditSubActionDataRef().mSelectedRange) {
+ RangeUpdaterRef().DropRangeItem(
+ *TopLevelEditSubActionDataRef().mSelectedRange);
+ }
+
+ // Reset the contenteditable count to its previous value
+ if (TopLevelEditSubActionDataRef().mRestoreContentEditableCount) {
+ Document* document = GetDocument();
+ if (NS_WARN_IF(!document)) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ if (document->GetEditingState() ==
+ Document::EditingState::eContentEditable) {
+ document->ChangeContentEditableCount(nullptr, -1);
+ }
+ }
+ break;
+ }
+ DebugOnly<nsresult> rvIgnored =
+ EditorBase::OnEndHandlingTopLevelEditSubAction();
+ NS_WARNING_ASSERTION(
+ NS_FAILED(rv) || NS_SUCCEEDED(rvIgnored),
+ "EditorBase::OnEndHandlingTopLevelEditSubAction() failed, but ignored");
+ MOZ_ASSERT(!GetTopLevelEditSubAction());
+ MOZ_ASSERT(GetDirectionOfTopLevelEditSubAction() == eNone);
+ return rv;
+}
+
+nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ nsresult rv = EnsureSelectionInBodyOrDocumentElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureSelectionInBodyOrDocumentElement() "
+ "failed, but ignored");
+
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eReplaceHeadWithHTMLSource:
+ case EditSubAction::eCreatePaddingBRElementForEmptyEditor:
+ return NS_OK;
+ default:
+ break;
+ }
+
+ if (TopLevelEditSubActionDataRef().mChangedRange->IsPositioned() &&
+ GetTopLevelEditSubAction() != EditSubAction::eUndo &&
+ GetTopLevelEditSubAction() != EditSubAction::eRedo) {
+ // don't let any txns in here move the selection around behind our back.
+ // Note that this won't prevent explicit selection setting from working.
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eInsertLineBreak:
+ case EditSubAction::eInsertParagraphSeparator:
+ case EditSubAction::eDeleteText: {
+ // XXX We should investigate whether this is really needed because it
+ // seems that the following code does not handle the white-spaces.
+ RefPtr<nsRange> extendedChangedRange =
+ CreateRangeIncludingAdjuscentWhiteSpaces(
+ *TopLevelEditSubActionDataRef().mChangedRange);
+ if (extendedChangedRange) {
+ MOZ_ASSERT(extendedChangedRange->IsPositioned());
+ // Use extended range temporarily.
+ TopLevelEditSubActionDataRef().mChangedRange =
+ std::move(extendedChangedRange);
+ }
+ break;
+ }
+ default: {
+ RefPtr<nsRange> extendedChangedRange =
+ CreateRangeExtendedToHardLineStartAndEnd(
+ *TopLevelEditSubActionDataRef().mChangedRange,
+ GetTopLevelEditSubAction());
+ if (extendedChangedRange) {
+ MOZ_ASSERT(extendedChangedRange->IsPositioned());
+ // Use extended range temporarily.
+ TopLevelEditSubActionDataRef().mChangedRange =
+ std::move(extendedChangedRange);
+ }
+ break;
+ }
+ }
+
+ // if we did a ranged deletion or handling backspace key, make sure we have
+ // a place to put caret.
+ // Note we only want to do this if the overall operation was deletion,
+ // not if deletion was done along the way for
+ // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
+ // That's why this is here rather than DeleteSelectionAsSubAction().
+ // However, we shouldn't insert <br> elements if we've already removed
+ // empty block parents because users may want to disappear the line by
+ // the deletion.
+ // XXX We should make HandleDeleteSelection() store expected container
+ // for handling this here since we cannot trust current selection is
+ // collapsed at deleted point.
+ if (GetTopLevelEditSubAction() == EditSubAction::eDeleteSelectedContent &&
+ TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange &&
+ !TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks) {
+ EditorDOMPoint newCaretPosition =
+ EditorBase::GetStartPoint(*SelectionRefPtr());
+ if (!newCaretPosition.IsSet()) {
+ NS_WARNING("There was no selection range");
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ nsresult rv = InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
+ newCaretPosition);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() "
+ "failed");
+ return rv;
+ }
+ }
+
+ // add in any needed <br>s, and remove any unneeded ones.
+ nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInRange(
+ TopLevelEditSubActionDataRef().mChangedRange->StartRef().AsRaw(),
+ TopLevelEditSubActionDataRef().mChangedRange->EndRef().AsRaw());
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange()"
+ " failed, but ignored");
+
+ // merge any adjacent text nodes
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ break;
+ default: {
+ nsresult rv = CollapseAdjacentTextNodes(
+ MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::CollapseAdjacentTextNodes() failed");
+ return rv;
+ }
+ break;
+ }
+ }
+
+ // clean up any empty nodes in the selection
+ rv = RemoveEmptyNodesIn(
+ MOZ_KnownLive(*TopLevelEditSubActionDataRef().mChangedRange));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveEmptyNodesIn() failed");
+ return rv;
+ }
+
+ // attempt to transform any unneeded nbsp's into spaces after doing various
+ // operations
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eDeleteSelectedContent:
+ if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
+ break;
+ }
+ [[fallthrough]];
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eInsertLineBreak:
+ case EditSubAction::eInsertParagraphSeparator:
+ case EditSubAction::ePasteHTMLContent:
+ case EditSubAction::eInsertHTMLSource: {
+ // TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
+ // white-spaces with NPSPs and then, we'll replace them with ASCII
+ // white-spaces here. We should avoid this overwriting things as
+ // far as possible because replacing characters in text nodes
+ // causes running mutation event listeners which are really
+ // expensive.
+ // Adjust end of composition string if there is composition string.
+ EditorRawDOMPoint pointToAdjust(GetCompositionEndPoint());
+ if (!pointToAdjust.IsSet()) {
+ // Otherwise, adjust current selection start point.
+ pointToAdjust = EditorBase::GetStartPoint(*SelectionRefPtr());
+ if (NS_WARN_IF(!pointToAdjust.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ rv = WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
+ *this, pointToAdjust);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
+ "failed");
+ return rv;
+ }
+
+ // also do this for original selection endpoints.
+ // XXX Hmm, if `NormalizeVisibleWhiteSpacesAt()` runs mutation event
+ // listener and that causes changing `mSelectedRange`, what we
+ // should do?
+ if (NS_WARN_IF(
+ !TopLevelEditSubActionDataRef().mSelectedRange->IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
+ *this,
+ TopLevelEditSubActionDataRef().mSelectedRange->StartRawPoint());
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
+ "failed, but ignored");
+ // we only need to handle old selection endpoint if it was different
+ // from start
+ if (TopLevelEditSubActionDataRef().mSelectedRange->IsCollapsed()) {
+ nsresult rv =
+ WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
+ *this,
+ TopLevelEditSubActionDataRef().mSelectedRange->EndRawPoint());
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
+ "failed, but ignored");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ // If we created a new block, make sure caret is in it.
+ if (TopLevelEditSubActionDataRef().mNewBlockElement &&
+ SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretInBlockElement(
+ MOZ_KnownLive(*TopLevelEditSubActionDataRef().mNewBlockElement));
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureSelectionInBlockElement() failed, but ignored");
+ }
+
+ // Adjust selection for insert text, html paste, and delete actions if
+ // we haven't removed new empty blocks. Note that if empty block parents
+ // are removed, Selection should've been adjusted by the method which
+ // did it.
+ if (!TopLevelEditSubActionDataRef().mDidDeleteEmptyParentBlocks &&
+ SelectionRefPtr()->IsCollapsed()) {
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eDeleteSelectedContent:
+ case EditSubAction::eInsertLineBreak:
+ case EditSubAction::eInsertParagraphSeparator:
+ case EditSubAction::ePasteHTMLContent:
+ case EditSubAction::eInsertHTMLSource:
+ // XXX AdjustCaretPositionAndEnsurePaddingBRElement() intentionally
+ // does not create padding `<br>` element for empty editor.
+ // Investigate which is better that whether this should does it
+ // or wait MaybeCreatePaddingBRElementForEmptyEditor().
+ rv = AdjustCaretPositionAndEnsurePaddingBRElement(
+ GetDirectionOfTopLevelEditSubAction());
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement() "
+ "failed");
+ return rv;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // check for any styles which were removed inappropriately
+ bool reapplyCachedStyle;
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eDeleteSelectedContent:
+ reapplyCachedStyle = true;
+ break;
+ default:
+ reapplyCachedStyle =
+ IsStyleCachePreservingSubAction(GetTopLevelEditSubAction());
+ break;
+ }
+ if (reapplyCachedStyle) {
+ DebugOnly<nsresult> rvIgnored =
+ mTypeInState->UpdateSelState(SelectionRefPtr());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "TypeInState::UpdateSelState() failed, but ignored");
+ rv = ReapplyCachedStyles();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
+ return rv;
+ }
+ TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
+ }
+ }
+
+ rv = HandleInlineSpellCheck(
+ TopLevelEditSubActionDataRef().mSelectedRange->StartPoint(),
+ TopLevelEditSubActionDataRef().mChangedRange);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::HandleInlineSpellCheck() failed");
+ return rv;
+ }
+
+ // detect empty doc
+ // XXX Need to investigate when the padding <br> element is removed because
+ // I don't see the <br> element with testing manually. If it won't be
+ // used, we can get rid of this cost.
+ rv = MaybeCreatePaddingBRElementForEmptyEditor();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
+ return rv;
+ }
+
+ // adjust selection HINT if needed
+ if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine &&
+ SelectionRefPtr()->IsCollapsed()) {
+ SetSelectionInterlinePosition();
+ }
+
+ return NS_OK;
+}
+
+EditActionResult HTMLEditor::CanHandleHTMLEditSubAction() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+
+ // If there is not selection ranges, we should ignore the result.
+ if (!SelectionRefPtr()->RangeCount()) {
+ return EditActionCanceled();
+ }
+
+ const nsRange* range = SelectionRefPtr()->GetRangeAt(0);
+ nsINode* selStartNode = range->GetStartContainer();
+ if (NS_WARN_IF(!selStartNode)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ if (!HTMLEditUtils::IsSimplyEditableNode(*selStartNode)) {
+ return EditActionCanceled();
+ }
+
+ nsINode* selEndNode = range->GetEndContainer();
+ if (NS_WARN_IF(!selEndNode)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ if (selStartNode == selEndNode) {
+ return EditActionIgnored();
+ }
+
+ if (!HTMLEditUtils::IsSimplyEditableNode(*selEndNode)) {
+ return EditActionCanceled();
+ }
+
+ // XXX What does it mean the common ancestor is editable? I have no idea.
+ // It should be in same (active) editing host, and even if it's editable,
+ // there may be non-editable contents in the range.
+ nsINode* commonAncestor = range->GetClosestCommonInclusiveAncestor();
+ if (!commonAncestor) {
+ NS_WARNING(
+ "AbstractRange::GetClosestCommonInclusiveAncestor() returned nullptr");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ return HTMLEditUtils::IsSimplyEditableNode(*commonAncestor)
+ ? EditActionIgnored()
+ : EditActionCanceled();
+}
+
+MOZ_CAN_RUN_SCRIPT static nsStaticAtom& MarginPropertyAtomForIndent(
+ nsIContent& aContent) {
+ nsAutoString direction;
+ DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
+ aContent, *nsGkAtoms::direction, direction);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "CSSEditUtils::GetComputedProperty(nsGkAtoms::direction)"
+ " failed, but ignored");
+ return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
+ : *nsGkAtoms::marginLeft;
+}
+
+nsresult HTMLEditor::EnsureCaretNotAfterPaddingBRElement() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
+ // If we are after a padding `<br>` element for empty last line in the same
+ // block, then move selection to be before it
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorRawDOMPoint atSelectionStart(firstRange->StartRef());
+ if (NS_WARN_IF(!atSelectionStart.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(atSelectionStart.IsSetAndValid());
+
+ nsCOMPtr<nsIContent> previousEditableContent =
+ GetPreviousEditableHTMLNode(atSelectionStart);
+ if (!previousEditableContent ||
+ !EditorUtils::IsPaddingBRElementForEmptyLastLine(
+ *previousEditableContent)) {
+ return NS_OK;
+ }
+
+ if (!atSelectionStart.IsInContentNode()) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> blockElementAtSelectionStart =
+ HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *atSelectionStart.ContainerAsContent());
+ RefPtr<Element> parentBlockElementOfPreviousEditableContent =
+ HTMLEditUtils::GetAncestorBlockElement(*previousEditableContent);
+
+ if (!blockElementAtSelectionStart ||
+ blockElementAtSelectionStart !=
+ parentBlockElementOfPreviousEditableContent) {
+ return NS_OK;
+ }
+
+ // If we are here then the selection is right after a padding <br>
+ // element for empty last line that is in the same block as the
+ // selection. We need to move the selection start to be before the
+ // padding <br> element.
+ EditorRawDOMPoint atPreviousEditableContent(previousEditableContent);
+ nsresult rv = CollapseSelectionTo(atPreviousEditableContent);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::PrepareInlineStylesForCaret() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+ MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
+ // XXX This method works with the top level edit sub-action, but this
+ // must be wrong if we are handling nested edit action.
+
+ if (TopLevelEditSubActionDataRef().mDidDeleteSelection) {
+ switch (GetTopLevelEditSubAction()) {
+ case EditSubAction::eInsertText:
+ case EditSubAction::eInsertTextComingFromIME:
+ case EditSubAction::eDeleteSelectedContent: {
+ nsresult rv = ReapplyCachedStyles();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::ReapplyCachedStyles() failed");
+ return rv;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ // For most actions we want to clear the cached styles, but there are
+ // exceptions
+ if (!IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
+ TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
+ }
+ return NS_OK;
+}
+
+EditActionResult HTMLEditor::HandleInsertText(
+ EditSubAction aEditSubAction, const nsAString& aInsertionString) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+ MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
+ aEditSubAction == EditSubAction::eInsertTextComingFromIME);
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ UndefineCaretBidiLevel();
+
+ // If the selection isn't collapsed, delete it. Don't delete existing inline
+ // tags, because we're hopefully going to insert text (bug 787432).
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv =
+ DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
+ "nsIEditor::eNoStrip) failed");
+ return EditActionHandled(rv);
+ }
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ RefPtr<Document> document = GetDocument();
+ if (NS_WARN_IF(!document)) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ RefPtr<const nsRange> firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ // for every property that is set, insert a new inline style node
+ // XXX CreateStyleForInsertText() adjusts selection automatically, but
+ // it should just return the insertion point instead.
+ rv = CreateStyleForInsertText(*firstRange);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
+ return EditActionHandled(rv);
+ }
+
+ firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ EditorDOMPoint pointToInsert(firstRange->StartRef());
+ if (NS_WARN_IF(!pointToInsert.IsSet()) ||
+ NS_WARN_IF(!pointToInsert.IsInContentNode())) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(pointToInsert.IsSetAndValid());
+
+ // dont put text in places that can't have it
+ if (!pointToInsert.IsInTextNode() &&
+ !HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
+ *nsGkAtoms::textTagName)) {
+ NS_WARNING("Selection start container couldn't have text nodes");
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) {
+ EditorRawDOMPoint compositionStartPoint = GetCompositionStartPoint();
+ if (!compositionStartPoint.IsSet()) {
+ compositionStartPoint = pointToInsert;
+ }
+
+ if (aInsertionString.IsEmpty()) {
+ // Right now the WhiteSpaceVisibilityKeeper code bails on empty strings,
+ // but IME needs the InsertTextWithTransaction() call to still happen
+ // since empty strings are meaningful there.
+ nsresult rv = InsertTextWithTransaction(*document, aInsertionString,
+ compositionStartPoint);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::InsertTextWithTransaction() failed");
+ return EditActionHandled(rv);
+ }
+
+ EditorRawDOMPoint compositionEndPoint = GetCompositionEndPoint();
+ if (!compositionEndPoint.IsSet()) {
+ compositionEndPoint = compositionStartPoint;
+ }
+ nsresult rv = WhiteSpaceVisibilityKeeper::ReplaceText(
+ *this, aInsertionString,
+ EditorDOMRange(compositionStartPoint, compositionEndPoint));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
+ return EditActionHandled(rv);
+ }
+
+ compositionStartPoint = GetCompositionStartPoint();
+ compositionEndPoint = GetCompositionEndPoint();
+ if (NS_WARN_IF(!compositionStartPoint.IsSet()) ||
+ NS_WARN_IF(!compositionEndPoint.IsSet())) {
+ // Mutation event listener has changed the DOM tree...
+ return EditActionHandled();
+ }
+ rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
+ compositionStartPoint.ToRawRangeBoundary(),
+ compositionEndPoint.ToRawRangeBoundary());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
+ return EditActionHandled(rv);
+ }
+
+ MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText);
+
+ // find where we are
+ EditorDOMPoint currentPoint(pointToInsert);
+
+ // is our text going to be PREformatted?
+ // We remember this so that we know how to handle tabs.
+ bool isPRE =
+ EditorUtils::IsContentPreformatted(*pointToInsert.ContainerAsContent());
+
+ // turn off the edit listener: we know how to
+ // build the "doc changed range" ourselves, and it's
+ // must faster to do it once here than to track all
+ // the changes one at a time.
+ AutoRestore<bool> disableListener(
+ EditSubActionDataRef().mAdjustChangedRangeFromListener);
+ EditSubActionDataRef().mAdjustChangedRangeFromListener = false;
+
+ // don't change my selection in subtransactions
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+ int32_t pos = 0;
+ constexpr auto newlineStr = NS_LITERAL_STRING_FROM_CSTRING(LFSTR);
+
+ {
+ AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
+
+ // for efficiency, break out the pre case separately. This is because
+ // its a lot cheaper to search the input string for only newlines than
+ // it is to search for both tabs and newlines.
+ if (isPRE || IsPlaintextEditor()) {
+ while (pos != -1 &&
+ pos < static_cast<int32_t>(aInsertionString.Length())) {
+ int32_t oldPos = pos;
+ int32_t subStrLen;
+ pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
+
+ if (pos != -1) {
+ subStrLen = pos - oldPos;
+ // if first char is newline, then use just it
+ if (!subStrLen) {
+ subStrLen = 1;
+ }
+ } else {
+ subStrLen = aInsertionString.Length() - oldPos;
+ pos = aInsertionString.Length();
+ }
+
+ nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
+
+ // is it a return?
+ if (subStr.Equals(newlineStr)) {
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(currentPoint, nsIEditor::eNone);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!brElement) {
+ NS_WARNING(
+ "HTMLEditor::InsertBRElementWithTransaction(eNone) failed");
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+ pos++;
+ if (brElement->GetNextSibling()) {
+ pointToInsert.Set(brElement->GetNextSibling());
+ } else {
+ pointToInsert.SetToEndOf(currentPoint.GetContainer());
+ }
+ // XXX In most cases, pointToInsert and currentPoint are same here.
+ // But if the <br> element has been moved to different point by
+ // mutation observer, those points become different.
+ currentPoint.SetAfter(brElement);
+ NS_WARNING_ASSERTION(currentPoint.IsSet(),
+ "Failed to set after the <br> element");
+ NS_WARNING_ASSERTION(currentPoint == pointToInsert,
+ "Perhaps, <br> element position has been moved "
+ "to different point "
+ "by mutation observer");
+ } else {
+ EditorRawDOMPoint pointAfterInsertedString;
+ nsresult rv = InsertTextWithTransaction(
+ *document, subStr, EditorRawDOMPoint(currentPoint),
+ &pointAfterInsertedString);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
+ return EditActionHandled(rv);
+ }
+ currentPoint = pointAfterInsertedString;
+ pointToInsert = pointAfterInsertedString;
+ }
+ }
+ } else {
+ constexpr auto tabStr = u"\t"_ns;
+ constexpr auto spacesStr = u" "_ns;
+ char specialChars[] = {TAB, nsCRT::LF, 0};
+ nsAutoString insertionString(aInsertionString); // For FindCharInSet().
+ while (pos != -1 &&
+ pos < static_cast<int32_t>(insertionString.Length())) {
+ int32_t oldPos = pos;
+ int32_t subStrLen;
+ pos = insertionString.FindCharInSet(specialChars, oldPos);
+
+ if (pos != -1) {
+ subStrLen = pos - oldPos;
+ // if first char is newline, then use just it
+ if (!subStrLen) {
+ subStrLen = 1;
+ }
+ } else {
+ subStrLen = insertionString.Length() - oldPos;
+ pos = insertionString.Length();
+ }
+
+ nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
+
+ // is it a tab?
+ if (subStr.Equals(tabStr)) {
+ EditorRawDOMPoint pointAfterInsertedSpaces;
+ nsresult rv = WhiteSpaceVisibilityKeeper::InsertText(
+ *this, spacesStr, currentPoint, &pointAfterInsertedSpaces);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
+ return EditActionHandled(rv);
+ }
+ pos++;
+ MOZ_ASSERT(pointAfterInsertedSpaces.IsSet());
+ currentPoint = pointAfterInsertedSpaces;
+ pointToInsert = pointAfterInsertedSpaces;
+ }
+ // is it a return?
+ else if (subStr.Equals(newlineStr)) {
+ Result<RefPtr<Element>, nsresult> result =
+ WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint);
+ if (result.isErr()) {
+ NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
+ return EditActionHandled(result.inspectErr());
+ }
+ pos++;
+ RefPtr<Element> newBRElement = result.unwrap();
+ MOZ_DIAGNOSTIC_ASSERT(newBRElement);
+ if (newBRElement->GetNextSibling()) {
+ pointToInsert.Set(newBRElement->GetNextSibling());
+ } else {
+ pointToInsert.SetToEndOf(currentPoint.GetContainer());
+ }
+ currentPoint.SetAfter(newBRElement);
+ NS_WARNING_ASSERTION(currentPoint.IsSet(),
+ "Failed to set after the new <br> element");
+ // XXX If the newBRElement has been moved or removed by mutation
+ // observer, we hit this assert. We need to check if
+ // newBRElement is in expected point, though, we must have
+ // a lot of same bugs...
+ NS_WARNING_ASSERTION(
+ currentPoint == pointToInsert,
+ "Perhaps, newBRElement has been moved or removed unexpectedly");
+ } else {
+ EditorRawDOMPoint pointAfterInsertedString;
+ nsresult rv = WhiteSpaceVisibilityKeeper::InsertText(
+ *this, subStr, currentPoint, &pointAfterInsertedString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
+ return EditActionHandled(rv);
+ }
+ MOZ_ASSERT(pointAfterInsertedString.IsSet());
+ currentPoint = pointAfterInsertedString;
+ pointToInsert = pointAfterInsertedString;
+ }
+ }
+ }
+
+ // After this block, pointToInsert is updated by AutoTrackDOMPoint.
+ }
+
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
+ NS_WARNING_ASSERTION(!ignoredError.Failed(),
+ "Failed to unset interline position");
+
+ if (currentPoint.IsSet()) {
+ nsresult rv = CollapseSelectionTo(currentPoint);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Selection::Collapse() failed, but ignored");
+ }
+
+ // manually update the doc changed range so that AfterEdit will clean up
+ // the correct portion of the document.
+ if (currentPoint.IsSet()) {
+ nsresult rv = TopLevelEditSubActionDataRef().mChangedRange->SetStartAndEnd(
+ pointToInsert.ToRawRangeBoundary(), currentPoint.ToRawRangeBoundary());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
+ return EditActionHandled(rv);
+ }
+
+ rv = TopLevelEditSubActionDataRef().mChangedRange->CollapseTo(pointToInsert);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::CollapseTo() failed");
+ return EditActionHandled(rv);
+}
+
+EditActionResult HTMLEditor::InsertParagraphSeparatorAsSubAction() {
+ if (NS_WARN_IF(!mInitSucceeded)) {
+ return EditActionIgnored(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ // XXX This may be called by execCommand() with "insertParagraph".
+ // In such case, naming the transaction "TypingTxnName" is odd.
+ AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
+ ScrollSelectionIntoView::Yes);
+
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eInsertParagraphSeparator, nsIEditor::eNext,
+ ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ UndefineCaretBidiLevel();
+
+ // If the selection isn't collapsed, delete it.
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv =
+ DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
+ return EditActionIgnored(rv);
+ }
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ // Split any mailcites in the way. Should we abort this if we encounter
+ // table cell boundaries?
+ if (IsMailEditor()) {
+ EditorDOMPoint pointToSplit(EditorBase::GetStartPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!pointToSplit.IsSet())) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+
+ EditActionResult result = SplitMailCiteElements(pointToSplit);
+ if (result.Failed()) {
+ NS_WARNING("HTMLEditor::SplitMailCiteElements() failed");
+ return result;
+ }
+ if (result.Handled()) {
+ return result;
+ }
+ }
+
+ // Smart splitting rules
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
+
+ // Do nothing if the node is read-only
+ if (!HTMLEditUtils::IsSimplyEditableNode(
+ *atStartOfSelection.GetContainer())) {
+ return EditActionCanceled();
+ }
+
+ // If the active editing host is an inline element, or if the active editing
+ // host is the block parent itself and we're configured to use <br> as a
+ // paragraph separator, just append a <br>.
+ RefPtr<Element> editingHost = GetActiveEditingHost();
+ if (NS_WARN_IF(!editingHost)) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+
+ // Look for the nearest parent block. However, don't return error even if
+ // there is no block parent here because in such case, i.e., editing host
+ // is an inline element, we should insert <br> simply.
+ RefPtr<Element> blockElement =
+ atStartOfSelection.IsInContentNode()
+ ? HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *atStartOfSelection.ContainerAsContent(), editingHost)
+ : nullptr;
+
+ ParagraphSeparator separator = GetDefaultParagraphSeparator();
+ bool insertBRElement;
+ // If there is no block parent in the editing host, i.e., the editing host
+ // itself is also a non-block element, we should insert a <br> element.
+ if (!blockElement) {
+ // XXX Chromium checks if the CSS box of the editing host is a block.
+ insertBRElement = true;
+ }
+ // If only the editing host is block, and the default paragraph separator
+ // is <br> or the editing host cannot contain a <p> element, we should
+ // insert a <br> element.
+ else if (editingHost == blockElement) {
+ insertBRElement = separator == ParagraphSeparator::br ||
+ !HTMLEditUtils::CanElementContainParagraph(*editingHost);
+ }
+ // If the nearest block parent is a single-line container declared in
+ // the execCommand spec and not the editing host, we should separate the
+ // block even if the default paragraph separator is <br> element.
+ else if (HTMLEditUtils::IsSingleLineContainer(*blockElement)) {
+ insertBRElement = false;
+ }
+ // Otherwise, unless there is no block ancestor which can contain <p>
+ // element, we shouldn't insert a <br> element here.
+ else {
+ insertBRElement = true;
+ for (Element* blockAncestor = blockElement;
+ blockAncestor && insertBRElement;
+ blockAncestor = HTMLEditUtils::GetAncestorBlockElement(*blockAncestor,
+ editingHost)) {
+ insertBRElement =
+ !HTMLEditUtils::CanElementContainParagraph(*blockAncestor);
+ }
+ }
+
+ // If we cannot insert a <p>/<div> element at the selection, we should insert
+ // a <br> element instead.
+ if (insertBRElement) {
+ nsresult rv = InsertBRElement(atStartOfSelection);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertBRElement() failed");
+ return EditActionIgnored(rv);
+ }
+ return EditActionHandled();
+ }
+
+ if (editingHost == blockElement && separator != ParagraphSeparator::br) {
+ // Insert a new block first
+ MOZ_ASSERT(separator == ParagraphSeparator::div ||
+ separator == ParagraphSeparator::p);
+ // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
+ // Therefore, even if it returns NS_OK, editor might have been destroyed
+ // at restoring Selection.
+ nsresult rv = FormatBlockContainerWithTransaction(
+ MOZ_KnownLive(HTMLEditor::ToParagraphSeparatorTagName(separator)));
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
+ NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ // We warn on failure, but don't handle it, because it might be harmless.
+ // Instead we just check that a new block was actually created.
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::FormatBlockContainerWithTransaction() "
+ "failed, but ignored");
+
+ firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+
+ atStartOfSelection = firstRange->StartRef();
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
+
+ blockElement =
+ atStartOfSelection.IsInContentNode()
+ ? HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *atStartOfSelection.ContainerAsContent(), editingHost)
+ : nullptr;
+ if (NS_WARN_IF(!blockElement)) {
+ return EditActionIgnored(NS_ERROR_UNEXPECTED);
+ }
+ if (NS_WARN_IF(blockElement == editingHost)) {
+ // Didn't create a new block for some reason, fall back to <br>
+ rv = InsertBRElement(atStartOfSelection);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertBRElement() failed");
+ return EditActionIgnored(rv);
+ }
+ return EditActionHandled();
+ }
+ // Now, mNewBlockElement is last created block element for wrapping inline
+ // elements around the caret position and AfterEditInner() will move
+ // caret into it. However, it may be different from block parent of
+ // the caret position. E.g., FormatBlockContainerWithTransaction() may
+ // wrap following inline elements of a <br> element which is next sibling
+ // of container of the caret. So, we need to adjust mNewBlockElement here
+ // for avoiding jumping caret to odd position.
+ TopLevelEditSubActionDataRef().mNewBlockElement = blockElement;
+ }
+
+ // If block is empty, populate with br. (For example, imagine a div that
+ // contains the word "text". The user selects "text" and types return.
+ // "Text" is deleted leaving an empty block. We want to put in one br to
+ // make block have a line. Then code further below will put in a second br.)
+ if (IsEmptyBlockElement(*blockElement, IgnoreSingleBR::No)) {
+ AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
+ EditorDOMPoint endOfBlockParent;
+ endOfBlockParent.SetToEndOf(blockElement);
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(endOfBlockParent);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+ }
+
+ RefPtr<Element> listItem = GetNearestAncestorListItemElement(*blockElement);
+ if (listItem && listItem != editingHost) {
+ nsresult rv = HandleInsertParagraphInListItemElement(
+ *listItem, MOZ_KnownLive(*atStartOfSelection.GetContainer()),
+ atStartOfSelection.Offset());
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleInsertParagraphInListItemElement() "
+ "failed, but ignored");
+ return EditActionHandled();
+ }
+
+ if (HTMLEditUtils::IsHeader(*blockElement)) {
+ // Headers: close (or split) header
+ nsresult rv = HandleInsertParagraphInHeadingElement(
+ *blockElement, MOZ_KnownLive(*atStartOfSelection.GetContainer()),
+ atStartOfSelection.Offset());
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleInsertParagraphInHeadingElement() "
+ "failed, but ignored");
+ return EditActionHandled();
+ }
+
+ // XXX Ideally, we should take same behavior with both <p> container and
+ // <div> container. However, we are still using <br> as default
+ // paragraph separator (non-standard) and we've split only <p> container
+ // long time. Therefore, some web apps may depend on this behavior like
+ // Gmail. So, let's use traditional odd behavior only when the default
+ // paragraph separator is <br>. Otherwise, take consistent behavior
+ // between <p> container and <div> container.
+ if ((separator == ParagraphSeparator::br &&
+ blockElement->IsHTMLElement(nsGkAtoms::p)) ||
+ (separator != ParagraphSeparator::br &&
+ blockElement->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) {
+ AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
+ // Paragraphs: special rules to look for <br>s
+ EditActionResult result = HandleInsertParagraphInParagraph(*blockElement);
+ if (result.Failed()) {
+ NS_WARNING("HTMLEditor::HandleInsertParagraphInParagraph() failed");
+ return result;
+ }
+ if (result.Handled()) {
+ // Now, atStartOfSelection may be invalid because the left paragraph
+ // may have less children than its offset. For avoiding warnings of
+ // validation of EditorDOMPoint, we should not touch it anymore.
+ lockOffset.Cancel();
+ return result;
+ }
+ // Fall through, if HandleInsertParagraphInParagraph() didn't handle it.
+ MOZ_ASSERT(!result.Canceled(),
+ "HandleInsertParagraphInParagraph() canceled this edit action, "
+ "InsertParagraphSeparatorAsSubAction() needs to handle this "
+ "action instead");
+ }
+
+ // If nobody handles this edit action, let's insert new <br> at the selection.
+ rv = InsertBRElement(atStartOfSelection);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertBRElement() failed");
+ return EditActionIgnored(rv);
+ }
+ return EditActionHandled();
+}
+
+nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (NS_WARN_IF(!aPointToBreak.IsSet())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool brElementIsAfterBlock = false, brElementIsBeforeBlock = false;
+
+ // First, insert a <br> element.
+ RefPtr<Element> brElement;
+ if (IsPlaintextEditor()) {
+ brElement = InsertBRElementWithTransaction(aPointToBreak);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ EditorDOMPoint pointToBreak(aPointToBreak);
+ WSRunScanner wsRunScanner(*this, pointToBreak);
+ WSScanResult backwardScanResult =
+ wsRunScanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToBreak);
+ if (backwardScanResult.Failed()) {
+ NS_WARNING(
+ "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
+ return NS_ERROR_FAILURE;
+ }
+ brElementIsAfterBlock = backwardScanResult.ReachedBlockBoundary();
+ WSScanResult forwardScanResult =
+ wsRunScanner.ScanNextVisibleNodeOrBlockBoundaryFrom(pointToBreak);
+ if (forwardScanResult.Failed()) {
+ NS_WARNING(
+ "WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
+ return NS_ERROR_FAILURE;
+ }
+ brElementIsBeforeBlock = forwardScanResult.ReachedBlockBoundary();
+ // If the container of the break is a link, we need to split it and
+ // insert new <br> between the split links.
+ RefPtr<Element> linkNode =
+ HTMLEditor::GetLinkElement(pointToBreak.GetContainer());
+ if (linkNode) {
+ SplitNodeResult splitLinkNodeResult = SplitNodeDeepWithTransaction(
+ *linkNode, pointToBreak, SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitLinkNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+ return splitLinkNodeResult.Rv();
+ }
+ pointToBreak = splitLinkNodeResult.SplitPoint();
+ }
+ Result<RefPtr<Element>, nsresult> result =
+ WhiteSpaceVisibilityKeeper::InsertBRElement(*this, pointToBreak);
+ if (result.isErr()) {
+ NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
+ return result.inspectErr();
+ }
+ brElement = result.unwrap();
+ MOZ_ASSERT(brElement);
+ }
+
+ // If the <br> element has already been removed from the DOM tree by a
+ // mutation event listener, don't continue handling this.
+ if (NS_WARN_IF(!brElement->GetParentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (brElementIsAfterBlock && brElementIsBeforeBlock) {
+ // We just placed a <br> between block boundaries. This is the one case
+ // where we want the selection to be before the br we just placed, as the
+ // br will be on a new line, rather than at end of prior line.
+ // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
+ // modifying the DOM tree. So, now, the <br> element may not be
+ // between blocks.
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(brElement));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+
+ EditorDOMPoint afterBRElement(brElement);
+ DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced,
+ "Failed to advance offset after the new <br> element");
+ WSScanResult forwardScanFromAfterBRElementResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, afterBRElement);
+ if (forwardScanFromAfterBRElementResult.Failed()) {
+ NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
+ return NS_ERROR_FAILURE;
+ }
+ if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
+ // The next thing after the break we inserted is another break. Move the
+ // second break to be the first break's sibling. This will prevent them
+ // from being in different inline nodes, which would break
+ // SetInterlinePosition(). It will also assure that if the user clicks
+ // away and then clicks back on their new blank line, they will still get
+ // the style from the line above.
+ if (brElement->GetNextSibling() !=
+ forwardScanFromAfterBRElementResult.BRElementPtr()) {
+ MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
+ nsresult rv = MoveNodeWithTransaction(
+ MOZ_KnownLive(*forwardScanFromAfterBRElementResult.BRElementPtr()),
+ afterBRElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+ }
+
+ // SetInterlinePosition(true) means we want the caret to stick to the
+ // content on the "right". We want the caret to stick to whatever is past
+ // the break. This is because the break is on the same line we were on,
+ // but the next content will be on the following line.
+
+ // An exception to this is if the break has a next sibling that is a block
+ // node. Then we stick to the left to avoid an uber caret.
+ nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(
+ !(nextSiblingOfBRElement &&
+ HTMLEditUtils::IsBlockElement(*nextSiblingOfBRElement)),
+ ignoredError);
+ NS_WARNING_ASSERTION(!ignoredError.Failed(),
+ "Selection::SetInterlinePosition() failed, but ignored");
+ nsresult rv = CollapseSelectionTo(afterBRElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::SplitMailCiteElements(
+ const EditorDOMPoint& aPointToSplit) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aPointToSplit.IsSet());
+
+ RefPtr<Element> citeNode =
+ GetMostAncestorMailCiteElement(*aPointToSplit.GetContainer());
+ if (!citeNode) {
+ return EditActionIgnored();
+ }
+
+ EditorDOMPoint pointToSplit(aPointToSplit);
+
+ // If our selection is just before a break, nudge it to be just after it.
+ // This does two things for us. It saves us the trouble of having to add
+ // a break here ourselves to preserve the "blockness" of the inline span
+ // mailquote (in the inline case), and :
+ // it means the break won't end up making an empty line that happens to be
+ // inside a mailquote (in either inline or block case).
+ // The latter can confuse a user if they click there and start typing,
+ // because being in the mailquote may affect wrapping behavior, or font
+ // color, etc.
+ WSScanResult forwardScanFromPointToSplitResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, pointToSplit);
+ if (forwardScanFromPointToSplitResult.Failed()) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ // If selection start point is before a break and it's inside the mailquote,
+ // let's split it after the visible node.
+ if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
+ forwardScanFromPointToSplitResult.BRElementPtr() != citeNode &&
+ citeNode->Contains(forwardScanFromPointToSplitResult.BRElementPtr())) {
+ pointToSplit = forwardScanFromPointToSplitResult.PointAfterContent();
+ }
+
+ if (NS_WARN_IF(!pointToSplit.GetContainerAsContent())) {
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+
+ SplitNodeResult splitCiteNodeResult = SplitNodeDeepWithTransaction(
+ *citeNode, pointToSplit, SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (splitCiteNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+ return EditActionIgnored(splitCiteNodeResult.Rv());
+ }
+ pointToSplit.Clear();
+
+ // Add an invisible <br> to the end of current cite node (If new left cite
+ // has not been created, we're at the end of it. Otherwise, we're still at
+ // the right node) if it was a <span> of style="display: block". This is
+ // important, since when serializing the cite to plain text, the span which
+ // caused the visual break is discarded. So the added <br> will guarantee
+ // that the serializer will insert a break where the user saw one.
+ // FYI: splitCiteNodeResult grabs the previous node with nsCOMPtr. So, it's
+ // safe to access previousNodeOfSplitPoint even after changing the DOM
+ // tree and/or selection even though it's raw pointer.
+ nsIContent* previousNodeOfSplitPoint = splitCiteNodeResult.GetPreviousNode();
+ if (previousNodeOfSplitPoint &&
+ previousNodeOfSplitPoint->IsHTMLElement(nsGkAtoms::span) &&
+ previousNodeOfSplitPoint->GetPrimaryFrame() &&
+ previousNodeOfSplitPoint->GetPrimaryFrame()->IsBlockFrameOrSubclass()) {
+ nsCOMPtr<nsINode> lastChild = previousNodeOfSplitPoint->GetLastChild();
+ if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
+ // We ignore the result here.
+ EditorDOMPoint endOfPreviousNodeOfSplitPoint;
+ endOfPreviousNodeOfSplitPoint.SetToEndOf(previousNodeOfSplitPoint);
+ RefPtr<Element> invisibleBRElement =
+ InsertBRElementWithTransaction(endOfPreviousNodeOfSplitPoint);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ invisibleBRElement,
+ "HTMLEditor::InsertBRElementWithTransaction() failed, but ignored");
+ }
+ }
+
+ // In most cases, <br> should be inserted after current cite. However, if
+ // left cite hasn't been created because the split point was start of the
+ // cite node, <br> should be inserted before the current cite.
+ EditorDOMPoint pointToInsertBRNode(splitCiteNodeResult.SplitPoint());
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(pointToInsertBRNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+ // Now, offset of pointToInsertBRNode is invalid. Let's clear it.
+ pointToInsertBRNode.Clear();
+
+ // Want selection before the break, and on same line.
+ EditorDOMPoint atBRElement(brElement);
+ {
+ AutoEditorDOMPointChildInvalidator lockOffset(atBRElement);
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ nsresult rv = CollapseSelectionTo(atBRElement);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
+ return EditActionIgnored(rv);
+ }
+ }
+
+ // if citeNode wasn't a block, we might also want another break before it.
+ // We need to examine the content both before the br we just added and also
+ // just after it. If we don't have another br or block boundary adjacent,
+ // then we will need a 2nd br added to achieve blank line that user expects.
+ if (HTMLEditUtils::IsInlineElement(*citeNode)) {
+ // Use DOM point which we tried to collapse to.
+ EditorDOMPoint pointToCreateNewBRElement(atBRElement);
+
+ WSScanResult backwardScanFromPointToCreateNewBRElementResult =
+ WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
+ *this, pointToCreateNewBRElement);
+ if (backwardScanFromPointToCreateNewBRElementResult.Failed()) {
+ NS_WARNING(
+ "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ if (backwardScanFromPointToCreateNewBRElementResult
+ .InNormalWhiteSpacesOrText() ||
+ backwardScanFromPointToCreateNewBRElementResult
+ .ReachedSpecialContent()) {
+ EditorRawDOMPoint pointAfterNewBRElement(
+ EditorRawDOMPoint::After(pointToCreateNewBRElement));
+ NS_WARNING_ASSERTION(pointAfterNewBRElement.IsSet(),
+ "Failed to set to after the <br> node");
+ WSScanResult forwardScanFromPointAfterNewBRElementResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ *this, pointAfterNewBRElement);
+ if (forwardScanFromPointAfterNewBRElementResult.Failed()) {
+ NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ if (forwardScanFromPointAfterNewBRElementResult
+ .InNormalWhiteSpacesOrText() ||
+ forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() ||
+ // In case we're at the very end.
+ forwardScanFromPointAfterNewBRElementResult
+ .ReachedCurrentBlockBoundary()) {
+ brElement = InsertBRElementWithTransaction(pointToCreateNewBRElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return EditActionIgnored(NS_ERROR_FAILURE);
+ }
+ // Now, those points may be invalid.
+ pointToCreateNewBRElement.Clear();
+ pointAfterNewBRElement.Clear();
+ }
+ }
+ }
+
+ // delete any empty cites
+ if (previousNodeOfSplitPoint &&
+ IsEmptyNode(*previousNodeOfSplitPoint, true, false)) {
+ nsresult rv =
+ DeleteNodeWithTransaction(MOZ_KnownLive(*previousNodeOfSplitPoint));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return EditActionIgnored(rv);
+ }
+ }
+
+ if (citeNode && IsEmptyNode(*citeNode, true, false)) {
+ nsresult rv = DeleteNodeWithTransaction(*citeNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return EditActionIgnored(rv);
+ }
+ }
+
+ return EditActionHandled();
+}
+
+HTMLEditor::CharPointData
+HTMLEditor::GetPreviousCharPointDataForNormalizingWhiteSpaces(
+ const EditorDOMPointInText& aPoint) const {
+ MOZ_ASSERT(aPoint.IsSetAndValid());
+
+ if (!aPoint.IsStartOfContainer()) {
+ return CharPointData::InSameTextNode(
+ HTMLEditor::GetPreviousCharPointType(aPoint));
+ }
+ EditorDOMPointInText previousCharPoint =
+ WSRunScanner::GetPreviousEditableCharPoint(*this, aPoint);
+ if (!previousCharPoint.IsSet()) {
+ return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
+ }
+ return CharPointData::InDifferentTextNode(
+ HTMLEditor::GetCharPointType(previousCharPoint));
+}
+
+HTMLEditor::CharPointData
+HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
+ const EditorDOMPointInText& aPoint) const {
+ MOZ_ASSERT(aPoint.IsSetAndValid());
+
+ if (!aPoint.IsEndOfContainer()) {
+ return CharPointData::InSameTextNode(HTMLEditor::GetCharPointType(aPoint));
+ }
+ EditorDOMPointInText nextCharPoint =
+ WSRunScanner::GetInclusiveNextEditableCharPoint(*this, aPoint);
+ if (!nextCharPoint.IsSet()) {
+ return CharPointData::InDifferentTextNode(CharPointType::TextEnd);
+ }
+ return CharPointData::InDifferentTextNode(
+ HTMLEditor::GetCharPointType(nextCharPoint));
+}
+
+// static
+void HTMLEditor::GenerateWhiteSpaceSequence(
+ nsAString& aResult, uint32_t aLength,
+ const CharPointData& aPreviousCharPointData,
+ const CharPointData& aNextCharPointData) {
+ MOZ_ASSERT(aResult.IsEmpty());
+ MOZ_ASSERT(aLength);
+ // For now, this method does not assume that result will be append to
+ // white-space sequence in the text node.
+ MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
+ !aPreviousCharPointData.IsWhiteSpace());
+ // For now, this method does not assume that the result will be inserted
+ // into white-space sequence nor start of white-space sequence.
+ MOZ_ASSERT(aNextCharPointData.AcrossTextNodeBoundary() ||
+ !aNextCharPointData.IsWhiteSpace());
+
+ if (aLength == 1) {
+ // Even if previous/next char is in different text node, we should put
+ // an ASCII white-space between visible characters.
+ // XXX This means that this does not allow to put an NBSP in HTML editor
+ // without preformatted style. However, Chrome has same issue too.
+ if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
+ aNextCharPointData.Type() == CharPointType::VisibleChar) {
+ aResult.Assign(HTMLEditUtils::kSpace);
+ return;
+ }
+ // If it's start or end of text, put an NBSP.
+ if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
+ aNextCharPointData.Type() == CharPointType::TextEnd) {
+ aResult.Assign(HTMLEditUtils::kNBSP);
+ return;
+ }
+ // Now, the white-space will be inserted to a white-space sequence, but not
+ // end of text. We can put an ASCII white-space only when both sides are
+ // not ASCII white-spaces.
+ aResult.Assign(
+ aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
+ aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
+ ? HTMLEditUtils::kNBSP
+ : HTMLEditUtils::kSpace);
+ return;
+ }
+
+ // Generate pairs of NBSP and ASCII white-space.
+ aResult.SetLength(aLength);
+ bool appendNBSP = true; // Basically, starts with an NBSP.
+ char16_t* lastChar = aResult.EndWriting() - 1;
+ for (char16_t* iter = aResult.BeginWriting(); iter != lastChar; iter++) {
+ *iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
+ appendNBSP = !appendNBSP;
+ }
+
+ // If the final one is expected to an NBSP, we can put an NBSP simply.
+ if (appendNBSP) {
+ *lastChar = HTMLEditUtils::kNBSP;
+ return;
+ }
+
+ // If next char point is end of text node or an ASCII white-space, we need to
+ // put an NBSP.
+ *lastChar =
+ aNextCharPointData.AcrossTextNodeBoundary() ||
+ aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
+ ? HTMLEditUtils::kNBSP
+ : HTMLEditUtils::kSpace;
+}
+
+void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
+ EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
+ nsAString& aNormalizedWhiteSpacesInStartNode,
+ nsAString& aNormalizedWhiteSpacesInEndNode) const {
+ MOZ_ASSERT(aStartToDelete.IsSetAndValid());
+ MOZ_ASSERT(aEndToDelete.IsSetAndValid());
+ MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
+ MOZ_ASSERT(aNormalizedWhiteSpacesInStartNode.IsEmpty());
+ MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.IsEmpty());
+
+ // First, check whether there is surrounding white-spaces or not, and if there
+ // are, check whether they are collapsible or not. Note that we shouldn't
+ // touch white-spaces in different text nodes for performance, but we need
+ // adjacent text node's first or last character information in some cases.
+ EditorDOMPointInText precedingCharPoint =
+ WSRunScanner::GetPreviousEditableCharPoint(*this, aStartToDelete);
+ EditorDOMPointInText followingCharPoint =
+ WSRunScanner::GetInclusiveNextEditableCharPoint(*this, aEndToDelete);
+ // Blink-compat: Normalize white-spaces in first node only when not removing
+ // its last character or no text nodes follow the first node.
+ // If removing last character of first node and there are
+ // following text nodes, white-spaces in following text node are
+ // normalized instead.
+ const bool removingLastCharOfStartNode =
+ aStartToDelete.ContainerAsText() != aEndToDelete.ContainerAsText() ||
+ (aEndToDelete.IsEndOfContainer() && followingCharPoint.IsSet());
+ const bool maybeNormalizePrecedingWhiteSpaces =
+ !removingLastCharOfStartNode && precedingCharPoint.IsSet() &&
+ !precedingCharPoint.IsEndOfContainer() &&
+ precedingCharPoint.ContainerAsText() ==
+ aStartToDelete.ContainerAsText() &&
+ precedingCharPoint.IsCharASCIISpaceOrNBSP() &&
+ !EditorUtils::IsContentPreformatted(
+ *precedingCharPoint.ContainerAsText());
+ const bool maybeNormalizeFollowingWhiteSpaces =
+ followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
+ (followingCharPoint.ContainerAsText() == aEndToDelete.ContainerAsText() ||
+ removingLastCharOfStartNode) &&
+ followingCharPoint.IsCharASCIISpaceOrNBSP() &&
+ !EditorUtils::IsContentPreformatted(
+ *followingCharPoint.ContainerAsText());
+
+ if (!maybeNormalizePrecedingWhiteSpaces &&
+ !maybeNormalizeFollowingWhiteSpaces) {
+ return; // There are no white-spaces.
+ }
+
+ // Next, consider the range to normalize.
+ EditorDOMPointInText startToNormalize, endToNormalize;
+ if (maybeNormalizePrecedingWhiteSpaces) {
+ Maybe<uint32_t> previousCharOffsetOfWhiteSpaces =
+ HTMLEditUtils::GetPreviousCharOffsetExceptWhiteSpaces(
+ precedingCharPoint);
+ startToNormalize.Set(precedingCharPoint.ContainerAsText(),
+ previousCharOffsetOfWhiteSpaces.isSome()
+ ? previousCharOffsetOfWhiteSpaces.value() + 1
+ : 0);
+ MOZ_ASSERT(!startToNormalize.IsEndOfContainer());
+ }
+ if (maybeNormalizeFollowingWhiteSpaces) {
+ Maybe<uint32_t> nextCharOffsetOfWhiteSpaces =
+ HTMLEditUtils::GetInclusiveNextCharOffsetExceptWhiteSpaces(
+ followingCharPoint);
+ if (nextCharOffsetOfWhiteSpaces.isSome()) {
+ endToNormalize.Set(followingCharPoint.ContainerAsText(),
+ nextCharOffsetOfWhiteSpaces.value());
+ } else {
+ endToNormalize.SetToEndOf(followingCharPoint.ContainerAsText());
+ }
+ MOZ_ASSERT(!endToNormalize.IsStartOfContainer());
+ }
+
+ // Next, retrieve surrounding information of white-space sequence.
+ // If we're removing first text node's last character, we need to
+ // normalize white-spaces starts from another text node. In this case,
+ // we need to lie for avoiding assertion in GenerateWhiteSpaceSequence().
+ CharPointData previousCharPointData =
+ removingLastCharOfStartNode
+ ? CharPointData::InDifferentTextNode(CharPointType::TextEnd)
+ : GetPreviousCharPointDataForNormalizingWhiteSpaces(
+ startToNormalize.IsSet() ? startToNormalize : aStartToDelete);
+ CharPointData nextCharPointData =
+ GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
+ endToNormalize.IsSet() ? endToNormalize : aEndToDelete);
+
+ // Next, compute number of white-spaces in start/end node.
+ uint32_t lengthInStartNode = 0, lengthInEndNode = 0;
+ if (startToNormalize.IsSet()) {
+ MOZ_ASSERT(startToNormalize.ContainerAsText() ==
+ aStartToDelete.ContainerAsText());
+ lengthInStartNode = aStartToDelete.Offset() - startToNormalize.Offset();
+ MOZ_ASSERT(lengthInStartNode);
+ }
+ if (endToNormalize.IsSet()) {
+ lengthInEndNode =
+ endToNormalize.ContainerAsText() == aEndToDelete.ContainerAsText()
+ ? endToNormalize.Offset() - aEndToDelete.Offset()
+ : endToNormalize.Offset();
+ MOZ_ASSERT(lengthInEndNode);
+ // If we normalize white-spaces in a text node, we can replace all of them
+ // with one ReplaceTextTransaction.
+ if (endToNormalize.ContainerAsText() == aStartToDelete.ContainerAsText()) {
+ lengthInStartNode += lengthInEndNode;
+ lengthInEndNode = 0;
+ }
+ }
+
+ MOZ_ASSERT(lengthInStartNode + lengthInEndNode);
+
+ // Next, generate normalized white-spaces.
+ if (!lengthInEndNode) {
+ HTMLEditor::GenerateWhiteSpaceSequence(
+ aNormalizedWhiteSpacesInStartNode, lengthInStartNode,
+ previousCharPointData, nextCharPointData);
+ } else if (!lengthInStartNode) {
+ HTMLEditor::GenerateWhiteSpaceSequence(
+ aNormalizedWhiteSpacesInEndNode, lengthInEndNode, previousCharPointData,
+ nextCharPointData);
+ } else {
+ // For making `GenerateWhiteSpaceSequence()` simpler, we should create
+ // whole white-space sequence first, then, copy to the out params.
+ nsAutoString whiteSpaces;
+ HTMLEditor::GenerateWhiteSpaceSequence(
+ whiteSpaces, lengthInStartNode + lengthInEndNode, previousCharPointData,
+ nextCharPointData);
+ aNormalizedWhiteSpacesInStartNode =
+ Substring(whiteSpaces, 0, lengthInStartNode);
+ aNormalizedWhiteSpacesInEndNode = Substring(whiteSpaces, lengthInStartNode);
+ MOZ_ASSERT(aNormalizedWhiteSpacesInEndNode.Length() == lengthInEndNode);
+ }
+
+ // TODO: Shrink the replacing range and string as far as possible because
+ // this may run a lot, i.e., HTMLEditor creates ReplaceTextTransaction
+ // a lot for normalizing white-spaces. Then, each transaction shouldn't
+ // have all white-spaces every time because once it's normalized, we
+ // don't need to normalize all of the sequence again, but currently
+ // we do.
+
+ // Finally, extend the range.
+ if (startToNormalize.IsSet()) {
+ aStartToDelete = startToNormalize;
+ }
+ if (endToNormalize.IsSet()) {
+ aEndToDelete = endToNormalize;
+ }
+}
+
+Result<EditorDOMPoint, nsresult>
+HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces(
+ const EditorDOMPointInText& aStartToDelete,
+ const EditorDOMPointInText& aEndToDelete,
+ TreatEmptyTextNodes aTreatEmptyTextNodes,
+ DeleteDirection aDeleteDirection) {
+ MOZ_ASSERT(aStartToDelete.IsSetAndValid());
+ MOZ_ASSERT(aEndToDelete.IsSetAndValid());
+ MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
+
+ // Use nsString for these replacing string because we should avoid to copy
+ // the buffer from auto storange to ReplaceTextTransaction.
+ nsString normalizedWhiteSpacesInFirstNode, normalizedWhiteSpacesInLastNode;
+
+ // First, check whether we need to normalize white-spaces after deleting
+ // the given range.
+ EditorDOMPointInText startToDelete(aStartToDelete);
+ EditorDOMPointInText endToDelete(aEndToDelete);
+ ExtendRangeToDeleteWithNormalizingWhiteSpaces(
+ startToDelete, endToDelete, normalizedWhiteSpacesInFirstNode,
+ normalizedWhiteSpacesInLastNode);
+
+ // If extended range is still collapsed, i.e., the caller just wants to
+ // normalize white-space sequence, but there is no white-spaces which need to
+ // be replaced, we need to do nothing here.
+ if (startToDelete == endToDelete) {
+ return EditorDOMPoint(aStartToDelete);
+ }
+
+ // Note that the container text node of startToDelete may be removed from
+ // the tree if it becomes empty. Therefore, we need to track the point.
+ EditorDOMPoint newCaretPosition;
+ if (aStartToDelete.ContainerAsText() == aEndToDelete.ContainerAsText()) {
+ newCaretPosition = aEndToDelete;
+ } else if (aDeleteDirection == DeleteDirection::Forward) {
+ newCaretPosition.SetToEndOf(aStartToDelete.ContainerAsText());
+ } else {
+ newCaretPosition.Set(aEndToDelete.ContainerAsText(), 0);
+ }
+
+ // Then, modify the text nodes in the range.
+ while (true) {
+ AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
+ &newCaretPosition);
+ // Use ReplaceTextTransaction if we need to normalize white-spaces in
+ // the first text node.
+ if (!normalizedWhiteSpacesInFirstNode.IsEmpty()) {
+ EditorDOMPoint trackingEndToDelete(endToDelete.ContainerAsText(),
+ endToDelete.Offset());
+ {
+ AutoTrackDOMPoint trackEndToDelete(RangeUpdaterRef(),
+ &trackingEndToDelete);
+ uint32_t lengthToReplaceInFirstTextNode =
+ startToDelete.ContainerAsText() ==
+ trackingEndToDelete.ContainerAsText()
+ ? trackingEndToDelete.Offset() - startToDelete.Offset()
+ : startToDelete.ContainerAsText()->TextLength() -
+ startToDelete.Offset();
+ nsresult rv = ReplaceTextWithTransaction(
+ MOZ_KnownLive(*startToDelete.ContainerAsText()),
+ startToDelete.Offset(), lengthToReplaceInFirstTextNode,
+ normalizedWhiteSpacesInFirstNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
+ return Err(rv);
+ }
+ if (startToDelete.ContainerAsText() ==
+ trackingEndToDelete.ContainerAsText()) {
+ MOZ_ASSERT(normalizedWhiteSpacesInLastNode.IsEmpty());
+ break; // There is no more text which we need to delete.
+ }
+ }
+ if (MayHaveMutationEventListeners(
+ NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
+ (NS_WARN_IF(!trackingEndToDelete.IsSetAndValid()) ||
+ NS_WARN_IF(!trackingEndToDelete.IsInTextNode()))) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+ MOZ_ASSERT(trackingEndToDelete.IsInTextNode());
+ endToDelete.Set(trackingEndToDelete.ContainerAsText(),
+ trackingEndToDelete.Offset());
+ // If the remaining range was modified by mutation event listener,
+ // we should stop handling the deletion.
+ startToDelete =
+ EditorDOMPointInText::AtEndOf(*startToDelete.ContainerAsText());
+ if (MayHaveMutationEventListeners(
+ NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED) &&
+ NS_WARN_IF(!startToDelete.IsBefore(endToDelete))) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+ }
+ // Delete ASCII whiteSpaces in the range simpley if there are some text
+ // nodes which we don't need to replace their text.
+ if (normalizedWhiteSpacesInLastNode.IsEmpty() ||
+ startToDelete.ContainerAsText() != endToDelete.ContainerAsText()) {
+ // If we need to replace text in the last text node, we should
+ // delete text before its previous text node.
+ EditorDOMPointInText endToDeleteExceptReplaceRange =
+ normalizedWhiteSpacesInLastNode.IsEmpty()
+ ? endToDelete
+ : EditorDOMPointInText(endToDelete.ContainerAsText(), 0);
+ if (startToDelete != endToDeleteExceptReplaceRange) {
+ nsresult rv = DeleteTextAndTextNodesWithTransaction(
+ startToDelete, endToDeleteExceptReplaceRange, aTreatEmptyTextNodes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
+ return Err(rv);
+ }
+ if (normalizedWhiteSpacesInLastNode.IsEmpty()) {
+ break; // There is no more text which we need to delete.
+ }
+ if (MayHaveMutationEventListeners(
+ NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED |
+ NS_EVENT_BITS_MUTATION_NODEREMOVED |
+ NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
+ NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED) &&
+ (NS_WARN_IF(!endToDeleteExceptReplaceRange.IsSetAndValid()) ||
+ NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
+ NS_WARN_IF(endToDelete.IsStartOfContainer()))) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+ // Then, replace the text in the last text node.
+ startToDelete = endToDeleteExceptReplaceRange;
+ }
+ }
+
+ // Replace ASCII whiteSpaces in the range and following character in the
+ // last text node.
+ MOZ_ASSERT(!normalizedWhiteSpacesInLastNode.IsEmpty());
+ MOZ_ASSERT(startToDelete.ContainerAsText() ==
+ endToDelete.ContainerAsText());
+ nsresult rv = ReplaceTextWithTransaction(
+ MOZ_KnownLive(*startToDelete.ContainerAsText()), startToDelete.Offset(),
+ endToDelete.Offset() - startToDelete.Offset(),
+ normalizedWhiteSpacesInLastNode);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
+ return Err(rv);
+ }
+ break;
+ }
+
+ if (!newCaretPosition.IsSetAndValid() ||
+ !newCaretPosition.GetContainer()->IsInComposedDoc()) {
+ NS_WARNING(
+ "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() got lost "
+ "the modifying line");
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ // Look for leaf node to put caret if we remove some empty inline ancestors
+ // at new caret position.
+ if (!newCaretPosition.IsInTextNode()) {
+ if (nsIContent* currentBlock = HTMLEditUtils::
+ GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
+ *newCaretPosition.ContainerAsContent())) {
+ Element* editingHost = GetActiveEditingHost();
+ // Try to put caret next to immediately after previous editable leaf.
+ nsIContent* previousContent =
+ HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
+ newCaretPosition, *currentBlock, editingHost);
+ if (previousContent && !HTMLEditUtils::IsBlockElement(*previousContent)) {
+ newCaretPosition =
+ previousContent->IsText() ||
+ HTMLEditUtils::IsContainerNode(*previousContent)
+ ? EditorDOMPoint::AtEndOf(*previousContent)
+ : EditorDOMPoint::After(*previousContent);
+ }
+ // But if the point is very first of a block element or immediately after
+ // a child block, look for next editable leaf instead.
+ else if (nsIContent* nextContent =
+ HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
+ newCaretPosition, *currentBlock, editingHost)) {
+ newCaretPosition = nextContent->IsText() ||
+ HTMLEditUtils::IsContainerNode(*nextContent)
+ ? EditorDOMPoint(nextContent, 0)
+ : EditorDOMPoint(nextContent);
+ }
+ }
+ }
+
+ // For compatibility with Blink, we should move caret to end of previous
+ // text node if it's direct previous sibling of the first text node in the
+ // range.
+ if (newCaretPosition.IsStartOfContainer() &&
+ newCaretPosition.IsInTextNode() &&
+ newCaretPosition.GetContainer()->GetPreviousSibling() &&
+ newCaretPosition.GetContainer()->GetPreviousSibling()->IsText()) {
+ newCaretPosition.SetToEndOf(
+ newCaretPosition.GetContainer()->GetPreviousSibling()->AsText());
+ }
+
+ {
+ AutoTrackDOMPoint trackingNewCaretPosition(RangeUpdaterRef(),
+ &newCaretPosition);
+ nsresult rv = InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
+ newCaretPosition);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() failed");
+ return Err(rv);
+ }
+ }
+ if (!newCaretPosition.IsSetAndValid()) {
+ NS_WARNING("Inserting <br> element caused unexpected DOM tree");
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+ return newCaretPosition;
+}
+
+nsresult HTMLEditor::InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary(
+ const EditorDOMPoint& aPointToInsert) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aPointToInsert.IsSet());
+
+ if (!aPointToInsert.GetContainerAsContent()) {
+ return NS_OK;
+ }
+
+ // If container of the point is not in a block, we don't need to put a
+ // `<br>` element here.
+ if (!HTMLEditUtils::IsBlockElement(*aPointToInsert.ContainerAsContent())) {
+ return NS_OK;
+ }
+
+ WSRunScanner wsRunScanner(*this, aPointToInsert);
+ // If the point is not start of a hard line, we don't need to put a `<br>`
+ // element here.
+ if (!wsRunScanner.StartsFromHardLineBreak()) {
+ return NS_OK;
+ }
+ // If the point is not end of a hard line or the hard line does not end with
+ // block boundary, we don't need to put a `<br>` element here.
+ if (!wsRunScanner.EndsByBlockBoundary()) {
+ return NS_OK;
+ }
+
+ // If we cannot insert a `<br>` element here, do nothing.
+ if (!HTMLEditUtils::CanNodeContain(*aPointToInsert.GetContainer(),
+ *nsGkAtoms::br)) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(aPointToInsert, nsIEditor::ePrevious);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(brElement,
+ "HTMLEditor::InsertBRElementWithTransaction() failed");
+ return brElement ? NS_OK : NS_ERROR_FAILURE;
+}
+
+EditorDOMPoint HTMLEditor::GetGoodCaretPointFor(
+ nsIContent& aContent, nsIEditor::EDirection aDirectionAndAmount) const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aDirectionAndAmount == nsIEditor::eNext ||
+ aDirectionAndAmount == nsIEditor::eNextWord ||
+ aDirectionAndAmount == nsIEditor::ePrevious ||
+ aDirectionAndAmount == nsIEditor::ePreviousWord ||
+ aDirectionAndAmount == nsIEditor::eToBeginningOfLine ||
+ aDirectionAndAmount == nsIEditor::eToEndOfLine);
+
+ bool goingForward = (aDirectionAndAmount == nsIEditor::eNext ||
+ aDirectionAndAmount == nsIEditor::eNextWord ||
+ aDirectionAndAmount == nsIEditor::eToEndOfLine);
+
+ // XXX Why don't we check whether the candidate position is enable or not?
+ // When the result is not editable point, caret will be enclosed in
+ // the non-editable content.
+
+ // If we can put caret in aContent, return start or end in it.
+ if (aContent.IsText() || HTMLEditUtils::IsContainerNode(aContent) ||
+ NS_WARN_IF(!aContent.GetParentNode())) {
+ return EditorDOMPoint(&aContent, goingForward ? 0 : aContent.Length());
+ }
+
+ // If we are going forward, put caret at aContent itself.
+ if (goingForward) {
+ return EditorDOMPoint(&aContent);
+ }
+
+ // If we are going backward, put caret to next node unless aContent is an
+ // invisible `<br>` element.
+ // XXX Shouldn't we put caret to first leaf of the next node?
+ if (!aContent.IsHTMLElement(nsGkAtoms::br) || IsVisibleBRElement(&aContent)) {
+ EditorDOMPoint ret(EditorDOMPoint::After(aContent));
+ NS_WARNING_ASSERTION(ret.IsSet(), "Failed to set after aContent");
+ return ret;
+ }
+
+ // Otherwise, we should put caret at the invisible `<br>` element.
+ return EditorDOMPoint(&aContent);
+}
+
+EditActionResult HTMLEditor::MakeOrChangeListAndListItemAsSubAction(
+ nsAtom& aListElementOrListItemElementTagName, const nsAString& aBulletType,
+ SelectAllOfCurrentList aSelectAllOfCurrentList) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::ol ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::dl ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::dt);
+
+ if (NS_WARN_IF(!mInitSucceeded)) {
+ return EditActionIgnored(NS_ERROR_NOT_INITIALIZED);
+ }
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Some selection containers are not content node, but ignored");
+ return EditActionIgnored();
+ }
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+
+ // XXX EditSubAction::eCreateOrChangeDefinitionListItem and
+ // EditSubAction::eCreateOrChangeList are treated differently in
+ // HTMLEditor::MaybeSplitElementsAtEveryBRElement(). Only when
+ // EditSubAction::eCreateOrChangeList, it splits inline nodes.
+ // Currently, it shouldn't be done when we called for formatting
+ // `<dd>` or `<dt>` by
+ // HTMLEditor::MakeDefinitionListItemWithTransaction(). But this
+ // difference may be a bug. We should investigate this later.
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this,
+ &aListElementOrListItemElementTagName == nsGkAtoms::dd ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::dt
+ ? EditSubAction::eCreateOrChangeDefinitionListItem
+ : EditSubAction::eCreateOrChangeList,
+ nsIEditor::eNext, ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ nsAtom* listTagName = nullptr;
+ nsAtom* listItemTagName = nullptr;
+ if (&aListElementOrListItemElementTagName == nsGkAtoms::ul ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::ol) {
+ listTagName = &aListElementOrListItemElementTagName;
+ listItemTagName = nsGkAtoms::li;
+ } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dl) {
+ listTagName = &aListElementOrListItemElementTagName;
+ listItemTagName = nsGkAtoms::dd;
+ } else if (&aListElementOrListItemElementTagName == nsGkAtoms::dd ||
+ &aListElementOrListItemElementTagName == nsGkAtoms::dt) {
+ listTagName = nsGkAtoms::dl;
+ listItemTagName = &aListElementOrListItemElementTagName;
+ } else {
+ NS_WARNING(
+ "aListElementOrListItemElementTagName was neither list element name "
+ "nor "
+ "definition listitem element name");
+ return EditActionResult(NS_ERROR_INVALID_ARG);
+ }
+
+ // Expands selection range to include the immediate block parent, and then
+ // further expands to include any ancestors whose children are all in the
+ // range.
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return EditActionResult(rv);
+ }
+ }
+
+ // ChangeSelectedHardLinesToList() creates AutoSelectionRestorer.
+ // Therefore, even if it returns NS_OK, editor might have been destroyed
+ // at restoring Selection.
+ result = ChangeSelectedHardLinesToList(MOZ_KnownLive(*listTagName),
+ MOZ_KnownLive(*listItemTagName),
+ aBulletType, aSelectAllOfCurrentList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::ChangeSelectedHardLinesToList() failed");
+ return result;
+}
+
+EditActionResult HTMLEditor::ChangeSelectedHardLinesToList(
+ nsAtom& aListElementTagName, nsAtom& aListItemElementTagName,
+ const nsAString& aBulletType,
+ SelectAllOfCurrentList aSelectAllOfCurrentList) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ Element* parentListElement =
+ aSelectAllOfCurrentList == SelectAllOfCurrentList::Yes
+ ? GetParentListElementAtSelection()
+ : nullptr;
+ if (parentListElement) {
+ arrayOfContents.AppendElement(
+ OwningNonNull<nsIContent>(*parentListElement));
+ } else {
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+ nsresult rv =
+ SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eCreateOrChangeList,
+ CollectNonEditableNodes::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
+ "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
+ return EditActionResult(rv);
+ }
+ }
+
+ // check if all our nodes are <br>s, or empty inlines
+ bool bOnlyBreaks = true;
+ for (auto& content : arrayOfContents) {
+ // if content is not a Break or empty inline, we're done
+ if (!content->IsHTMLElement(nsGkAtoms::br) && !IsEmptyInlineNode(content)) {
+ bOnlyBreaks = false;
+ break;
+ }
+ }
+
+ // if no nodes, we make empty list. Ditto if the user tried to make a list
+ // of some # of breaks.
+ if (arrayOfContents.IsEmpty() || bOnlyBreaks) {
+ // if only breaks, delete them
+ if (bOnlyBreaks) {
+ for (auto& content : arrayOfContents) {
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ }
+ }
+
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ // Make sure we can put a list here.
+ if (!HTMLEditUtils::CanNodeContain(*atStartOfSelection.GetContainer(),
+ aListElementTagName)) {
+ return EditActionCanceled();
+ }
+
+ SplitNodeResult splitAtSelectionStartResult =
+ MaybeSplitAncestorsForInsertWithTransaction(aListElementTagName,
+ atStartOfSelection);
+ if (splitAtSelectionStartResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return EditActionResult(splitAtSelectionStartResult.Rv());
+ }
+ RefPtr<Element> theList = CreateNodeWithTransaction(
+ aListElementTagName, splitAtSelectionStartResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!theList) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ RefPtr<Element> theListItem = CreateNodeWithTransaction(
+ aListItemElementTagName, EditorDOMPoint(theList, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!theListItem) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = theListItem;
+ // Put selection in new list item and don't restore the Selection.
+ restoreSelectionLater.Abort();
+ nsresult rv = CollapseSelectionToStartOf(*theListItem);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return EditActionResult(rv);
+ }
+
+ // if there is only one node in the array, and it is a list, div, or
+ // blockquote, then look inside of it until we find inner list or content.
+ if (arrayOfContents.Length() == 1) {
+ if (Element* deepestDivBlockquoteOrListElement =
+ GetDeepestEditableOnlyChildDivBlockquoteOrListElement(
+ arrayOfContents[0])) {
+ if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
+ nsGkAtoms::div, nsGkAtoms::blockquote)) {
+ arrayOfContents.Clear();
+ CollectChildren(*deepestDivBlockquoteOrListElement, arrayOfContents, 0,
+ CollectListChildren::No, CollectTableChildren::No,
+ CollectNonEditableNodes::Yes);
+ } else {
+ arrayOfContents.ReplaceElementAt(
+ 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
+ }
+ }
+ }
+
+ // Ok, now go through all the nodes and put then in the list,
+ // or whatever is approriate. Wohoo!
+
+ uint32_t countOfCollectedContents = arrayOfContents.Length();
+ RefPtr<Element> curList, prevListItem;
+
+ for (uint32_t i = 0; i < countOfCollectedContents; i++) {
+ // here's where we actually figure out what to do
+ OwningNonNull<nsIContent> content = arrayOfContents[i];
+
+ // make sure we don't assemble content that is in different table cells
+ // into the same list. respect table cell boundaries when listifying.
+ if (curList &&
+ HTMLEditor::NodesInDifferentTableElements(*curList, content)) {
+ curList = nullptr;
+ }
+
+ // If current node is a `<br>` element, delete it and forget previous
+ // list item element.
+ // If current node is an empty inline node, just delete it.
+ if (EditorUtils::IsEditableContent(content, EditorType::HTML) &&
+ (content->IsHTMLElement(nsGkAtoms::br) || IsEmptyInlineNode(content))) {
+ nsresult rv = DeleteNodeWithTransaction(*content);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ if (content->IsHTMLElement(nsGkAtoms::br)) {
+ prevListItem = nullptr;
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(content)) {
+ // If we met a list element and current list element is not a descendant
+ // of the list, append current node to end of the current list element.
+ // Then, wrap it with list item element and delete the old container.
+ if (curList && !EditorUtils::IsDescendantOf(*content, *curList)) {
+ nsresult rv = MoveNodeToEndWithTransaction(*content, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ CreateElementResult convertListTypeResult =
+ ChangeListElementType(MOZ_KnownLive(*content->AsElement()),
+ aListElementTagName, aListItemElementTagName);
+ if (convertListTypeResult.Failed()) {
+ NS_WARNING("HTMLEditor::ChangeListElementType() failed");
+ return EditActionResult(convertListTypeResult.Rv());
+ }
+ rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*convertListTypeResult.GetNewNode()));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ prevListItem = nullptr;
+ continue;
+ }
+
+ // If current list element is in found list element or we've not met a
+ // list element, convert current list element to proper type.
+ CreateElementResult convertListTypeResult =
+ ChangeListElementType(MOZ_KnownLive(*content->AsElement()),
+ aListElementTagName, aListItemElementTagName);
+ if (convertListTypeResult.Failed()) {
+ NS_WARNING("HTMLEditor::ChangeListElementType() failed");
+ return EditActionResult(convertListTypeResult.Rv());
+ }
+ curList = convertListTypeResult.forget();
+ prevListItem = nullptr;
+ continue;
+ }
+
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(atContent.IsSetAndValid());
+ if (HTMLEditUtils::IsListItem(content)) {
+ // If current list item element is not in proper list element, we need
+ // to conver the list element.
+ if (!atContent.IsContainerHTMLElement(&aListElementTagName)) {
+ // If we've not met a list element or current node is not in current
+ // list element, insert a list element at current node and set
+ // current list element to the new one.
+ if (!curList || EditorUtils::IsDescendantOf(*content, *curList)) {
+ if (NS_WARN_IF(!atContent.GetContainerAsContent())) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ ErrorResult error;
+ nsCOMPtr<nsIContent> newLeftNode =
+ SplitNodeWithTransaction(atContent, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error.SuppressException();
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (error.Failed()) {
+ NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
+ return EditActionResult(error.StealNSResult());
+ }
+ curList = CreateNodeWithTransaction(
+ aListElementTagName, EditorDOMPoint(atContent.GetContainer()));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!curList) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ }
+ // Then, move current node into current list element.
+ nsresult rv = MoveNodeToEndWithTransaction(*content, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ // Convert list item type if current node is different list item type.
+ if (!content->IsHTMLElement(&aListItemElementTagName)) {
+ RefPtr<Element> newListItemElement = ReplaceContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()), aListItemElementTagName);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!newListItemElement) {
+ NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ }
+ } else {
+ // If we've not met a list element, set current list element to the
+ // parent of current list item element.
+ if (!curList) {
+ curList = atContent.GetContainerAsElement();
+ NS_WARNING_ASSERTION(
+ HTMLEditUtils::IsAnyListElement(curList),
+ "Current list item parent is not a list element");
+ }
+ // If current list item element is not a child of current list element,
+ // move it into current list item.
+ else if (atContent.GetContainer() != curList) {
+ nsresult rv = MoveNodeToEndWithTransaction(*content, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ }
+ // Then, if current list item element is not proper type for current
+ // list element, convert list item element to proper element.
+ if (!content->IsHTMLElement(&aListItemElementTagName)) {
+ RefPtr<Element> newListItemElement = ReplaceContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()), aListItemElementTagName);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!newListItemElement) {
+ NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ }
+ }
+ Element* element = Element::FromNode(content);
+ if (NS_WARN_IF(!element)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ // If bullet type is specified, set list type attribute.
+ // XXX Cannot we set type attribute before inserting the list item
+ // element into the DOM tree?
+ if (!aBulletType.IsEmpty()) {
+ nsresult rv = SetAttributeWithTransaction(
+ MOZ_KnownLive(*element), *nsGkAtoms::type, aBulletType);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::SetAttributeWithTransaction(nsGkAtoms::type) "
+ "failed");
+ return EditActionResult(rv);
+ }
+ continue;
+ }
+
+ // Otherwise, remove list type attribute if there is.
+ if (!element->HasAttr(nsGkAtoms::type)) {
+ continue;
+ }
+ nsresult rv = RemoveAttributeWithTransaction(MOZ_KnownLive(*element),
+ *nsGkAtoms::type);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::type) "
+ "failed");
+ return EditActionResult(rv);
+ }
+ continue;
+ }
+
+ MOZ_ASSERT(!HTMLEditUtils::IsAnyListElement(content) &&
+ !HTMLEditUtils::IsListItem(content));
+
+ // If current node is a `<div>` element, replace it in the array with
+ // its children.
+ // XXX I think that this should be done when we collect the nodes above.
+ // Then, we can change this `for` loop to ranged-for loop.
+ if (content->IsHTMLElement(nsGkAtoms::div)) {
+ prevListItem = nullptr;
+ CollectChildren(*content, arrayOfContents, i + 1,
+ CollectListChildren::Yes, CollectTableChildren::Yes,
+ CollectNonEditableNodes::Yes);
+ nsresult rv =
+ RemoveContainerWithTransaction(MOZ_KnownLive(*content->AsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ // Extend the loop length to handle all children collected here.
+ countOfCollectedContents = arrayOfContents.Length();
+ continue;
+ }
+
+ // If we've not met a list element, create a list element and make it
+ // current list element.
+ if (!curList) {
+ SplitNodeResult splitCurNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(aListElementTagName,
+ atContent);
+ if (splitCurNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return EditActionResult(splitCurNodeResult.Rv());
+ }
+ prevListItem = nullptr;
+ curList = CreateNodeWithTransaction(aListElementTagName,
+ splitCurNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!curList) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ // Set new block element of top level edit sub-action to the new list
+ // element for setting selection into it.
+ // XXX This must be wrong. If we're handling nested edit action,
+ // we shouldn't overwrite the new block element.
+ TopLevelEditSubActionDataRef().mNewBlockElement = curList;
+
+ // atContent is now referring the right node with mOffset but
+ // referring the left node with mRef. So, invalidate it now.
+ atContent.Clear();
+ }
+
+ // If we're currently handling contents of a list item and current node
+ // is not a block element, move current node into the list item.
+ if (HTMLEditUtils::IsInlineElement(content) && prevListItem) {
+ nsresult rv = MoveNodeToEndWithTransaction(*content, *prevListItem);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ continue;
+ }
+
+ // If current node is a paragraph, that means that it does not contain
+ // block children so that we can just replace it with new list item
+ // element and move it into current list element.
+ // XXX This is too rough handling. If web apps modifies DOM tree directly,
+ // any elements can have block elements as children.
+ if (content->IsHTMLElement(nsGkAtoms::p)) {
+ RefPtr<Element> newListItemElement = ReplaceContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()), aListItemElementTagName);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!newListItemElement) {
+ NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ prevListItem = nullptr;
+ nsresult rv = MoveNodeToEndWithTransaction(*newListItemElement, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ // XXX Why don't we set `type` attribute here??
+ continue;
+ }
+
+ // If current node is not a paragraph, wrap current node with new list
+ // item element and move it into current list element.
+ RefPtr<Element> newListItemElement =
+ InsertContainerWithTransaction(*content, aListItemElementTagName);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!newListItemElement) {
+ NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ // If current node is not a block element, new list item should have
+ // following inline nodes too.
+ if (HTMLEditUtils::IsInlineElement(content)) {
+ prevListItem = newListItemElement;
+ } else {
+ prevListItem = nullptr;
+ }
+ nsresult rv = MoveNodeToEndWithTransaction(*newListItemElement, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ // XXX Why don't we set `type` attribute here??
+ }
+
+ return EditActionHandled();
+}
+
+nsresult HTMLEditor::RemoveListAtSelectionAsSubAction() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result.Rv();
+ }
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eRemoveList, nsIEditor::eNext, ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return ignoredError.StealNSResult();
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return rv;
+ }
+ }
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ {
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+ nsresult rv =
+ SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eCreateOrChangeList,
+ CollectNonEditableNodes::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
+ "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
+ return rv;
+ }
+ }
+
+ // Remove all non-editable nodes. Leave them be.
+ // XXX SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges()
+ // should return only editable contents when it's called with
+ // CollectNonEditableNodes::No.
+ for (int32_t i = arrayOfContents.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsIContent>& content = arrayOfContents[i];
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ arrayOfContents.RemoveElementAt(i);
+ }
+ }
+
+ // Only act on lists or list items in the array
+ for (auto& content : arrayOfContents) {
+ // here's where we actually figure out what to do
+ if (HTMLEditUtils::IsListItem(content)) {
+ // unlist this listitem
+ nsresult rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
+ LiftUpFromAllParentListElements::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
+ ":Yes) failed");
+ return rv;
+ }
+ continue;
+ }
+ if (HTMLEditUtils::IsAnyListElement(content)) {
+ // node is a list, move list items out
+ nsresult rv =
+ DestroyListStructureRecursively(MOZ_KnownLive(*content->AsElement()));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
+ return rv;
+ }
+ continue;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return rv;
+ }
+ }
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eCreateOrRemoveBlock,
+ CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
+ "eCreateOrRemoveBlock, CollectNonEditableNodes::Yes) failed");
+ return rv;
+ }
+
+ // If there is no visible and editable nodes in the edit targets, make an
+ // empty block.
+ // XXX Isn't this odd if there are only non-editable visible nodes?
+ if (IsEmptyOneHardLine(arrayOfContents)) {
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorDOMPoint pointToInsertBlock(firstRange->StartRef());
+ if (&blockType == nsGkAtoms::normal || &blockType == nsGkAtoms::_empty) {
+ if (!pointToInsertBlock.IsInContentNode()) {
+ NS_WARNING(
+ "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
+ "block parent because container of the point is not content");
+ return NS_ERROR_FAILURE;
+ }
+ // We are removing blocks (going to "body text")
+ RefPtr<Element> blockElement =
+ HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *pointToInsertBlock.ContainerAsContent());
+ if (!blockElement) {
+ NS_WARNING(
+ "HTMLEditor::FormatBlockContainerWithTransaction() couldn't find "
+ "block parent");
+ return NS_ERROR_FAILURE;
+ }
+ if (!HTMLEditUtils::IsFormatNode(blockElement)) {
+ return NS_OK;
+ }
+
+ // If the first editable node after selection is a br, consume it.
+ // Otherwise it gets pushed into a following block after the split,
+ // which is visually bad.
+ nsCOMPtr<nsIContent> brContent =
+ GetNextEditableHTMLNode(pointToInsertBlock);
+ if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) {
+ AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
+ rv = DeleteNodeWithTransaction(*brContent);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+ // Do the splits!
+ SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
+ *blockElement, pointToInsertBlock,
+ SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitNodeResult.Failed()) {
+ NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+ EditorDOMPoint pointToInsertBRNode(splitNodeResult.SplitPoint());
+ // Put a <br> element at the split point
+ brContent = InsertBRElementWithTransaction(pointToInsertBRNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!brContent) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // Don't restore the selection
+ restoreSelectionLater.Abort();
+ // Put selection at the split point
+ nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(brContent));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+
+ // We are making a block. Consume a br, if needed.
+ nsCOMPtr<nsIContent> brNode =
+ GetNextEditableHTMLNodeInBlock(pointToInsertBlock);
+ if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
+ AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
+ rv = DeleteNodeWithTransaction(*brNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ // We don't need to act on this node any more
+ arrayOfContents.RemoveElement(brNode);
+ }
+ // Make sure we can put a block here.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(blockType,
+ pointToInsertBlock);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+ RefPtr<Element> block =
+ CreateNodeWithTransaction(blockType, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!block) {
+ NS_WARNING("CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = block;
+ // Delete anything that was in the list of nodes
+ while (!arrayOfContents.IsEmpty()) {
+ OwningNonNull<nsIContent>& content = arrayOfContents[0];
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ arrayOfContents.RemoveElementAt(0);
+ }
+ // Don't restore the selection
+ restoreSelectionLater.Abort();
+ // Put selection in new block
+ rv = CollapseSelectionToStartOf(*block);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+ // Okay, now go through all the nodes and make the right kind of blocks, or
+ // whatever is approriate. Woohoo! Note: blockquote is handled a little
+ // differently.
+ if (&blockType == nsGkAtoms::blockquote) {
+ nsresult rv = MoveNodesIntoNewBlockquoteElement(arrayOfContents);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::MoveNodesIntoNewBlockquoteElement() failed");
+ return rv;
+ }
+ if (&blockType == nsGkAtoms::normal || &blockType == nsGkAtoms::_empty) {
+ nsresult rv = RemoveBlockContainerElements(arrayOfContents);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::RemoveBlockContainerElements() failed");
+ return rv;
+ }
+ rv = CreateOrChangeBlockContainerElement(arrayOfContents, blockType);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ return NS_OK;
+ }
+
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+ const RangeBoundary& atStartOfSelection = firstRange->StartRef();
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!atStartOfSelection.Container()->IsElement()) {
+ return NS_OK;
+ }
+ OwningNonNull<Element> startContainerElement =
+ *atStartOfSelection.Container()->AsElement();
+ nsresult rv =
+ InsertPaddingBRElementForEmptyLastLineIfNeeded(startContainerElement);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::IndentAsSubAction() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eIndent, nsIEditor::eNext, ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Some selection containers are not content node, but ignored");
+ return EditActionIgnored();
+ }
+
+ result |= HandleIndentAtSelection();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::HandleIndentAtSelection() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() failed");
+ return result.SetResult(rv);
+}
+
+// Helper for Handle[CSS|HTML]IndentAtSelectionInternal
+nsresult HTMLEditor::IndentListChild(RefPtr<Element>* aCurList,
+ const EditorDOMPoint& aCurPoint,
+ nsIContent& aContent) {
+ MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(aCurPoint.GetContainer()),
+ "unexpected container");
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ // some logic for putting list items into nested lists...
+
+ // Check for whether we should join a list that follows aContent.
+ // We do this if the next element is a list, and the list is of the
+ // same type (li/ol) as aContent was a part it.
+ if (nsIContent* nextEditableSibling =
+ GetNextHTMLSibling(&aContent, SkipWhiteSpace::Yes)) {
+ if (HTMLEditUtils::IsAnyListElement(nextEditableSibling) &&
+ aCurPoint.GetContainer()->NodeInfo()->NameAtom() ==
+ nextEditableSibling->NodeInfo()->NameAtom() &&
+ aCurPoint.GetContainer()->NodeInfo()->NamespaceID() ==
+ nextEditableSibling->NodeInfo()->NamespaceID()) {
+ nsresult rv = MoveNodeWithTransaction(
+ aContent, EditorDOMPoint(nextEditableSibling, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EdigtorBase::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ // Check for whether we should join a list that preceeds aContent.
+ // We do this if the previous element is a list, and the list is of
+ // the same type (li/ol) as aContent was a part of.
+ if (nsCOMPtr<nsIContent> previousEditableSibling =
+ GetPriorHTMLSibling(&aContent, SkipWhiteSpace::Yes)) {
+ if (HTMLEditUtils::IsAnyListElement(previousEditableSibling) &&
+ aCurPoint.GetContainer()->NodeInfo()->NameAtom() ==
+ previousEditableSibling->NodeInfo()->NameAtom() &&
+ aCurPoint.GetContainer()->NodeInfo()->NamespaceID() ==
+ previousEditableSibling->NodeInfo()->NamespaceID()) {
+ nsresult rv =
+ MoveNodeToEndWithTransaction(aContent, *previousEditableSibling);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ // check to see if aCurList is still appropriate. Which it is if
+ // aContent is still right after it in the same list.
+ nsIContent* previousEditableSibling =
+ *aCurList ? GetPriorHTMLSibling(&aContent, SkipWhiteSpace::Yes) : nullptr;
+ if (!*aCurList ||
+ (previousEditableSibling && previousEditableSibling != *aCurList)) {
+ nsAtom* containerName = aCurPoint.GetContainer()->NodeInfo()->NameAtom();
+ // Create a new nested list of correct type.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(
+ MOZ_KnownLive(*containerName), aCurPoint);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+ *aCurList = CreateNodeWithTransaction(MOZ_KnownLive(*containerName),
+ splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!*aCurList) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // aCurList is now the correct thing to put aContent in
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = *aCurList;
+ }
+ // tuck the node into the end of the active list
+ RefPtr<nsINode> container = *aCurList;
+ nsresult rv = MoveNodeToEndWithTransaction(aContent, *container);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::HandleIndentAtSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed the selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ if (IsCSSEnabled()) {
+ nsresult rv = HandleCSSIndentAtSelection();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleCSSIndentAtSelection() failed");
+ return EditActionHandled(rv);
+ }
+ rv = HandleHTMLIndentAtSelection();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleHTMLIndent() failed");
+ return EditActionHandled(rv);
+}
+
+nsresult HTMLEditor::HandleCSSIndentAtSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return rv;
+ }
+ }
+
+ // HandleCSSIndentAtSelectionInternal() creates AutoSelectionRestorer.
+ // Therefore, even if it returns NS_OK, editor might have been destroyed
+ // at restoring Selection.
+ nsresult rv = HandleCSSIndentAtSelectionInternal();
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleCSSIndentAtSelectionInternal() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::HandleCSSIndentAtSelectionInternal() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+
+ // short circuit: detect case of collapsed selection inside an <li>.
+ // just sublist that <li>. This prevents bug 97797.
+
+ if (SelectionRefPtr()->IsCollapsed()) {
+ EditorRawDOMPoint atCaret(EditorBase::GetStartPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!atCaret.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(atCaret.IsInContentNode());
+ Element* blockElement = HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *atCaret.ContainerAsContent());
+ if (blockElement && HTMLEditUtils::IsListItem(blockElement)) {
+ arrayOfContents.AppendElement(*blockElement);
+ }
+ }
+
+ if (arrayOfContents.IsEmpty()) {
+ nsresult rv =
+ SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eIndent,
+ CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
+ "eIndent, CollectNonEditableNodes::Yes) failed");
+ return rv;
+ }
+ }
+
+ // If there is no visible and editable nodes in the edit targets, make an
+ // empty block.
+ // XXX Isn't this odd if there are only non-editable visible nodes?
+ if (IsEmptyOneHardLine(arrayOfContents)) {
+ // get selection location
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure we can put a block here
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
+ atStartOfSelection);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms::"
+ "div) failed");
+ return splitNodeResult.Rv();
+ }
+ RefPtr<Element> theBlock = CreateNodeWithTransaction(
+ *nsGkAtoms::div, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!theBlock) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = theBlock;
+ nsresult rv = ChangeMarginStart(*theBlock, ChangeMargin::Increase);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::ChangeMarginStart() failed, but ignored");
+ // delete anything that was in the list of nodes
+ // XXX We don't need to remove the nodes from the array for performance.
+ while (!arrayOfContents.IsEmpty()) {
+ OwningNonNull<nsIContent>& content = arrayOfContents[0];
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ arrayOfContents.RemoveElementAt(0);
+ }
+ // Don't restore the selection
+ restoreSelectionLater.Abort();
+ // put selection in new block
+ rv = CollapseSelectionToStartOf(*theBlock);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ // Ok, now go through all the nodes and put them in a blockquote,
+ // or whatever is appropriate.
+ RefPtr<Element> curList, curQuote;
+ for (OwningNonNull<nsIContent>& content : arrayOfContents) {
+ // Here's where we actually figure out what to do.
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ continue;
+ }
+
+ // Ignore all non-editable nodes. Leave them be.
+ // XXX We ignore non-editable nodes here, but not so in the above block.
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ nsresult rv =
+ IndentListChild(&curList, atContent, MOZ_KnownLive(content));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::IndentListChild() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ // Not a list item.
+
+ if (HTMLEditUtils::IsBlockElement(content)) {
+ nsresult rv = ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
+ ChangeMargin::Increase);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::ChangeMarginStart() failed, but ignored");
+ curQuote = nullptr;
+ continue;
+ }
+
+ if (!curQuote) {
+ // First, check that our element can contain a div.
+ if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
+ *nsGkAtoms::div)) {
+ return NS_OK; // cancelled
+ }
+
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
+ atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms:"
+ ":div) failed");
+ return splitNodeResult.Rv();
+ }
+ curQuote = CreateNodeWithTransaction(*nsGkAtoms::div,
+ splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curQuote) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = ChangeMarginStart(*curQuote, ChangeMargin::Increase);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::ChangeMarginStart() failed, but ignored");
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = curQuote;
+ // curQuote is now the correct thing to put content in
+ }
+
+ // tuck the node into the end of the active blockquote
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ nsresult rv =
+ MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curQuote);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::HandleHTMLIndentAtSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return rv;
+ }
+ }
+
+ // HandleHTMLIndentAtSelectionInternal() creates AutoSelectionRestorer.
+ // Therefore, even if it returns NS_OK, editor might have been destroyed
+ // at restoring Selection.
+ nsresult rv = HandleHTMLIndentAtSelectionInternal();
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::HandleHTMLIndentAtSelectionInternal() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::HandleHTMLIndentAtSelectionInternal() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ // convert the selection ranges into "promoted" selection ranges:
+ // this basically just expands the range to include the immediate
+ // block parent, and then further expands to include any ancestors
+ // whose children are all in the range
+
+ AutoTArray<RefPtr<nsRange>, 4> arrayOfRanges;
+ GetSelectionRangesExtendedToHardLineStartAndEnd(arrayOfRanges,
+ EditSubAction::eIndent);
+
+ // use these ranges to construct a list of nodes to act on.
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ nsresult rv = SplitInlinesAndCollectEditTargetNodes(
+ arrayOfRanges, arrayOfContents, EditSubAction::eIndent,
+ CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::SplitInlinesAndCollectEditTargetNodes(eIndent, "
+ "CollectNonEditableNodes::Yes) failed");
+ return rv;
+ }
+
+ // If there is no visible and editable nodes in the edit targets, make an
+ // empty block.
+ // XXX Isn't this odd if there are only non-editable visible nodes?
+ if (IsEmptyOneHardLine(arrayOfContents)) {
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure we can put a block here.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote,
+ atStartOfSelection);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms::"
+ "blockquote) failed");
+ return splitNodeResult.Rv();
+ }
+ RefPtr<Element> theBlock = CreateNodeWithTransaction(
+ *nsGkAtoms::blockquote, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!theBlock) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = theBlock;
+ // delete anything that was in the list of nodes
+ // XXX We don't need to remove the nodes from the array for performance.
+ while (!arrayOfContents.IsEmpty()) {
+ OwningNonNull<nsIContent>& content = arrayOfContents[0];
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ arrayOfContents.RemoveElementAt(0);
+ }
+ // Don't restore the selection
+ restoreSelectionLater.Abort();
+ nsresult rv = CollapseSelectionToStartOf(*theBlock);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ // Ok, now go through all the nodes and put them in a blockquote,
+ // or whatever is appropriate. Wohoo!
+ RefPtr<Element> curList, curQuote, indentedLI;
+ for (OwningNonNull<nsIContent>& content : arrayOfContents) {
+ // Here's where we actually figure out what to do.
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ continue;
+ }
+
+ // Ignore all non-editable nodes. Leave them be.
+ // XXX We ignore non-editable nodes here, but not so in the above block.
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ nsresult rv =
+ IndentListChild(&curList, atContent, MOZ_KnownLive(content));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::IndentListChild() failed");
+ return rv;
+ }
+ // forget curQuote, if any
+ curQuote = nullptr;
+ continue;
+ }
+
+ // Not a list item, use blockquote?
+
+ // if we are inside a list item, we don't want to blockquote, we want
+ // to sublist the list item. We may have several nodes listed in the
+ // array of nodes to act on, that are in the same list item. Since
+ // we only want to indent that li once, we must keep track of the most
+ // recent indented list item, and not indent it if we find another node
+ // to act on that is still inside the same li.
+ if (RefPtr<Element> listItem = GetNearestAncestorListItemElement(content)) {
+ if (indentedLI == listItem) {
+ // already indented this list item
+ continue;
+ }
+ // check to see if curList is still appropriate. Which it is if
+ // content is still right after it in the same list.
+ nsIContent* previousEditableSibling =
+ curList ? GetPriorHTMLSibling(listItem) : nullptr;
+ if (!curList ||
+ (previousEditableSibling && previousEditableSibling != curList)) {
+ EditorDOMPoint atListItem(listItem);
+ if (NS_WARN_IF(!listItem)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAtom* containerName =
+ atListItem.GetContainer()->NodeInfo()->NameAtom();
+ // Create a new nested list of correct type.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(
+ MOZ_KnownLive(*containerName), atListItem);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
+ "failed");
+ return splitNodeResult.Rv();
+ }
+ curList = CreateNodeWithTransaction(MOZ_KnownLive(*containerName),
+ splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curList) {
+ NS_WARNING("HTMLEditor::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ rv = MoveNodeToEndWithTransaction(*listItem, *curList);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+
+ // remember we indented this li
+ indentedLI = listItem;
+
+ continue;
+ }
+
+ // need to make a blockquote to put things in if we haven't already,
+ // or if this node doesn't go in blockquote we used earlier.
+ // One reason it might not go in prio blockquote is if we are now
+ // in a different table cell.
+ if (curQuote &&
+ HTMLEditor::NodesInDifferentTableElements(*curQuote, content)) {
+ curQuote = nullptr;
+ }
+
+ if (!curQuote) {
+ // First, check that our element can contain a blockquote.
+ if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
+ *nsGkAtoms::blockquote)) {
+ return NS_OK; // cancelled
+ }
+
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote,
+ atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms:"
+ ":blockquote) failed");
+ return splitNodeResult.Rv();
+ }
+ curQuote = CreateNodeWithTransaction(*nsGkAtoms::blockquote,
+ splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curQuote) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::blockquote) "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = curQuote;
+ // curQuote is now the correct thing to put curNode in
+ }
+
+ // tuck the node into the end of the active blockquote
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
+ // keep it alive.
+ rv = MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curQuote);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ // forget curList, if any
+ curList = nullptr;
+ }
+ return NS_OK;
+}
+
+EditActionResult HTMLEditor::OutdentAsSubAction() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eOutdent, nsIEditor::eNext, ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Some selection containers are not content node, but ignored");
+ return EditActionIgnored();
+ }
+
+ result |= HandleOutdentAtSelection();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::HandleOutdentAtSelection() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed the selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ nsresult rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
+ "failed");
+ return result.SetResult(rv);
+}
+
+EditActionResult HTMLEditor::HandleOutdentAtSelection() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EditActionHandled(rv);
+ }
+ }
+
+ // HandleOutdentAtSelectionInternal() creates AutoSelectionRestorer.
+ // Therefore, even if it returns NS_OK, the editor might have been destroyed
+ // at restoring Selection.
+ SplitRangeOffFromNodeResult outdentResult =
+ HandleOutdentAtSelectionInternal();
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (outdentResult.Failed()) {
+ NS_WARNING("HTMLEditor::HandleOutdentAtSelectionInternal() failed");
+ return EditActionHandled(outdentResult.Rv());
+ }
+
+ // Make sure selection didn't stick to last piece of content in old bq (only
+ // a problem for collapsed selections)
+ if (!outdentResult.GetLeftContent() && !outdentResult.GetRightContent()) {
+ return EditActionHandled();
+ }
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ return EditActionHandled();
+ }
+
+ // Push selection past end of left element of last split indented element.
+ if (outdentResult.GetLeftContent()) {
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionHandled();
+ }
+ const RangeBoundary& atStartOfSelection = firstRange->StartRef();
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+ if (atStartOfSelection.Container() == outdentResult.GetLeftContent() ||
+ EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
+ *outdentResult.GetLeftContent())) {
+ // Selection is inside the left node - push it past it.
+ EditorRawDOMPoint afterRememberedLeftBQ(
+ EditorRawDOMPoint::After(*outdentResult.GetLeftContent()));
+ NS_WARNING_ASSERTION(
+ afterRememberedLeftBQ.IsSet(),
+ "Failed to set after remembered left blockquote element");
+ nsresult rv = CollapseSelectionTo(afterRememberedLeftBQ);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed, but ignored");
+ }
+ }
+ // And pull selection before beginning of right element of last split
+ // indented element.
+ if (outdentResult.GetRightContent()) {
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionHandled();
+ }
+ const RangeBoundary& atStartOfSelection = firstRange->StartRef();
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+ if (atStartOfSelection.Container() == outdentResult.GetRightContent() ||
+ EditorUtils::IsDescendantOf(*atStartOfSelection.Container(),
+ *outdentResult.GetRightContent())) {
+ // Selection is inside the right element - push it before it.
+ EditorRawDOMPoint atRememberedRightBQ(outdentResult.GetRightContent());
+ nsresult rv = CollapseSelectionTo(atRememberedRightBQ);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed, but ignored");
+ }
+ }
+ return EditActionHandled();
+}
+
+SplitRangeOffFromNodeResult HTMLEditor::HandleOutdentAtSelectionInternal() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ bool useCSS = IsCSSEnabled();
+
+ // Convert the selection ranges into "promoted" selection ranges: this
+ // basically just expands the range to include the immediate block parent,
+ // and then further expands to include any ancestors whose children are all
+ // in the range
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eOutdent, CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges() "
+ "failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+
+ nsCOMPtr<nsIContent> leftContentOfLastOutdented;
+ nsCOMPtr<nsIContent> middleContentOfLastOutdented;
+ nsCOMPtr<nsIContent> rightContentOfLastOutdented;
+ RefPtr<Element> indentedParentElement;
+ nsCOMPtr<nsIContent> firstContentToBeOutdented, lastContentToBeOutdented;
+ BlockIndentedWith indentedParentIndentedWith = BlockIndentedWith::HTML;
+ for (OwningNonNull<nsIContent>& content : arrayOfContents) {
+ // Here's where we actually figure out what to do
+ EditorDOMPoint atContent(content);
+ if (!atContent.IsSet()) {
+ continue;
+ }
+
+ // If it's a `<blockquote>`, remove it to outdent its children.
+ if (content->IsHTMLElement(nsGkAtoms::blockquote)) {
+ // If we've already found an ancestor block element indented, we need to
+ // split it and remove the block element first.
+ if (indentedParentElement) {
+ MOZ_ASSERT(indentedParentElement == content);
+ SplitRangeOffFromNodeResult outdentResult = OutdentPartOfBlock(
+ *indentedParentElement, *firstContentToBeOutdented,
+ *lastContentToBeOutdented, indentedParentIndentedWith);
+ if (outdentResult.Failed()) {
+ NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
+ return outdentResult;
+ }
+ leftContentOfLastOutdented = outdentResult.GetLeftContent();
+ middleContentOfLastOutdented = outdentResult.GetMiddleContent();
+ rightContentOfLastOutdented = outdentResult.GetRightContent();
+ indentedParentElement = nullptr;
+ firstContentToBeOutdented = nullptr;
+ lastContentToBeOutdented = nullptr;
+ indentedParentIndentedWith = BlockIndentedWith::HTML;
+ }
+ nsresult rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ continue;
+ }
+
+ // If we're using CSS and the node is a block element, check its start
+ // margin whether it's indented with CSS.
+ if (useCSS && HTMLEditUtils::IsBlockElement(content)) {
+ nsStaticAtom& marginProperty =
+ MarginPropertyAtomForIndent(MOZ_KnownLive(content));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ nsAutoString value;
+ DebugOnly<nsresult> rvIgnored =
+ CSSEditUtils::GetSpecifiedProperty(content, marginProperty, value);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
+ float startMargin = 0;
+ RefPtr<nsAtom> unit;
+ CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
+ // If indented with CSS, we should decrease the start mergin.
+ if (startMargin > 0) {
+ nsresult rv = ChangeMarginStart(MOZ_KnownLive(*content->AsElement()),
+ ChangeMargin::Decrease);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::ChangeMarginStart(ChangeMargin::"
+ "Decrease) failed, but ignored");
+ continue;
+ }
+ }
+
+ // If it's a list item, we should treat as that it "indents" its children.
+ if (HTMLEditUtils::IsListItem(content)) {
+ // If it is a list item, that means we are not outdenting whole list.
+ // XXX I don't understand this sentence... We may meet parent list
+ // element, no?
+ if (indentedParentElement) {
+ SplitRangeOffFromNodeResult outdentResult = OutdentPartOfBlock(
+ *indentedParentElement, *firstContentToBeOutdented,
+ *lastContentToBeOutdented, indentedParentIndentedWith);
+ if (NS_WARN_IF(outdentResult.Failed())) {
+ return outdentResult;
+ }
+ leftContentOfLastOutdented = outdentResult.GetLeftContent();
+ middleContentOfLastOutdented = outdentResult.GetMiddleContent();
+ rightContentOfLastOutdented = outdentResult.GetRightContent();
+ indentedParentElement = nullptr;
+ firstContentToBeOutdented = nullptr;
+ lastContentToBeOutdented = nullptr;
+ indentedParentIndentedWith = BlockIndentedWith::HTML;
+ }
+ // XXX `content` could become different element since
+ // `OutdentPartOfBlock()` may run mutation event listeners.
+ rv = LiftUpListItemElement(MOZ_KnownLive(*content->AsElement()),
+ LiftUpFromAllParentListElements::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
+ ":No) failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ continue;
+ }
+
+ // If we've found an ancestor block element which indents its children
+ // and the current node is NOT a descendant of it, we should remove it to
+ // outdent its children. Otherwise, i.e., current node is a descendant of
+ // it, we meet new node which should be outdented when the indented parent
+ // is removed.
+ if (indentedParentElement) {
+ if (EditorUtils::IsDescendantOf(*content, *indentedParentElement)) {
+ // Extend the range to be outdented at removing the
+ // indentedParentElement.
+ lastContentToBeOutdented = content;
+ continue;
+ }
+ SplitRangeOffFromNodeResult outdentResult = OutdentPartOfBlock(
+ *indentedParentElement, *firstContentToBeOutdented,
+ *lastContentToBeOutdented, indentedParentIndentedWith);
+ if (outdentResult.Failed()) {
+ NS_WARNING("HTMLEditor::OutdentPartOfBlock() failed");
+ return outdentResult;
+ }
+ leftContentOfLastOutdented = outdentResult.GetLeftContent();
+ middleContentOfLastOutdented = outdentResult.GetMiddleContent();
+ rightContentOfLastOutdented = outdentResult.GetRightContent();
+ indentedParentElement = nullptr;
+ firstContentToBeOutdented = nullptr;
+ lastContentToBeOutdented = nullptr;
+ // curBlockIndentedWith = HTMLEditor::BlockIndentedWith::HTML;
+
+ // Then, we need to look for next indentedParentElement.
+ }
+
+ indentedParentIndentedWith = BlockIndentedWith::HTML;
+ RefPtr<Element> editingHost = GetActiveEditingHost();
+ for (nsCOMPtr<nsIContent> parentContent = content->GetParent();
+ parentContent && !parentContent->IsHTMLElement(nsGkAtoms::body) &&
+ parentContent != editingHost &&
+ (parentContent->IsHTMLElement(nsGkAtoms::table) ||
+ !HTMLEditUtils::IsAnyTableElement(parentContent));
+ parentContent = parentContent->GetParent()) {
+ // If we reach a `<blockquote>` ancestor, it should be split at next
+ // time at least for outdenting current node.
+ if (parentContent->IsHTMLElement(nsGkAtoms::blockquote)) {
+ indentedParentElement = parentContent->AsElement();
+ firstContentToBeOutdented = content;
+ lastContentToBeOutdented = content;
+ break;
+ }
+
+ if (!useCSS) {
+ continue;
+ }
+
+ nsCOMPtr<nsINode> grandParentNode = parentContent->GetParentNode();
+ nsStaticAtom& marginProperty =
+ MarginPropertyAtomForIndent(MOZ_KnownLive(content));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_WARN_IF(grandParentNode != parentContent->GetParentNode())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+ nsAutoString value;
+ DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
+ *parentContent, marginProperty, value);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
+ // XXX Now, editing host may become different element. If so, shouldn't
+ // we stop this handling?
+ float startMargin;
+ RefPtr<nsAtom> unit;
+ CSSEditUtils::ParseLength(value, &startMargin, getter_AddRefs(unit));
+ // If we reach a block element which indents its children with start
+ // margin, we should remove it at next time.
+ if (startMargin > 0 &&
+ !(HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) &&
+ HTMLEditUtils::IsAnyListElement(content))) {
+ indentedParentElement = parentContent->AsElement();
+ firstContentToBeOutdented = content;
+ lastContentToBeOutdented = content;
+ indentedParentIndentedWith = BlockIndentedWith::CSS;
+ break;
+ }
+ }
+
+ if (indentedParentElement) {
+ continue;
+ }
+
+ // If we don't have any block elements which indents current node and
+ // both current node and its parent are list element, remove current
+ // node to move all its children to the parent list.
+ // XXX This is buggy. When both lists' item types are different,
+ // we create invalid tree. E.g., `<ul>` may have `<dd>` as its
+ // list item element.
+ if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
+ // Move node out of list
+ if (HTMLEditUtils::IsAnyListElement(content)) {
+ // Just unwrap this sublist
+ nsresult rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ }
+ continue;
+ }
+
+ // If current content is a list element but its parent is not a list
+ // element, move children to where it is and remove it from the tree.
+ if (HTMLEditUtils::IsAnyListElement(content)) {
+ // XXX If mutation event listener appends new children forever, this
+ // becomes an infinite loop so that we should set limitation from
+ // first child count.
+ for (nsCOMPtr<nsIContent> lastChildContent = content->GetLastChild();
+ lastChildContent; lastChildContent = content->GetLastChild()) {
+ if (HTMLEditUtils::IsListItem(lastChildContent)) {
+ nsresult rv = LiftUpListItemElement(
+ MOZ_KnownLive(*lastChildContent->AsElement()),
+ LiftUpFromAllParentListElements::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::LiftUpListItemElement("
+ "LiftUpFromAllParentListElements::No) failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(lastChildContent)) {
+ // We have an embedded list, so move it out from under the parent
+ // list. Be sure to put it after the parent list because this
+ // loop iterates backwards through the parent's list of children.
+ EditorDOMPoint afterCurrentList(EditorDOMPoint::After(atContent));
+ NS_WARNING_ASSERTION(
+ afterCurrentList.IsSet(),
+ "Failed to set it to after current list element");
+ nsresult rv =
+ MoveNodeWithTransaction(*lastChildContent, afterCurrentList);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ continue;
+ }
+
+ // Delete any non-list items for now
+ // XXX Chrome moves it from the list element. We should follow it.
+ nsresult rv = DeleteNodeWithTransaction(*lastChildContent);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ }
+ // Delete the now-empty list
+ nsresult rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ continue;
+ }
+
+ if (useCSS) {
+ if (RefPtr<Element> element = content->GetAsElementOrParentElement()) {
+ nsresult rv = ChangeMarginStart(*element, ChangeMargin::Decrease);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::ChangeMarginStart(ChangeMargin::"
+ "Decrease) failed, but ignored");
+ }
+ continue;
+ }
+ }
+
+ if (!indentedParentElement) {
+ return SplitRangeOffFromNodeResult(leftContentOfLastOutdented,
+ middleContentOfLastOutdented,
+ rightContentOfLastOutdented);
+ }
+
+ // We have a <blockquote> we haven't finished handling.
+ SplitRangeOffFromNodeResult outdentResult =
+ OutdentPartOfBlock(*indentedParentElement, *firstContentToBeOutdented,
+ *lastContentToBeOutdented, indentedParentIndentedWith);
+ NS_WARNING_ASSERTION(outdentResult.Succeeded(),
+ "HTMLEditor::OutdentPartOfBlock() failed");
+ return outdentResult;
+}
+
+SplitRangeOffFromNodeResult
+HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ Element& aBlockElement, nsIContent& aStartOfRange,
+ nsIContent& aEndOfRange) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ SplitRangeOffFromNodeResult splitResult =
+ SplitRangeOffFromBlock(aBlockElement, aStartOfRange, aEndOfRange);
+ if (NS_WARN_IF(splitResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) {
+ return splitResult;
+ }
+ NS_WARNING_ASSERTION(
+ splitResult.Succeeded(),
+ "HTMLEditor::SplitRangeOffFromBlock() failed, but might be ignored");
+ nsresult rv = RemoveBlockContainerWithTransaction(aBlockElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), nullptr,
+ splitResult.GetRightContent());
+}
+
+SplitRangeOffFromNodeResult HTMLEditor::SplitRangeOffFromBlock(
+ Element& aBlockElement, nsIContent& aStartOfMiddleElement,
+ nsIContent& aEndOfMiddleElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive
+ // descendants of aBlockElement.
+ MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartOfMiddleElement, aBlockElement));
+ MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aBlockElement));
+
+ // Split at the start.
+ SplitNodeResult splitAtStartResult = SplitNodeDeepWithTransaction(
+ aBlockElement, EditorDOMPoint(&aStartOfMiddleElement),
+ SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(splitAtStartResult.Succeeded(),
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+
+ // Split at after the end
+ EditorDOMPoint atAfterEnd(&aEndOfMiddleElement);
+ DebugOnly<bool> advanced = atAfterEnd.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced, "Failed to advance offset after the end node");
+ SplitNodeResult splitAtEndResult = SplitNodeDeepWithTransaction(
+ aBlockElement, atAfterEnd, SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(splitAtEndResult.Succeeded(),
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+
+ return SplitRangeOffFromNodeResult(splitAtStartResult, splitAtEndResult);
+}
+
+SplitRangeOffFromNodeResult HTMLEditor::OutdentPartOfBlock(
+ Element& aBlockElement, nsIContent& aStartOfOutdent,
+ nsIContent& aEndOfOutdent, BlockIndentedWith aBlockIndentedWith) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ SplitRangeOffFromNodeResult splitResult =
+ SplitRangeOffFromBlock(aBlockElement, aStartOfOutdent, aEndOfOutdent);
+ if (NS_WARN_IF(splitResult.EditorDestroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+
+ if (!splitResult.GetMiddleContentAsElement()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlock() didn't return middle content");
+ return SplitRangeOffFromNodeResult(NS_ERROR_FAILURE);
+ }
+ NS_WARNING_ASSERTION(
+ splitResult.Succeeded(),
+ "HTMLEditor::SplitRangeOffFromBlock() failed, but might be ignored");
+
+ if (aBlockIndentedWith == BlockIndentedWith::HTML) {
+ nsresult rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*splitResult.GetMiddleContentAsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), nullptr,
+ splitResult.GetRightContent());
+ }
+
+ if (splitResult.GetMiddleContentAsElement()) {
+ nsresult rv = ChangeMarginStart(
+ MOZ_KnownLive(*splitResult.GetMiddleContentAsElement()),
+ ChangeMargin::Decrease);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::ChangeMarginStart(ChangeMargin::Decrease) failed");
+ return SplitRangeOffFromNodeResult(rv);
+ }
+ return splitResult;
+ }
+
+ return splitResult;
+}
+
+CreateElementResult HTMLEditor::ChangeListElementType(Element& aListElement,
+ nsAtom& aNewListTag,
+ nsAtom& aNewListItemTag) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ for (nsIContent* childContent = aListElement.GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ if (!childContent->IsElement()) {
+ continue;
+ }
+ if (HTMLEditUtils::IsListItem(childContent->AsElement()) &&
+ !childContent->IsHTMLElement(&aNewListItemTag)) {
+ OwningNonNull<Element> listItemElement = *childContent->AsElement();
+ RefPtr<Element> newListItemElement =
+ ReplaceContainerWithTransaction(listItemElement, aNewListItemTag);
+ if (NS_WARN_IF(Destroyed())) {
+ return CreateElementResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!newListItemElement) {
+ NS_WARNING("HTMLEditor::ReplaceContainerWithTransaction() failed");
+ return CreateElementResult(NS_ERROR_FAILURE);
+ }
+ childContent = newListItemElement;
+ continue;
+ }
+ if (HTMLEditUtils::IsAnyListElement(childContent->AsElement()) &&
+ !childContent->IsHTMLElement(&aNewListTag)) {
+ // XXX List elements shouldn't have other list elements as their
+ // child. Why do we handle such invalid tree?
+ // -> Maybe, for bug 525888.
+ OwningNonNull<Element> listElement = *childContent->AsElement();
+ CreateElementResult convertListTypeResult =
+ ChangeListElementType(listElement, aNewListTag, aNewListItemTag);
+ if (convertListTypeResult.Failed()) {
+ NS_WARNING("HTMLEditor::ChangeListElementType() failed");
+ return convertListTypeResult;
+ }
+ childContent = convertListTypeResult.GetNewNode();
+ continue;
+ }
+ }
+
+ if (aListElement.IsHTMLElement(&aNewListTag)) {
+ return CreateElementResult(&aListElement);
+ }
+
+ RefPtr<Element> listElement =
+ ReplaceContainerWithTransaction(aListElement, aNewListTag);
+ if (NS_WARN_IF(Destroyed())) {
+ return CreateElementResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(listElement,
+ "HTMLEditor::ReplaceContainerWithTransaction() failed");
+ return CreateElementResult(std::move(listElement));
+}
+
+nsresult HTMLEditor::CreateStyleForInsertText(
+ const AbstractRange& aAbstractRange) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aAbstractRange.IsPositioned());
+ MOZ_ASSERT(mTypeInState);
+
+ RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
+ if (NS_WARN_IF(!documentRootElement)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // process clearing any styles first
+ UniquePtr<PropItem> item = mTypeInState->TakeClearProperty();
+
+ EditorDOMPoint pointToPutCaret(aAbstractRange.StartRef());
+ bool putCaret = false;
+ {
+ // Transactions may set selection, but we will set selection if necessary.
+ AutoTransactionsConserveSelection dontChangeMySelection(*this);
+
+ while (item && pointToPutCaret.GetContainer() != documentRootElement) {
+ EditResult result = ClearStyleAt(
+ pointToPutCaret, MOZ_KnownLive(item->tag), MOZ_KnownLive(item->attr));
+ if (result.Failed()) {
+ NS_WARNING("HTMLEditor::ClearStyleAt() failed");
+ return result.Rv();
+ }
+ pointToPutCaret = result.PointRefToCollapseSelection();
+ item = mTypeInState->TakeClearProperty();
+ putCaret = true;
+ }
+ }
+
+ // then process setting any styles
+ int32_t relFontSize = mTypeInState->TakeRelativeFontSize();
+ item = mTypeInState->TakeSetProperty();
+
+ if (item || relFontSize) {
+ // we have at least one style to add; make a new text node to insert style
+ // nodes above.
+ if (pointToPutCaret.IsInTextNode()) {
+ // if we are in a text node, split it
+ SplitNodeResult splitTextNodeResult = SplitNodeDeepWithTransaction(
+ MOZ_KnownLive(*pointToPutCaret.GetContainerAsText()), pointToPutCaret,
+ SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitTextNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eAllowToCreateEmptyContainer) failed");
+ return splitTextNodeResult.Rv();
+ }
+ pointToPutCaret = splitTextNodeResult.SplitPoint();
+ }
+ if (!pointToPutCaret.IsInContentNode() ||
+ !HTMLEditUtils::IsContainerNode(
+ *pointToPutCaret.ContainerAsContent())) {
+ return NS_OK;
+ }
+ RefPtr<Text> newEmptyTextNode = CreateTextNode(u""_ns);
+ if (!newEmptyTextNode) {
+ NS_WARNING("EditorBase::CreateTextNode() failed");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = InsertNodeWithTransaction(*newEmptyTextNode, pointToPutCaret);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
+ return rv;
+ }
+ pointToPutCaret.Set(newEmptyTextNode, 0);
+ putCaret = true;
+
+ if (relFontSize) {
+ // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
+ HTMLEditor::FontSize dir = relFontSize > 0 ? HTMLEditor::FontSize::incr
+ : HTMLEditor::FontSize::decr;
+ for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) {
+ nsresult rv =
+ RelativeFontChangeOnTextNode(dir, *newEmptyTextNode, 0, -1);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
+ return rv;
+ }
+ }
+ }
+
+ while (item) {
+ nsresult rv = SetInlinePropertyOnNode(
+ MOZ_KnownLive(*pointToPutCaret.GetContainerAsContent()),
+ MOZ_KnownLive(*item->tag), MOZ_KnownLive(item->attr), item->value);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
+ return rv;
+ }
+ item = mTypeInState->TakeSetProperty();
+ }
+ }
+
+ if (!putCaret) {
+ return NS_OK;
+ }
+
+ nsresult rv = CollapseSelectionTo(pointToPutCaret);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+}
+
+bool HTMLEditor::IsEmptyBlockElement(Element& aElement,
+ IgnoreSingleBR aIgnoreSingleBR) const {
+ if (!HTMLEditUtils::IsBlockElement(aElement)) {
+ return false;
+ }
+ return IsEmptyNode(aElement, aIgnoreSingleBR == IgnoreSingleBR::Yes);
+}
+
+EditActionResult HTMLEditor::AlignAsSubAction(const nsAString& aAlignType) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eSetOrClearAlignment, nsIEditor::eNext,
+ ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Some selection containers are not content node, but ignored");
+ return EditActionIgnored();
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed the selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return EditActionResult(rv);
+ }
+ }
+
+ // AlignContentsAtSelection() creates AutoSelectionRestorer. Therefore,
+ // we need to check whether we've been destroyed or not even if it returns
+ // NS_OK.
+ rv = AlignContentsAtSelection(aAlignType);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::AlignContentsAtSelection() failed");
+ return EditActionHandled(rv);
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed the selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
+ "failed");
+ return EditActionHandled(rv);
+}
+
+nsresult HTMLEditor::AlignContentsAtSelection(const nsAString& aAlignType) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ // Convert the selection ranges into "promoted" selection ranges: This
+ // basically just expands the range to include the immediate block parent,
+ // and then further expands to include any ancestors whose children are all
+ // in the range
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ nsresult rv = SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges(
+ arrayOfContents, EditSubAction::eSetOrClearAlignment,
+ CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "SplitInlinesAndCollectEditTargetNodesInExtendedSelectionRanges("
+ "eSetOrClearAlignment, CollectNonEditableNodes::Yes) failed");
+ return rv;
+ }
+
+ // If we don't have any nodes, or we have only a single br, then we are
+ // creating an empty alignment div. We have to do some different things for
+ // these.
+ bool createEmptyDivElement = arrayOfContents.IsEmpty();
+ if (arrayOfContents.Length() == 1) {
+ OwningNonNull<nsIContent>& content = arrayOfContents[0];
+
+ if (HTMLEditUtils::SupportsAlignAttr(content)) {
+ // The node is a table element, an hr, a paragraph, a div or a section
+ // header; in HTML 4, it can directly carry the ALIGN attribute and we
+ // don't need to make a div! If we are in CSS mode, all the work is done
+ // in SetBlockElementAlign().
+ nsresult rv =
+ SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
+ EditTarget::OnlyDescendantsExceptTable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::SetBlockElementAlign() failed");
+ return rv;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::br)) {
+ // The special case createEmptyDivElement code (below) that consumes
+ // `<br>` elements can cause tables to split if the start node of the
+ // selection is not in a table cell or caption, for example parent is a
+ // `<tr>`. Avoid this unnecessary splitting if possible by leaving
+ // createEmptyDivElement false so that we fall through to the normal case
+ // alignment code.
+ //
+ // XXX: It seems a little error prone for the createEmptyDivElement
+ // special case code to assume that the start node of the selection
+ // is the parent of the single node in the arrayOfContents, as the
+ // paragraph above points out. Do we rely on the selection start
+ // node because of the fact that arrayOfContents can be empty? We
+ // should probably revisit this issue. - kin
+
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+ const RangeBoundary& atStartOfSelection = firstRange->StartRef();
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ nsINode* parent = atStartOfSelection.Container();
+ createEmptyDivElement = !HTMLEditUtils::IsAnyTableElement(parent) ||
+ HTMLEditUtils::IsTableCellOrCaption(*parent);
+ }
+ }
+
+ if (createEmptyDivElement) {
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutaiton event listener might have changed the selection");
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ EditActionResult result =
+ AlignContentsAtSelectionWithEmptyDivElement(aAlignType);
+ NS_WARNING_ASSERTION(
+ result.Succeeded(),
+ "HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement() failed");
+ if (result.Handled()) {
+ restoreSelectionLater.Abort();
+ }
+ return rv;
+ }
+
+ rv = AlignNodesAndDescendants(arrayOfContents, aAlignType);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::AlignNodesAndDescendants() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::AlignContentsAtSelectionWithEmptyDivElement(
+ const nsAString& aAlignType) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsertWithTransaction(
+ *nsGkAtoms::div, atStartOfSelection);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms::"
+ "div) failed");
+ return EditActionResult(splitNodeResult.Rv());
+ }
+
+ EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint());
+
+ // Consume a trailing br, if any. This is to keep an alignment from
+ // creating extra lines, if possible.
+ if (nsCOMPtr<nsIContent> maybeBRContent =
+ GetNextEditableHTMLNodeInBlock(splitNodeResult.SplitPoint())) {
+ if (maybeBRContent->IsHTMLElement(nsGkAtoms::br) &&
+ pointToInsertDiv.GetChild()) {
+ // Making use of html structure... if next node after where we are
+ // putting our div is not a block, then the br we found is in same block
+ // we are, so it's safe to consume it.
+ if (nsIContent* nextEditableSibling =
+ GetNextHTMLSibling(pointToInsertDiv.GetChild())) {
+ if (!HTMLEditUtils::IsBlockElement(*nextEditableSibling)) {
+ AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv);
+ nsresult rv = DeleteNodeWithTransaction(*maybeBRContent);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return EditActionResult(rv);
+ }
+ }
+ }
+ }
+ }
+
+ RefPtr<Element> divElement =
+ CreateNodeWithTransaction(*nsGkAtoms::div, pointToInsertDiv);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (!divElement) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = divElement;
+ // Set up the alignment on the div, using HTML or CSS
+ nsresult rv = SetBlockElementAlign(*divElement, aAlignType,
+ EditTarget::OnlyDescendantsExceptTable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::SetBlockElementAlign(EditTarget::"
+ "OnlyDescendantsExceptTable) failed");
+ return EditActionResult(rv);
+ }
+ // Put in a padding <br> element for empty last line so that it won't get
+ // deleted.
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(
+ EditorDOMPoint(divElement, 0));
+ if (createPaddingBRResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
+ "failed");
+ return EditActionResult(createPaddingBRResult.Rv());
+ }
+ rv = CollapseSelectionToStartOf(*divElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return EditActionHandled(rv);
+}
+
+nsresult HTMLEditor::AlignNodesAndDescendants(
+ nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
+ const nsAString& aAlignType) {
+ // Detect all the transitions in the array, where a transition means that
+ // adjacent nodes in the array don't have the same parent.
+ AutoTArray<bool, 64> transitionList;
+ HTMLEditor::MakeTransitionList(aArrayOfContents, transitionList);
+
+ // Okay, now go through all the nodes and give them an align attrib or put
+ // them in a div, or whatever is appropriate. Woohoo!
+
+ RefPtr<Element> createdDivElement;
+ bool useCSS = IsCSSEnabled();
+ int32_t indexOfTransitionList = -1;
+ for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
+ ++indexOfTransitionList;
+
+ // Ignore all non-editable nodes. Leave them be.
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+
+ // The node is a table element, an hr, a paragraph, a div or a section
+ // header; in HTML 4, it can directly carry the ALIGN attribute and we
+ // don't need to nest it, just set the alignment. In CSS, assign the
+ // corresponding CSS styles in SetBlockElementAlign().
+ if (HTMLEditUtils::SupportsAlignAttr(content)) {
+ nsresult rv =
+ SetBlockElementAlign(MOZ_KnownLive(*content->AsElement()), aAlignType,
+ EditTarget::NodeAndDescendantsExceptTable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::SetBlockElementAlign(EditTarget::"
+ "NodeAndDescendantsExceptTable) failed");
+ return rv;
+ }
+ // Clear out createdDivElement so that we don't put nodes after this one
+ // into it
+ createdDivElement = nullptr;
+ continue;
+ }
+
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ continue;
+ }
+
+ // Skip insignificant formatting text nodes to prevent unnecessary
+ // structure splitting!
+ if (content->IsText() &&
+ ((HTMLEditUtils::IsAnyTableElement(atContent.GetContainer()) &&
+ !HTMLEditUtils::IsTableCellOrCaption(*atContent.GetContainer())) ||
+ HTMLEditUtils::IsAnyListElement(atContent.GetContainer()) ||
+ IsEmptyNode(*content))) {
+ continue;
+ }
+
+ // If it's a list item, or a list inside a list, forget any "current" div,
+ // and instead put divs inside the appropriate block (td, li, etc.)
+ if (HTMLEditUtils::IsListItem(content) ||
+ HTMLEditUtils::IsAnyListElement(content)) {
+ Element* listOrListItemElement = content->AsElement();
+ AutoEditorDOMPointOffsetInvalidator lockChild(atContent);
+ // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
+ // which is array of OwningNonNull.
+ nsresult rv = RemoveAlignFromDescendants(
+ MOZ_KnownLive(*listOrListItemElement), aAlignType,
+ EditTarget::OnlyDescendantsExceptTable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
+ "OnlyDescendantsExceptTable) failed");
+ return rv;
+ }
+
+ if (useCSS) {
+ if (nsStyledElement* styledListOrListItemElement =
+ nsStyledElement::FromNode(listOrListItemElement)) {
+ // MOZ_KnownLive(*styledListOrListItemElement): An element of
+ // aArrayOfContents which is array of OwningNonNull.
+ Result<int32_t, nsresult> result =
+ mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
+ MOZ_KnownLive(*styledListOrListItemElement), nullptr,
+ nsGkAtoms::align, &aAlignType);
+ if (result.isErr()) {
+ if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
+ NS_WARNING(
+ "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
+ "nsGkAtoms::align) destroyed the editor");
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING(
+ "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
+ "nsGkAtoms::align) failed, but ignored");
+ }
+ }
+ createdDivElement = nullptr;
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
+ // If we don't use CSS, add a content to list element: they have to
+ // be inside another list, i.e., >= second level of nesting.
+ // XXX AlignContentsInAllTableCellsAndListItems() handles only list
+ // item elements and table cells. Is it intentional? Why don't
+ // we need to align contents in other type blocks?
+ // MOZ_KnownLive(*listOrListItemElement): An element of aArrayOfContents
+ // which is array of OwningNonNull.
+ nsresult rv = AlignContentsInAllTableCellsAndListItems(
+ MOZ_KnownLive(*listOrListItemElement), aAlignType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::AlignContentsInAllTableCellsAndListItems() failed");
+ return rv;
+ }
+ createdDivElement = nullptr;
+ continue;
+ }
+
+ // Clear out createdDivElement so that we don't put nodes after this one
+ // into it
+ }
+
+ // Need to make a div to put things in if we haven't already, or if this
+ // node doesn't go in div we used earlier.
+ if (!createdDivElement || transitionList[indexOfTransitionList]) {
+ // First, check that our element can contain a div.
+ if (!HTMLEditUtils::CanNodeContain(*atContent.GetContainer(),
+ *nsGkAtoms::div)) {
+ // XXX Why do we return NS_OK here rather than returning error or
+ // doing continue?
+ return NS_OK;
+ }
+
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
+ atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms:"
+ ":div) failed");
+ return splitNodeResult.Rv();
+ }
+ createdDivElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!createdDivElement) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = createdDivElement;
+ // Set up the alignment on the div
+ nsresult rv =
+ SetBlockElementAlign(*createdDivElement, aAlignType,
+ EditTarget::OnlyDescendantsExceptTable);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::SetBlockElementAlign(EditTarget::"
+ "OnlyDescendantsExceptTable) failed, but ignored");
+ }
+
+ // Tuck the node into the end of the active div
+ //
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it alive.
+ nsresult rv = MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
+ *createdDivElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLEditor::AlignContentsInAllTableCellsAndListItems(
+ Element& aElement, const nsAString& aAlignType) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // Gather list of table cells or list items
+ AutoTArray<OwningNonNull<Element>, 64> arrayOfTableCellsAndListItems;
+ DOMIterator iter(aElement);
+ iter.AppendNodesToArray(
+ +[](nsINode& aNode, void*) -> bool {
+ MOZ_ASSERT(Element::FromNode(&aNode));
+ return HTMLEditUtils::IsTableCell(&aNode) ||
+ HTMLEditUtils::IsListItem(&aNode);
+ },
+ arrayOfTableCellsAndListItems);
+
+ // Now that we have the list, align their contents as requested
+ for (auto& tableCellOrListItemElement : arrayOfTableCellsAndListItems) {
+ // MOZ_KnownLive because 'arrayOfTableCellsAndListItems' is guaranteed to
+ // keep it alive.
+ nsresult rv = AlignBlockContentsWithDivElement(
+ MOZ_KnownLive(tableCellOrListItemElement), aAlignType);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::AlignBlockContentsWithDivElement() failed");
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLEditor::AlignBlockContentsWithDivElement(
+ Element& aBlockElement, const nsAString& aAlignType) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // XXX I don't understand why we should NOT align non-editable children
+ // with modifying EDITABLE `<div>` element.
+ nsCOMPtr<nsIContent> firstEditableContent =
+ GetFirstEditableChild(aBlockElement);
+ if (!firstEditableContent) {
+ // This block has no editable content, nothing to align.
+ return NS_OK;
+ }
+
+ // If there is only one editable content and it's a `<div>` element,
+ // just set `align` attribute of it.
+ nsCOMPtr<nsIContent> lastEditableContent =
+ GetLastEditableChild(aBlockElement);
+ if (firstEditableContent == lastEditableContent &&
+ firstEditableContent->IsHTMLElement(nsGkAtoms::div)) {
+ nsresult rv = SetAttributeOrEquivalent(
+ MOZ_KnownLive(firstEditableContent->AsElement()), nsGkAtoms::align,
+ aAlignType, false);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
+ return rv;
+ }
+
+ // Otherwise, we need to insert a `<div>` element to set `align` attribute.
+ // XXX Don't insert the new `<div>` element until we set `align` attribute
+ // for avoiding running mutation event listeners.
+ RefPtr<Element> divElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, EditorDOMPoint(&aBlockElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!divElement) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv =
+ SetAttributeOrEquivalent(divElement, nsGkAtoms::align, aAlignType, false);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
+ return rv;
+ }
+ // XXX This is tricky and does not work with mutation event listeners.
+ // But I'm not sure what we should do if new content is inserted.
+ // Anyway, I don't think that we should move editable contents
+ // over non-editable contents. Chrome does no do that.
+ while (lastEditableContent && (lastEditableContent != divElement)) {
+ nsresult rv = MoveNodeWithTransaction(*lastEditableContent,
+ EditorDOMPoint(divElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ lastEditableContent = GetLastEditableChild(aBlockElement);
+ }
+ return NS_OK;
+}
+
+size_t HTMLEditor::CollectChildren(
+ nsINode& aNode, nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
+ size_t aIndexToInsertChildren, CollectListChildren aCollectListChildren,
+ CollectTableChildren aCollectTableChildren,
+ CollectNonEditableNodes aCollectNonEditableNodes) const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ size_t numberOfFoundChildren = 0;
+ for (nsIContent* content = GetFirstEditableChild(aNode); content;
+ content = content->GetNextSibling()) {
+ if ((aCollectListChildren == CollectListChildren::Yes &&
+ (HTMLEditUtils::IsAnyListElement(content) ||
+ HTMLEditUtils::IsListItem(content))) ||
+ (aCollectTableChildren == CollectTableChildren::Yes &&
+ HTMLEditUtils::IsAnyTableElement(content))) {
+ numberOfFoundChildren += CollectChildren(
+ *content, aOutArrayOfContents,
+ aIndexToInsertChildren + numberOfFoundChildren, aCollectListChildren,
+ aCollectTableChildren, aCollectNonEditableNodes);
+ } else if (aCollectNonEditableNodes == CollectNonEditableNodes::Yes ||
+ EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
+ aOutArrayOfContents.InsertElementAt(
+ aIndexToInsertChildren + numberOfFoundChildren++, *content);
+ }
+ }
+ return numberOfFoundChildren;
+}
+
+nsresult HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // This tweaks selections to be more "natural".
+ // Idea here is to adjust edges of selection ranges so that they do not cross
+ // breaks or block boundaries unless something editable beyond that boundary
+ // is also selected. This adjustment makes it much easier for the various
+ // block operations to determine what nodes to act on.
+
+ // We don't need to mess with cell selections, and we assume multirange
+ // selections are those.
+ // XXX Why? Even in <input>, user can select 2 or more ranges.
+ if (SelectionRefPtr()->RangeCount() != 1) {
+ return NS_OK;
+ }
+
+ const RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!range)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!range->IsPositioned())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const EditorDOMPoint startPoint(range->StartRef());
+ if (NS_WARN_IF(!startPoint.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ const EditorDOMPoint endPoint(range->EndRef());
+ if (NS_WARN_IF(!endPoint.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // adjusted values default to original values
+ EditorDOMPoint newStartPoint(startPoint);
+ EditorDOMPoint newEndPoint(endPoint);
+
+ // Is there any intervening visible white-space? If so we can't push
+ // selection past that, it would visibly change meaning of users selection.
+ WSRunScanner wsScannerAtEnd(*this, endPoint);
+ WSScanResult scanResultAtEnd =
+ wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint);
+ if (scanResultAtEnd.Failed()) {
+ NS_WARNING(
+ "WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom() failed");
+ return NS_ERROR_FAILURE;
+ }
+ if (scanResultAtEnd.ReachedSomething()) {
+ // eThisBlock and eOtherBlock conveniently distinguish cases
+ // of going "down" into a block and "up" out of a block.
+ if (wsScannerAtEnd.StartsFromOtherBlockElement()) {
+ // endpoint is just after the close of a block.
+ nsIContent* child = HTMLEditUtils::GetLastLeafChild(
+ *wsScannerAtEnd.StartReasonOtherBlockElementPtr(),
+ ChildBlockBoundary::TreatAsLeaf);
+ if (child) {
+ newEndPoint.SetAfter(child);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsScannerAtEnd.StartsFromCurrentBlockBoundary()) {
+ // endpoint is just after start of this block
+ nsINode* child = GetPreviousEditableHTMLNode(endPoint);
+ if (child) {
+ newEndPoint.SetAfter(child);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsScannerAtEnd.StartsFromBRElement()) {
+ // endpoint is just after break. lets adjust it to before it.
+ newEndPoint.Set(wsScannerAtEnd.StartReasonBRElementPtr());
+ }
+ }
+
+ // Is there any intervening visible white-space? If so we can't push
+ // selection past that, it would visibly change meaning of users selection.
+ WSRunScanner wsScannerAtStart(*this, startPoint);
+ WSScanResult scanResultAtStart =
+ wsScannerAtStart.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint);
+ if (scanResultAtStart.Failed()) {
+ NS_WARNING("WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom() failed");
+ return NS_ERROR_FAILURE;
+ }
+ if (scanResultAtStart.ReachedSomething()) {
+ // eThisBlock and eOtherBlock conveniently distinguish cases
+ // of going "down" into a block and "up" out of a block.
+ if (wsScannerAtStart.EndsByOtherBlockElement()) {
+ // startpoint is just before the start of a block.
+ nsINode* child = HTMLEditUtils::GetFirstLeafChild(
+ *wsScannerAtStart.EndReasonOtherBlockElementPtr(),
+ ChildBlockBoundary::TreatAsLeaf);
+ if (child) {
+ newStartPoint.Set(child);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsScannerAtStart.EndsByCurrentBlockBoundary()) {
+ // startpoint is just before end of this block
+ nsINode* child = GetNextEditableHTMLNode(startPoint);
+ if (child) {
+ newStartPoint.Set(child);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsScannerAtStart.EndsByBRElement()) {
+ // startpoint is just before a break. lets adjust it to after it.
+ newStartPoint.SetAfter(wsScannerAtStart.EndReasonBRElementPtr());
+ }
+ }
+
+ // There is a demented possibility we have to check for. We might have a very
+ // strange selection that is not collapsed and yet does not contain any
+ // editable content, and satisfies some of the above conditions that cause
+ // tweaking. In this case we don't want to tweak the selection into a block
+ // it was never in, etc. There are a variety of strategies one might use to
+ // try to detect these cases, but I think the most straightforward is to see
+ // if the adjusted locations "cross" the old values: i.e., new end before old
+ // start, or new start after old end. If so then just leave things alone.
+
+ Maybe<int32_t> comp = nsContentUtils::ComparePoints(
+ startPoint.ToRawRangeBoundary(), newEndPoint.ToRawRangeBoundary());
+
+ if (NS_WARN_IF(!comp)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (*comp == 1) {
+ return NS_OK; // New end before old start.
+ }
+
+ comp = nsContentUtils::ComparePoints(newStartPoint.ToRawRangeBoundary(),
+ endPoint.ToRawRangeBoundary());
+
+ if (NS_WARN_IF(!comp)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (*comp == 1) {
+ return NS_OK; // New start after old end.
+ }
+
+ // Otherwise set selection to new values. Note that end point may be prior
+ // to start point. So, we cannot use Selection::SetStartAndEndInLimit() here.
+ ErrorResult error;
+ MOZ_KnownLive(SelectionRefPtr())
+ ->SetBaseAndExtentInLimiter(newStartPoint, newEndPoint, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error = NS_ERROR_EDITOR_DESTROYED;
+ } else if (error.Failed()) {
+ NS_WARNING("Selection::SetBaseAndExtentInLimiter() failed");
+ }
+ return error.StealNSResult();
+}
+
+// static
+template <typename PT, typename RT>
+EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+ const RangeBoundaryBase<PT, RT>& aPoint, ScanDirection aScanDirection) {
+ if (NS_WARN_IF(!aPoint.IsSet()) ||
+ NS_WARN_IF(!aPoint.Container()->IsContent())) {
+ return EditorDOMPoint();
+ }
+
+ bool isSpace = false, isNBSP = false;
+ nsIContent* newContent = aPoint.Container()->AsContent();
+ int32_t newOffset = *aPoint.Offset(
+ RangeBoundaryBase<PT, RT>::OffsetFilter::kValidOrInvalidOffsets);
+ while (newContent) {
+ int32_t offset = -1;
+ nsCOMPtr<nsIContent> content;
+ if (aScanDirection == ScanDirection::Backward) {
+ HTMLEditor::IsPrevCharInNodeWhiteSpace(newContent, newOffset, &isSpace,
+ &isNBSP, getter_AddRefs(content),
+ &offset);
+ } else {
+ HTMLEditor::IsNextCharInNodeWhiteSpace(newContent, newOffset, &isSpace,
+ &isNBSP, getter_AddRefs(content),
+ &offset);
+ }
+ if (!isSpace && !isNBSP) {
+ break;
+ }
+ newContent = content;
+ newOffset = offset;
+ }
+ return EditorDOMPoint(newContent, newOffset);
+}
+
+template <typename PT, typename RT>
+EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+ const RangeBoundaryBase<PT, RT>& aPoint,
+ EditSubAction aEditSubAction) const {
+ if (NS_WARN_IF(!aPoint.IsSet())) {
+ return EditorDOMPoint();
+ }
+
+ EditorDOMPoint point(aPoint);
+ // Start scanning from the container node if aPoint is in a text node.
+ // XXX Perhaps, IsInDataNode() must be expected.
+ if (point.IsInTextNode()) {
+ if (!point.GetContainer()->GetParentNode()) {
+ // Okay, can't promote any further
+ // XXX Why don't we return start of the text node?
+ return point;
+ }
+ point.Set(point.GetContainer());
+ }
+
+ // Look back through any further inline nodes that aren't across a <br>
+ // from us, and that are enclosed in the same block.
+ // I.e., looking for start of current hard line.
+ for (nsIContent* previousEditableContent =
+ GetPreviousEditableHTMLNodeInBlock(point);
+ previousEditableContent && previousEditableContent->GetParentNode() &&
+ !IsVisibleBRElement(previousEditableContent) &&
+ !HTMLEditUtils::IsBlockElement(*previousEditableContent);
+ previousEditableContent = GetPreviousEditableHTMLNodeInBlock(point)) {
+ point.Set(previousEditableContent);
+ // XXX Why don't we check \n in pre-formated text node here?
+ }
+
+ // Finding the real start for this point unless current line starts after
+ // <br> element. Look up the tree for as long as we are the first node in
+ // the container (typically, start of nearest block ancestor), and as long
+ // as we haven't hit the body node.
+ for (nsIContent* nearContent = GetPreviousEditableHTMLNodeInBlock(point);
+ !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
+ point.GetContainer()->GetParentNode();
+ nearContent = GetPreviousEditableHTMLNodeInBlock(point)) {
+ // Don't keep looking up if we have found a blockquote element to act on
+ // when we handle outdent.
+ // XXX Sounds like this is hacky. If possible, it should be check in
+ // outdent handler for consistency between edit sub-actions.
+ // We should check Chromium's behavior of outdent when Selection
+ // starts from `<blockquote>` and starts from first child of
+ // `<blockquote>`.
+ if (aEditSubAction == EditSubAction::eOutdent &&
+ point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
+ break;
+ }
+
+ // Don't walk past the editable section. Note that we need to check
+ // before walking up to a parent because we need to return the parent
+ // object, so the parent itself might not be in the editable area, but
+ // it's OK if we're not performing a block-level action.
+ // XXX Here is too slow. Let's cache active editing host first, then,
+ // compair with container of the point when we climb up the tree.
+ bool blockLevelAction =
+ aEditSubAction == EditSubAction::eIndent ||
+ aEditSubAction == EditSubAction::eOutdent ||
+ aEditSubAction == EditSubAction::eSetOrClearAlignment ||
+ aEditSubAction == EditSubAction::eCreateOrRemoveBlock;
+ if (!IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode()) &&
+ (blockLevelAction || !IsDescendantOfEditorRoot(point.GetContainer()))) {
+ break;
+ }
+
+ point.Set(point.GetContainer());
+ }
+ return point;
+}
+
+template <typename PT, typename RT>
+EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+ const RangeBoundaryBase<PT, RT>& aPoint) const {
+ if (NS_WARN_IF(!aPoint.IsSet())) {
+ return EditorDOMPoint();
+ }
+
+ EditorDOMPoint point(aPoint);
+ // Start scanning from the container node if aPoint is in a text node.
+ // XXX Perhaps, IsInDataNode() must be expected.
+ if (point.IsInTextNode()) {
+ if (!point.GetContainer()->GetParentNode()) {
+ // Okay, can't promote any further
+ // XXX Why don't we return end of the text node?
+ return point;
+ }
+ // want to be after the text node
+ point.SetAfter(point.GetContainer());
+ NS_WARNING_ASSERTION(point.IsSet(), "Failed to set to after the text node");
+ }
+
+ // Look ahead through any further inline nodes that aren't across a <br> from
+ // us, and that are enclosed in the same block.
+ // XXX Currently, we stop block-extending when finding visible <br> element.
+ // This might be different from "block-extend" of execCommand spec.
+ // However, the spec is really unclear.
+ // XXX Probably, scanning only editable nodes is wrong for
+ // EditSubAction::eCreateOrRemoveBlock because it might be better to wrap
+ // existing inline elements even if it's non-editable. For example,
+ // following examples with insertParagraph causes different result:
+ // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
+ // * <div contenteditable>foo[]<b>bar</b></div>
+ // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
+ // Only in the first case, after the caret position isn't wrapped with
+ // new <div> element.
+ for (nsIContent* nextEditableContent = GetNextEditableHTMLNodeInBlock(point);
+ nextEditableContent &&
+ !HTMLEditUtils::IsBlockElement(*nextEditableContent) &&
+ nextEditableContent->GetParent();
+ nextEditableContent = GetNextEditableHTMLNodeInBlock(point)) {
+ point.SetAfter(nextEditableContent);
+ if (NS_WARN_IF(!point.IsSet())) {
+ break;
+ }
+ if (IsVisibleBRElement(nextEditableContent)) {
+ break;
+ }
+
+ // Check for newlines in pre-formatted text nodes.
+ if (nextEditableContent->IsText() &&
+ EditorUtils::IsContentPreformatted(*nextEditableContent)) {
+ nsAutoString textContent;
+ nextEditableContent->GetAsText()->GetData(textContent);
+ int32_t newlinePos = textContent.FindChar(nsCRT::LF);
+ if (newlinePos >= 0) {
+ if (static_cast<uint32_t>(newlinePos) + 1 == textContent.Length()) {
+ // No need for special processing if the newline is at the end.
+ break;
+ }
+ return EditorDOMPoint(nextEditableContent, newlinePos + 1);
+ }
+ }
+ }
+
+ // Finding the real end for this point unless current line ends with a <br>
+ // element. Look up the tree for as long as we are the last node in the
+ // container (typically, block node), and as long as we haven't hit the body
+ // node.
+ for (nsIContent* nearContent = GetNextEditableHTMLNodeInBlock(point);
+ !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
+ point.GetContainer()->GetParentNode();
+ nearContent = GetNextEditableHTMLNodeInBlock(point)) {
+ // Don't walk past the editable section. Note that we need to check before
+ // walking up to a parent because we need to return the parent object, so
+ // the parent itself might not be in the editable area, but it's OK.
+ // XXX Maybe returning parent of editing host is really error prone since
+ // everybody need to check whether the end point is in editing host
+ // when they touch there.
+ // XXX Here is too slow. Let's cache active editing host first, then,
+ // compair with container of the point when we climb up the tree.
+ if (!IsDescendantOfEditorRoot(point.GetContainer()) &&
+ !IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode())) {
+ break;
+ }
+
+ point.SetAfter(point.GetContainer());
+ if (NS_WARN_IF(!point.IsSet())) {
+ break;
+ }
+ }
+ return point;
+}
+
+void HTMLEditor::GetSelectionRangesExtendedToIncludeAdjuscentWhiteSpaces(
+ nsTArray<RefPtr<nsRange>>& aOutArrayOfRanges) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aOutArrayOfRanges.IsEmpty());
+
+ aOutArrayOfRanges.SetCapacity(SelectionRefPtr()->RangeCount());
+ for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); i++) {
+ const nsRange* selectionRange = SelectionRefPtr()->GetRangeAt(i);
+ MOZ_ASSERT(selectionRange);
+
+ RefPtr<nsRange> extendedRange =
+ CreateRangeIncludingAdjuscentWhiteSpaces(*selectionRange);
+ if (!extendedRange) {
+ extendedRange = selectionRange->CloneRange();
+ }
+
+ aOutArrayOfRanges.AppendElement(extendedRange);
+ }
+}
+void HTMLEditor::GetSelectionRangesExtendedToHardLineStartAndEnd(
+ nsTArray<RefPtr<nsRange>>& aOutArrayOfRanges,
+ EditSubAction aEditSubAction) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aOutArrayOfRanges.IsEmpty());
+
+ aOutArrayOfRanges.SetCapacity(SelectionRefPtr()->RangeCount());
+ for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); i++) {
+ // Make a new adjusted range to represent the appropriate block content.
+ // The basic idea is to push out the range endpoints to truly enclose the
+ // blocks that we will affect. This call alters opRange.
+ nsRange* selectionRange = SelectionRefPtr()->GetRangeAt(i);
+ MOZ_ASSERT(selectionRange);
+
+ RefPtr<nsRange> extendedRange = CreateRangeExtendedToHardLineStartAndEnd(
+ *selectionRange, aEditSubAction);
+ if (!extendedRange) {
+ extendedRange = selectionRange->CloneRange();
+ }
+
+ aOutArrayOfRanges.AppendElement(extendedRange);
+ }
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+ RangeBoundaryBase<SPT, SRT>& aStartRef,
+ RangeBoundaryBase<EPT, ERT>& aEndRef) const {
+ // MOOSE major hack:
+ // The GetWhiteSpaceEndPoint(), GetCurrentHardLineStartPoint() and
+ // GetCurrentHardLineEndPoint() don't really do the right thing for
+ // collapsed ranges inside block elements that contain nothing but a solo
+ // <br>. It's easier/ to put a workaround here than to revamp them. :-(
+ // XXX This sounds odd in the cases using `GetWhiteSpaceEndPoint()`. Don't
+ // we hack something in the edit sub-action handlers?
+ if (aStartRef != aEndRef) {
+ return;
+ }
+
+ if (!aStartRef.Container()->IsContent()) {
+ return;
+ }
+
+ Element* blockElement = HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *aStartRef.Container()->AsContent());
+ if (!blockElement) {
+ return;
+ }
+
+ Element* editingHost = GetActiveEditingHost();
+ if (NS_WARN_IF(!editingHost)) {
+ return;
+ }
+
+ // Make sure we don't go higher than our root element in the content tree
+ if (editingHost->IsInclusiveDescendantOf(blockElement)) {
+ return;
+ }
+
+ if (IsEmptyNode(*blockElement, true, false)) {
+ aStartRef = {blockElement, 0u};
+ aEndRef = {blockElement, blockElement->Length()};
+ }
+}
+
+already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const AbstractRange& aAbstractRange) {
+ if (!aAbstractRange.IsPositioned()) {
+ return nullptr;
+ }
+ return CreateRangeIncludingAdjuscentWhiteSpaces(aAbstractRange.StartRef(),
+ aAbstractRange.EndRef());
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+ const RangeBoundaryBase<SPT, SRT>& aStartRef,
+ const RangeBoundaryBase<EPT, ERT>& aEndRef) {
+ if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
+ return nullptr;
+ }
+
+ if (!aStartRef.Container()->IsContent() ||
+ !aEndRef.Container()->IsContent()) {
+ return nullptr;
+ }
+
+ RangeBoundaryBase<SPT, SRT> startRef(aStartRef);
+ RangeBoundaryBase<EPT, ERT> endRef(aEndRef);
+ SelectBRElementIfCollapsedInEmptyBlock(startRef, endRef);
+
+ // For text actions, we want to look backwards (or forwards, as
+ // appropriate) for additional white-space or nbsp's. We may have to act
+ // on these later even though they are outside of the initial selection.
+ // Even if they are in another node!
+ // XXX Although the comment mentioned that this may scan other text nodes,
+ // GetWhiteSpaceEndPoint() scans only in given container node.
+ // XXX Looks like that we should make GetWhiteSpaceEndPoint() be a
+ // instance method and stop scanning white-spaces when it reaches
+ // active editing host.
+ EditorDOMPoint startPoint = HTMLEditor::GetWhiteSpaceEndPoint(
+ startRef, HTMLEditor::ScanDirection::Backward);
+ if (!IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+ return nullptr;
+ }
+ EditorDOMPoint endPoint = HTMLEditor::GetWhiteSpaceEndPoint(
+ endRef, HTMLEditor::ScanDirection::Forward);
+ EditorRawDOMPoint lastRawPoint(endPoint);
+ lastRawPoint.RewindOffset();
+ if (!IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+ return nullptr;
+ }
+
+ RefPtr<nsRange> range =
+ nsRange::Create(startPoint.ToRawRangeBoundary(),
+ endPoint.ToRawRangeBoundary(), IgnoreErrors());
+ NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
+ return range.forget();
+}
+
+already_AddRefed<nsRange> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const AbstractRange& aAbstractRange, EditSubAction aEditSubAction) const {
+ if (!aAbstractRange.IsPositioned()) {
+ return nullptr;
+ }
+ return CreateRangeExtendedToHardLineStartAndEnd(
+ aAbstractRange.StartRef(), aAbstractRange.EndRef(), aEditSubAction);
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<nsRange> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+ const RangeBoundaryBase<SPT, SRT>& aStartRef,
+ const RangeBoundaryBase<EPT, ERT>& aEndRef,
+ EditSubAction aEditSubAction) const {
+ if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
+ return nullptr;
+ }
+
+ RangeBoundaryBase<SPT, SRT> startRef(aStartRef);
+ RangeBoundaryBase<EPT, ERT> endRef(aEndRef);
+ SelectBRElementIfCollapsedInEmptyBlock(startRef, endRef);
+
+ // Make a new adjusted range to represent the appropriate block content.
+ // This is tricky. The basic idea is to push out the range endpoints to
+ // truly enclose the blocks that we will affect.
+
+ // Make sure that the new range ends up to be in the editable section.
+ // XXX Looks like that this check wastes the time. Perhaps, we should
+ // implement a method which checks both two DOM points in the editor
+ // root.
+
+ EditorDOMPoint startPoint =
+ GetCurrentHardLineStartPoint(startRef, aEditSubAction);
+ // XXX GetCurrentHardLineStartPoint() may return point of editing
+ // host. Perhaps, we should change it and stop checking it here
+ // since this check may be expensive.
+ if (!IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+ return nullptr;
+ }
+ EditorDOMPoint endPoint = GetCurrentHardLineEndPoint(endRef);
+ EditorRawDOMPoint lastRawPoint(endPoint);
+ lastRawPoint.RewindOffset();
+ // XXX GetCurrentHardLineEndPoint() may return point of editing host.
+ // Perhaps, we should change it and stop checking it here since this
+ // check may be expensive.
+ if (!IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+ return nullptr;
+ }
+
+ RefPtr<nsRange> range =
+ nsRange::Create(startPoint.ToRawRangeBoundary(),
+ endPoint.ToRawRangeBoundary(), IgnoreErrors());
+ NS_WARNING_ASSERTION(range, "nsRange::Create() failed");
+ return range.forget();
+}
+
+nsresult HTMLEditor::SplitInlinesAndCollectEditTargetNodes(
+ nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
+ nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
+ EditSubAction aEditSubAction,
+ CollectNonEditableNodes aCollectNonEditableNodes) {
+ nsresult rv = SplitTextNodesAtRangeEnd(aArrayOfRanges);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::SplitTextNodesAtRangeEnd() failed");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = SplitParentInlineElementsAtRangeEdges(aArrayOfRanges);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = CollectEditTargetNodes(aArrayOfRanges, aOutArrayOfContents,
+ aEditSubAction, aCollectNonEditableNodes);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollectEditTargetNodes() failed");
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = MaybeSplitElementsAtEveryBRElement(aOutArrayOfContents, aEditSubAction);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::MaybeSplitElementsAtEveryBRElement() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::SplitTextNodesAtRangeEnd(
+ nsTArray<RefPtr<nsRange>>& aArrayOfRanges) {
+ // Split text nodes. This is necessary, since given ranges may end in text
+ // nodes in case where part of a pre-formatted elements needs to be moved.
+ ErrorResult error;
+ IgnoredErrorResult ignoredError;
+ for (RefPtr<nsRange>& range : aArrayOfRanges) {
+ EditorDOMPoint atEnd(range->EndRef());
+ if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode()) {
+ continue;
+ }
+
+ if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) {
+ // Split the text node.
+ nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atEnd, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error = NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (error.Failed()) {
+ NS_WARNING_ASSERTION(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED),
+ "HTMLEditor::SplitNodeWithTransaction() failed");
+ return error.StealNSResult();
+ }
+
+ // Correct the range.
+ // The new end parent becomes the parent node of the text.
+ EditorRawDOMPoint atContainerOfSplitNode(atEnd.GetContainer());
+ MOZ_ASSERT(!range->IsInSelection());
+ range->SetEnd(atContainerOfSplitNode, ignoredError);
+ NS_WARNING_ASSERTION(!ignoredError.Failed(),
+ "nsRange::SetEnd() failed, but ignored");
+ ignoredError.SuppressException();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::SplitParentInlineElementsAtRangeEdges(
+ nsTArray<RefPtr<nsRange>>& aArrayOfRanges) {
+ nsTArray<OwningNonNull<RangeItem>> rangeItemArray;
+ rangeItemArray.AppendElements(aArrayOfRanges.Length());
+
+ // First register ranges for special editor gravity
+ for (auto& rangeItem : rangeItemArray) {
+ rangeItem = new RangeItem();
+ rangeItem->StoreRange(*aArrayOfRanges[0]);
+ RangeUpdaterRef().RegisterRangeItem(*rangeItem);
+ aArrayOfRanges.RemoveElementAt(0);
+ }
+ // Now bust up inlines.
+ nsresult rv = NS_OK;
+ for (auto& item : Reversed(rangeItemArray)) {
+ // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
+ rv = SplitParentInlineElementsAtRangeEdges(MOZ_KnownLive(*item));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
+ break;
+ }
+ }
+ // Then unregister the ranges
+ for (auto& item : rangeItemArray) {
+ RangeUpdaterRef().DropRangeItem(item);
+ RefPtr<nsRange> range = item->GetRange();
+ if (range) {
+ aArrayOfRanges.AppendElement(range);
+ }
+ }
+
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::CollectEditTargetNodes(
+ nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
+ nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
+ EditSubAction aEditSubAction,
+ CollectNonEditableNodes aCollectNonEditableNodes) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // Gather up a list of all the nodes
+ for (auto& range : aArrayOfRanges) {
+ DOMSubtreeIterator iter;
+ nsresult rv = iter.Init(*range);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("DOMSubtreeIterator::Init() failed");
+ return rv;
+ }
+ if (aOutArrayOfContents.IsEmpty()) {
+ iter.AppendAllNodesToArray(aOutArrayOfContents);
+ } else {
+ AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfTopChildren;
+ iter.AppendNodesToArray(
+ +[](nsINode& aNode, void* aArray) -> bool {
+ MOZ_ASSERT(aArray);
+ return !static_cast<nsTArray<OwningNonNull<nsIContent>>*>(aArray)
+ ->Contains(&aNode);
+ },
+ arrayOfTopChildren, &aOutArrayOfContents);
+ aOutArrayOfContents.AppendElements(std::move(arrayOfTopChildren));
+ }
+ if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
+ for (size_t i = aOutArrayOfContents.Length(); i > 0; --i) {
+ if (!EditorUtils::IsEditableContent(aOutArrayOfContents[i - 1],
+ EditorType::HTML)) {
+ aOutArrayOfContents.RemoveElementAt(i - 1);
+ }
+ }
+ }
+ }
+
+ switch (aEditSubAction) {
+ case EditSubAction::eCreateOrRemoveBlock:
+ // Certain operations should not act on li's and td's, but rather inside
+ // them. Alter the list as needed.
+ for (int32_t i = aOutArrayOfContents.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsIContent> content = aOutArrayOfContents[i];
+ if (HTMLEditUtils::IsListItem(content)) {
+ aOutArrayOfContents.RemoveElementAt(i);
+ CollectChildren(*content, aOutArrayOfContents, i,
+ CollectListChildren::Yes, CollectTableChildren::Yes,
+ aCollectNonEditableNodes);
+ }
+ }
+ // Empty text node shouldn't be selected if unnecessary
+ for (int32_t i = aOutArrayOfContents.Length() - 1; i >= 0; i--) {
+ if (Text* text = aOutArrayOfContents[i]->GetAsText()) {
+ // Don't select empty text except to empty block
+ if (!IsVisibleTextNode(*text)) {
+ aOutArrayOfContents.RemoveElementAt(i);
+ }
+ }
+ }
+ break;
+ case EditSubAction::eCreateOrChangeList: {
+ for (size_t i = aOutArrayOfContents.Length(); i > 0; i--) {
+ // Scan for table elements. If we find table elements other than
+ // table, replace it with a list of any editable non-table content
+ // because if a selection range starts from end in a table-cell and
+ // ends at or starts from outside the `<table>`, we need to make
+ // lists in each selected table-cells.
+ OwningNonNull<nsIContent> content = aOutArrayOfContents[i - 1];
+ if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) {
+ // XXX aCollectNonEditableNodes is ignored here. Maybe a bug.
+ aOutArrayOfContents.RemoveElementAt(i - 1);
+ CollectChildren(content, aOutArrayOfContents, i - 1,
+ CollectListChildren::No, CollectTableChildren::Yes,
+ CollectNonEditableNodes::Yes);
+ }
+ }
+ // If there is only one node in the array, and it is a `<div>`,
+ // `<blockquote>` or a list element, then look inside of it until we
+ // find inner list or content.
+ if (aOutArrayOfContents.Length() != 1) {
+ break;
+ }
+ Element* deepestDivBlockquoteOrListElement =
+ GetDeepestEditableOnlyChildDivBlockquoteOrListElement(
+ aOutArrayOfContents[0]);
+ if (!deepestDivBlockquoteOrListElement) {
+ break;
+ }
+ if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
+ nsGkAtoms::div, nsGkAtoms::blockquote)) {
+ aOutArrayOfContents.Clear();
+ // XXX Before we're called, non-editable nodes are ignored. However,
+ // we may append non-editable nodes here.
+ CollectChildren(*deepestDivBlockquoteOrListElement, aOutArrayOfContents,
+ 0, CollectListChildren::No, CollectTableChildren::No,
+ CollectNonEditableNodes::Yes);
+ break;
+ }
+ aOutArrayOfContents.ReplaceElementAt(
+ 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
+ break;
+ }
+ case EditSubAction::eOutdent:
+ case EditSubAction::eIndent:
+ case EditSubAction::eSetPositionToAbsolute:
+ // Indent/outdent already do something special for list items, but we
+ // still need to make sure we don't act on table elements
+ for (int32_t i = aOutArrayOfContents.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsIContent> content = aOutArrayOfContents[i];
+ if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) {
+ aOutArrayOfContents.RemoveElementAt(i);
+ CollectChildren(*content, aOutArrayOfContents, i,
+ CollectListChildren::Yes, CollectTableChildren::Yes,
+ aCollectNonEditableNodes);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Outdent should look inside of divs.
+ if (aEditSubAction == EditSubAction::eOutdent && !IsCSSEnabled()) {
+ for (int32_t i = aOutArrayOfContents.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsIContent> content = aOutArrayOfContents[i];
+ if (content->IsHTMLElement(nsGkAtoms::div)) {
+ aOutArrayOfContents.RemoveElementAt(i);
+ CollectChildren(*content, aOutArrayOfContents, i,
+ CollectListChildren::No, CollectTableChildren::No,
+ aCollectNonEditableNodes);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLEditor::MaybeSplitElementsAtEveryBRElement(
+ nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
+ EditSubAction aEditSubAction) {
+ // Post-process the list to break up inline containers that contain br's, but
+ // only for operations that might care, like making lists or paragraphs
+ switch (aEditSubAction) {
+ case EditSubAction::eCreateOrRemoveBlock:
+ case EditSubAction::eMergeBlockContents:
+ case EditSubAction::eCreateOrChangeList:
+ case EditSubAction::eSetOrClearAlignment:
+ case EditSubAction::eSetPositionToAbsolute:
+ case EditSubAction::eIndent:
+ case EditSubAction::eOutdent:
+ for (int32_t i = aArrayOfContents.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsIContent>& content = aArrayOfContents[i];
+ if (HTMLEditUtils::IsInlineElement(content) &&
+ HTMLEditUtils::IsContainerNode(content) && !content->IsText()) {
+ AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfInlineContents;
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
+ // alive.
+ nsresult rv = SplitElementsAtEveryBRElement(MOZ_KnownLive(content),
+ arrayOfInlineContents);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::SplitElementsAtEveryBRElement() failed");
+ return rv;
+ }
+
+ // Put these nodes in aArrayOfContents, replacing the current node
+ aArrayOfContents.RemoveElementAt(i);
+ aArrayOfContents.InsertElementsAt(i, arrayOfInlineContents);
+ }
+ }
+ return NS_OK;
+ default:
+ return NS_OK;
+ }
+}
+
+Element* HTMLEditor::GetParentListElementAtSelection() const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
+
+ for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); ++i) {
+ nsRange* range = SelectionRefPtr()->GetRangeAt(i);
+ for (nsINode* parent = range->GetClosestCommonInclusiveAncestor(); parent;
+ parent = parent->GetParentNode()) {
+ if (HTMLEditUtils::IsAnyListElement(parent)) {
+ return parent->AsElement();
+ }
+ }
+ }
+ return nullptr;
+}
+
+Element* HTMLEditor::GetDeepestEditableOnlyChildDivBlockquoteOrListElement(
+ nsINode& aNode) {
+ if (!aNode.IsElement()) {
+ return nullptr;
+ }
+ // XXX If aNode is non-editable, shouldn't we return nullptr here?
+ Element* parentElement = nullptr;
+ for (nsIContent* content = aNode.AsContent();
+ content && content->IsElement() &&
+ (content->IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::blockquote) ||
+ HTMLEditUtils::IsAnyListElement(content));
+ content = content->GetFirstChild()) {
+ if (CountEditableChildren(content) != 1) {
+ return content->AsElement();
+ }
+ parentElement = content->AsElement();
+ }
+ return parentElement;
+}
+
+nsresult HTMLEditor::SplitParentInlineElementsAtRangeEdges(
+ RangeItem& aRangeItem) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (!aRangeItem.IsCollapsed()) {
+ nsCOMPtr<nsIContent> mostAncestorInlineContentAtEnd =
+ GetMostAncestorInlineElement(*aRangeItem.mEndContainer);
+
+ if (mostAncestorInlineContentAtEnd) {
+ SplitNodeResult splitEndInlineResult = SplitNodeDeepWithTransaction(
+ *mostAncestorInlineContentAtEnd, aRangeItem.EndPoint(),
+ SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitEndInlineResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+ return splitEndInlineResult.Rv();
+ }
+ EditorRawDOMPoint splitPointAtEnd(splitEndInlineResult.SplitPoint());
+ if (!splitPointAtEnd.IsSet()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) didn't return split point");
+ return NS_ERROR_FAILURE;
+ }
+ aRangeItem.mEndContainer = splitPointAtEnd.GetContainer();
+ aRangeItem.mEndOffset = splitPointAtEnd.Offset();
+ }
+ }
+
+ nsCOMPtr<nsIContent> mostAncestorInlineContentAtStart =
+ GetMostAncestorInlineElement(*aRangeItem.mStartContainer);
+
+ if (mostAncestorInlineContentAtStart) {
+ SplitNodeResult splitStartInlineResult = SplitNodeDeepWithTransaction(
+ *mostAncestorInlineContentAtStart, aRangeItem.StartPoint(),
+ SplitAtEdges::eDoNotCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitStartInlineResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) failed");
+ return splitStartInlineResult.Rv();
+ }
+ // XXX If we split only here because of collapsed range, we're modifying
+ // only start point of aRangeItem. Shouldn't we modify end point here
+ // if it's collapsed?
+ EditorRawDOMPoint splitPointAtStart(splitStartInlineResult.SplitPoint());
+ if (!splitPointAtStart.IsSet()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eDoNotCreateEmptyContainer) didn't return split point");
+ return NS_ERROR_FAILURE;
+ }
+ aRangeItem.mStartContainer = splitPointAtStart.GetContainer();
+ aRangeItem.mStartOffset = splitPointAtStart.Offset();
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLEditor::SplitElementsAtEveryBRElement(
+ nsIContent& aMostAncestorToBeSplit,
+ nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // First build up a list of all the break nodes inside the inline container.
+ AutoTArray<OwningNonNull<HTMLBRElement>, 24> arrayOfBRElements;
+ DOMIterator iter(aMostAncestorToBeSplit);
+ iter.AppendAllNodesToArray(arrayOfBRElements);
+
+ // If there aren't any breaks, just put inNode itself in the array
+ if (arrayOfBRElements.IsEmpty()) {
+ aOutArrayOfContents.AppendElement(aMostAncestorToBeSplit);
+ return NS_OK;
+ }
+
+ // Else we need to bust up aMostAncestorToBeSplit along all the breaks
+ nsCOMPtr<nsIContent> nextContent = &aMostAncestorToBeSplit;
+ for (OwningNonNull<HTMLBRElement>& brElement : arrayOfBRElements) {
+ EditorDOMPoint atBRNode(brElement);
+ if (NS_WARN_IF(!atBRNode.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
+ *nextContent, atBRNode, SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitNodeResult.Failed()) {
+ NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+
+ // Put previous node at the split point.
+ if (splitNodeResult.GetPreviousNode()) {
+ // Might not be a left node. A break might have been at the very
+ // beginning of inline container, in which case
+ // SplitNodeDeepWithTransaction() would not actually split anything.
+ aOutArrayOfContents.AppendElement(*splitNodeResult.GetPreviousNode());
+ }
+
+ // Move break outside of container and also put in node list
+ EditorDOMPoint atNextNode(splitNodeResult.GetNextNode());
+ // MOZ_KnownLive because 'arrayOfBRElements' is guaranteed to keep it alive.
+ nsresult rv = MoveNodeWithTransaction(MOZ_KnownLive(brElement), atNextNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ aOutArrayOfContents.AppendElement(brElement);
+
+ nextContent = splitNodeResult.GetNextNode();
+ }
+
+ // Now tack on remaining next node.
+ aOutArrayOfContents.AppendElement(*nextContent);
+
+ return NS_OK;
+}
+
+nsIContent* HTMLEditor::GetMostAncestorInlineElement(nsINode& aNode) const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (!aNode.IsContent() || HTMLEditUtils::IsBlockElement(*aNode.AsContent())) {
+ return nullptr;
+ }
+
+ Element* host = GetActiveEditingHost();
+ if (NS_WARN_IF(!host)) {
+ return nullptr;
+ }
+
+ // If aNode is the editing host itself, there is no modifiable inline
+ // parent.
+ if (&aNode == host) {
+ return nullptr;
+ }
+
+ // If aNode is outside of the <body> element, we don't support to edit
+ // such elements for now.
+ // XXX This should be MOZ_ASSERT after fixing bug 1413131 for avoiding
+ // calling this expensive method.
+ if (NS_WARN_IF(!EditorUtils::IsDescendantOf(aNode, *host))) {
+ return nullptr;
+ }
+
+ if (!aNode.GetParent()) {
+ return aNode.AsContent();
+ }
+
+ // Looks for the highest inline parent in the editing host.
+ nsIContent* topMostInlineContent = aNode.AsContent();
+ for (nsIContent* content : aNode.AncestorsOfType<nsIContent>()) {
+ if (content == host || !HTMLEditUtils::IsInlineElement(*content)) {
+ break;
+ }
+ topMostInlineContent = content;
+ }
+ return topMostInlineContent;
+}
+
+// static
+void HTMLEditor::MakeTransitionList(
+ const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
+ nsTArray<bool>& aTransitionArray) {
+ nsINode* prevParent = nullptr;
+ aTransitionArray.EnsureLengthAtLeast(aArrayOfContents.Length());
+ for (uint32_t i = 0; i < aArrayOfContents.Length(); i++) {
+ aTransitionArray[i] = aArrayOfContents[i]->GetParentNode() != prevParent;
+ prevParent = aArrayOfContents[i]->GetParentNode();
+ }
+}
+
+Element* HTMLEditor::GetNearestAncestorListItemElement(
+ nsIContent& aContent) const {
+ // XXX Why don't we test whether aContent is in an editing host?
+ if (HTMLEditUtils::IsListItem(&aContent)) {
+ return aContent.AsElement();
+ }
+
+ // XXX This loop is too expensive since calling IsDescendantOfEditorRoot()
+ // a lot. Rewrite this with caching active editing host and check
+ // whether we reach it or not.
+ for (Element* parentElement = aContent.GetParentElement();
+ parentElement && IsDescendantOfEditorRoot(parentElement) &&
+ !HTMLEditUtils::IsAnyTableElement(parentElement);
+ parentElement = parentElement->GetParentElement()) {
+ if (HTMLEditUtils::IsListItem(parentElement)) {
+ return parentElement;
+ }
+ }
+ return nullptr;
+}
+
+nsresult HTMLEditor::HandleInsertParagraphInHeadingElement(Element& aHeader,
+ nsINode& aNode,
+ int32_t aOffset) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ // Remember where the header is
+ nsCOMPtr<nsINode> headerParent = aHeader.GetParentNode();
+ int32_t offset = headerParent ? headerParent->ComputeIndexOf(&aHeader) : -1;
+
+ // Get ws code to adjust any ws
+ nsCOMPtr<nsINode> node = &aNode;
+ nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
+ *this, address_of(node), &aOffset);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() failed");
+ return rv;
+ }
+ if (!node->IsContent()) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() returned "
+ "Document or something non-content node");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Split the header
+ SplitNodeResult splitHeaderResult =
+ SplitNodeDeepWithTransaction(aHeader, EditorDOMPoint(node, aOffset),
+ SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ splitHeaderResult.Succeeded(),
+ "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
+
+ // If the previous heading of split point is empty, put a padding <br>
+ // element for empty last line into it.
+ nsCOMPtr<nsIContent> prevItem = GetPriorHTMLSibling(&aHeader);
+ if (prevItem) {
+ MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*prevItem));
+ if (IsEmptyNode(*prevItem)) {
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(
+ EditorDOMPoint(prevItem, 0));
+ if (createPaddingBRResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
+ ") failed");
+ return createPaddingBRResult.Rv();
+ }
+ }
+ }
+
+ // If the new (righthand) header node is empty, delete it
+ if (IsEmptyBlockElement(aHeader, IgnoreSingleBR::Yes)) {
+ nsresult rv = DeleteNodeWithTransaction(aHeader);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ // Layout tells the caret to blink in a weird place if we don't place a
+ // break after the header.
+ nsCOMPtr<nsIContent> sibling;
+ if (aHeader.GetNextSibling()) {
+ sibling = GetNextHTMLSibling(aHeader.GetNextSibling());
+ }
+ if (!sibling || !sibling->IsHTMLElement(nsGkAtoms::br)) {
+ TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
+ mTypeInState->ClearAllProps();
+
+ // Create a paragraph
+ nsStaticAtom& paraAtom = DefaultParagraphSeparatorTagName();
+ // We want a wrapper element even if we separate with <br>
+ EditorDOMPoint nextToHeader(headerParent, offset + 1);
+ RefPtr<Element> pOrDivElement = CreateNodeWithTransaction(
+ &paraAtom == nsGkAtoms::br ? *nsGkAtoms::p : MOZ_KnownLive(paraAtom),
+ nextToHeader);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!pOrDivElement) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Append a <br> to it
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(EditorDOMPoint(pOrDivElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set selection to before the break
+ nsresult rv = CollapseSelectionToStartOf(*pOrDivElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ EditorRawDOMPoint afterSibling(EditorRawDOMPoint::After(*sibling));
+ if (NS_WARN_IF(!afterSibling.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ // Put selection after break
+ rv = CollapseSelectionTo(afterSibling);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+
+ // Put selection at front of righthand heading
+ rv = CollapseSelectionToStartOf(aHeader);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::HandleInsertParagraphInParagraph(
+ Element& aParentDivOrP) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+
+ EditorDOMPoint atStartOfSelection(firstRange->StartRef());
+ if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
+ return EditActionResult(NS_ERROR_FAILURE);
+ }
+ MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
+
+ // We shouldn't create new anchor element which has non-empty href unless
+ // splitting middle of it because we assume that users don't want to create
+ // *same* anchor element across two or more paragraphs in most cases.
+ // So, adjust selection start if it's edge of anchor element(s).
+ // XXX We don't support white-space collapsing in these cases since it needs
+ // some additional work with WhiteSpaceVisibilityKeeper but it's not usual
+ // case. E.g., |<a href="foo"><b>foo []</b> </a>|
+ if (atStartOfSelection.IsStartOfContainer()) {
+ for (nsIContent* container = atStartOfSelection.GetContainerAsContent();
+ container && container != &aParentDivOrP;
+ container = container->GetParent()) {
+ if (HTMLEditUtils::IsLink(container)) {
+ // Found link should be only in right node. So, we shouldn't split
+ // it.
+ atStartOfSelection.Set(container);
+ // Even if we found an anchor element, don't break because DOM API
+ // allows to nest anchor elements.
+ }
+ // If the container is middle of its parent, stop adjusting split point.
+ if (container->GetPreviousSibling()) {
+ // XXX Should we check if previous sibling is visible content?
+ // E.g., should we ignore comment node, invisible <br> element?
+ break;
+ }
+ }
+ }
+ // We also need to check if selection is at invisible <br> element at end
+ // of an <a href="foo"> element because editor inserts a <br> element when
+ // user types Enter key after a white-space which is at middle of
+ // <a href="foo"> element and when setting selection at end of the element,
+ // selection becomes referring the <br> element. We may need to change this
+ // behavior later if it'd be standardized.
+ else if (atStartOfSelection.IsEndOfContainer() ||
+ atStartOfSelection.IsBRElementAtEndOfContainer()) {
+ // If there are 2 <br> elements, the first <br> element is visible. E.g.,
+ // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
+ // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
+ // we should not split the <a> element and ignore inline elements in it.
+ bool foundBRElement = atStartOfSelection.IsBRElementAtEndOfContainer();
+ for (nsIContent* container = atStartOfSelection.GetContainerAsContent();
+ container && container != &aParentDivOrP;
+ container = container->GetParent()) {
+ if (HTMLEditUtils::IsLink(container)) {
+ // Found link should be only in left node. So, we shouldn't split it.
+ atStartOfSelection.SetAfter(container);
+ // Even if we found an anchor element, don't break because DOM API
+ // allows to nest anchor elements.
+ }
+ // If the container is middle of its parent, stop adjusting split point.
+ if (nsIContent* nextSibling = container->GetNextSibling()) {
+ if (foundBRElement) {
+ // If we've already found a <br> element, we assume found node is
+ // visible <br> or something other node.
+ // XXX Should we check if non-text data node like comment?
+ break;
+ }
+
+ // XXX Should we check if non-text data node like comment?
+ if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
+ break;
+ }
+ foundBRElement = true;
+ }
+ }
+ }
+
+ bool doesCRCreateNewP = GetReturnInParagraphCreatesNewParagraph();
+ bool splitAfterNewBR = false;
+ nsCOMPtr<nsIContent> brContent;
+
+ EditorDOMPoint pointToSplitParentDivOrP(atStartOfSelection);
+
+ EditorDOMPoint pointToInsertBR;
+ if (doesCRCreateNewP && atStartOfSelection.GetContainer() == &aParentDivOrP) {
+ // We are at the edges of the block, so, we don't need to create new <br>.
+ brContent = nullptr;
+ } else if (atStartOfSelection.IsInTextNode()) {
+ // at beginning of text node?
+ if (atStartOfSelection.IsStartOfContainer()) {
+ // is there a BR prior to it?
+ brContent = GetPriorHTMLSibling(atStartOfSelection.GetContainer());
+ if (!brContent || !IsVisibleBRElement(brContent) ||
+ EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent)) {
+ pointToInsertBR.Set(atStartOfSelection.GetContainer());
+ brContent = nullptr;
+ }
+ } else if (atStartOfSelection.IsEndOfContainer()) {
+ // we're at the end of text node...
+ // is there a BR after to it?
+ brContent = GetNextHTMLSibling(atStartOfSelection.GetContainer());
+ if (!brContent || !IsVisibleBRElement(brContent) ||
+ EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent)) {
+ pointToInsertBR.SetAfter(atStartOfSelection.GetContainer());
+ NS_WARNING_ASSERTION(
+ pointToInsertBR.IsSet(),
+ "Failed to set to after the container of selection start");
+ brContent = nullptr;
+ }
+ } else {
+ if (doesCRCreateNewP) {
+ ErrorResult error;
+ nsCOMPtr<nsIContent> newLeftDivOrP =
+ SplitNodeWithTransaction(pointToSplitParentDivOrP, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error = NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (error.Failed()) {
+ NS_WARNING_ASSERTION(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED),
+ "HTMLEditor::SplitNodeWithTransaction() failed");
+ return EditActionResult(error.StealNSResult());
+ }
+ pointToSplitParentDivOrP.SetToEndOf(newLeftDivOrP);
+ }
+
+ // We need to put new <br> after the left node if given node was split
+ // above.
+ pointToInsertBR.Set(pointToSplitParentDivOrP.GetContainer());
+ DebugOnly<bool> advanced = pointToInsertBR.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced,
+ "Failed to advance offset to after the container "
+ "of selection start");
+ }
+ } else {
+ // not in a text node.
+ // is there a BR prior to it?
+ nsCOMPtr<nsIContent> nearContent =
+ GetPreviousEditableHTMLNode(atStartOfSelection);
+ if (!nearContent || !IsVisibleBRElement(nearContent) ||
+ EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent)) {
+ // is there a BR after it?
+ nearContent = GetNextEditableHTMLNode(atStartOfSelection);
+ if (!nearContent || !IsVisibleBRElement(nearContent) ||
+ EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent)) {
+ pointToInsertBR = atStartOfSelection;
+ splitAfterNewBR = true;
+ }
+ }
+ if (!pointToInsertBR.IsSet() && nearContent->IsHTMLElement(nsGkAtoms::br)) {
+ brContent = nearContent;
+ }
+ }
+ if (pointToInsertBR.IsSet()) {
+ // if CR does not create a new P, default to BR creation
+ if (NS_WARN_IF(!doesCRCreateNewP)) {
+ return EditActionResult(NS_OK);
+ }
+
+ brContent = InsertBRElementWithTransaction(pointToInsertBR);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ brContent,
+ "HTMLEditor::InsertBRElementWithTransaction() failed, but ignored");
+ if (splitAfterNewBR) {
+ // We split the parent after the br we've just inserted.
+ pointToSplitParentDivOrP.SetAfter(brContent);
+ NS_WARNING_ASSERTION(pointToSplitParentDivOrP.IsSet(),
+ "Failed to set after the new <br>");
+ }
+ }
+ EditActionResult result(
+ SplitParagraph(aParentDivOrP, pointToSplitParentDivOrP, brContent));
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::SplitParagraph() failed");
+ result.MarkAsHandled();
+ return result;
+}
+
+template <typename PT, typename CT>
+nsresult HTMLEditor::SplitParagraph(
+ Element& aParentDivOrP, const EditorDOMPointBase<PT, CT>& aStartOfRightNode,
+ nsIContent* aNextBRNode) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsCOMPtr<nsINode> selNode = aStartOfRightNode.GetContainer();
+ int32_t selOffset = aStartOfRightNode.Offset();
+ nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
+ *this, address_of(selNode), &selOffset);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() failed");
+ return rv;
+ }
+ if (!selNode->IsContent()) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() returned "
+ "Document or something non-content node");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Split the paragraph.
+ SplitNodeResult splitDivOrPResult = SplitNodeDeepWithTransaction(
+ aParentDivOrP, EditorDOMPoint(selNode, selOffset),
+ SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (splitDivOrPResult.Failed()) {
+ NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
+ return splitDivOrPResult.Rv();
+ }
+ if (!splitDivOrPResult.DidSplit()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get rid of the break, if it is visible (otherwise it may be needed to
+ // prevent an empty p).
+ if (aNextBRNode && IsVisibleBRElement(aNextBRNode)) {
+ rv = DeleteNodeWithTransaction(*aNextBRNode);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ // Remove ID attribute on the paragraph from the existing right node.
+ rv = RemoveAttributeWithTransaction(aParentDivOrP, *nsGkAtoms::id);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
+ return rv;
+ }
+
+ // We need to ensure to both paragraphs visible even if they are empty.
+ // However, padding <br> element for empty last line isn't useful in this
+ // case because it'll be ignored by PlaintextSerializer. Additionally,
+ // it'll be exposed as <br> with Element.innerHTML. Therefore, we can use
+ // normal <br> elements for placeholder in this case. Note that Chromium
+ // also behaves so.
+ if (splitDivOrPResult.GetPreviousNode()->IsElement()) {
+ rv = InsertBRElementIfEmptyBlockElement(
+ MOZ_KnownLive(*splitDivOrPResult.GetPreviousNode()->AsElement()));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertBRElementIfEmptyBlockElement() failed");
+ return rv;
+ }
+ }
+ if (splitDivOrPResult.GetNextNode()->IsElement()) {
+ rv = InsertBRElementIfEmptyBlockElement(
+ MOZ_KnownLive(*splitDivOrPResult.GetNextNode()->AsElement()));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::InsertBRElementIfEmptyBlockElement() failed");
+ return rv;
+ }
+ }
+
+ // selection to beginning of right hand para;
+ // look inside any containers that are up front.
+ nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstLeafChild(
+ aParentDivOrP, ChildBlockBoundary::TreatAsLeaf);
+ if (child && (child->IsText() || HTMLEditUtils::IsContainerNode(*child))) {
+ nsresult rv = CollapseSelectionToStartOf(*child);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
+ } else {
+ nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(child));
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed, but ignored");
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::HandleInsertParagraphInListItemElement(Element& aListItem,
+ nsINode& aNode,
+ int32_t aOffset) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItem));
+
+ // Get the item parent and the active editing host.
+ RefPtr<Element> host = GetActiveEditingHost();
+
+ // If we are in an empty item, then we want to pop up out of the list, but
+ // only if prefs say it's okay and if the parent isn't the active editing
+ // host.
+ if (host != aListItem.GetParentElement() &&
+ IsEmptyBlockElement(aListItem, IgnoreSingleBR::Yes)) {
+ nsCOMPtr<nsIContent> leftListNode = aListItem.GetParent();
+ // Are we the last list item in the list?
+ if (!IsLastEditableChild(&aListItem)) {
+ // We need to split the list!
+ EditorDOMPoint atListItem(&aListItem);
+ ErrorResult error;
+ leftListNode = SplitNodeWithTransaction(atListItem, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error = NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (error.Failed()) {
+ NS_WARNING_ASSERTION(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED),
+ "HTMLEditor::SplitNodeWithTransaction() failed");
+ return error.StealNSResult();
+ }
+ }
+
+ // Are we in a sublist?
+ EditorDOMPoint atNextSiblingOfLeftList(leftListNode);
+ DebugOnly<bool> advanced = atNextSiblingOfLeftList.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced,
+ "Failed to advance offset after the right list node");
+ if (HTMLEditUtils::IsAnyListElement(
+ atNextSiblingOfLeftList.GetContainer())) {
+ // If so, move item out of this list and into the grandparent list
+ nsresult rv = MoveNodeWithTransaction(aListItem, atNextSiblingOfLeftList);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ rv = CollapseSelectionToStartOf(aListItem);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ // Otherwise kill this item
+ nsresult rv = DeleteNodeWithTransaction(aListItem);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+
+ // Time to insert a paragraph
+ nsStaticAtom& paraAtom = DefaultParagraphSeparatorTagName();
+ // We want a wrapper even if we separate with <br>
+ RefPtr<Element> pOrDivElement = CreateNodeWithTransaction(
+ &paraAtom == nsGkAtoms::br ? *nsGkAtoms::p : MOZ_KnownLive(paraAtom),
+ atNextSiblingOfLeftList);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!pOrDivElement) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Append a <br> to it
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(EditorDOMPoint(pOrDivElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set selection to before the break
+ rv = CollapseSelectionToStartOf(*pOrDivElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ // Else we want a new list item at the same list level. Get ws code to
+ // adjust any ws.
+ nsCOMPtr<nsINode> selNode = &aNode;
+ nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
+ *this, address_of(selNode), &aOffset);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() failed");
+ return rv;
+ }
+ if (!selNode->IsContent()) {
+ NS_WARNING(
+ "WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks() returned "
+ "document node or something non-content node");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now split the list item.
+ SplitNodeResult splitListItemResult =
+ SplitNodeDeepWithTransaction(aListItem, EditorDOMPoint(selNode, aOffset),
+ SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ splitListItemResult.Succeeded(),
+ "HTMLEditor::SplitNodeDeepWithTransaction() failed, but ignored");
+
+ // Hack: until I can change the damaged doc range code back to being
+ // extra-inclusive, I have to manually detect certain list items that may be
+ // left empty.
+ nsCOMPtr<nsIContent> prevItem = GetPriorHTMLSibling(&aListItem);
+ if (prevItem && HTMLEditUtils::IsListItem(prevItem)) {
+ if (IsEmptyNode(*prevItem)) {
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(
+ EditorDOMPoint(prevItem, 0));
+ if (createPaddingBRResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction("
+ ") failed");
+ return createPaddingBRResult.Rv();
+ }
+ } else {
+ if (IsEmptyNode(aListItem, true)) {
+ if (aListItem.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
+ nsCOMPtr<nsINode> list = aListItem.GetParentNode();
+ int32_t itemOffset = list ? list->ComputeIndexOf(&aListItem) : -1;
+
+ nsStaticAtom* nextDefinitionListItemTagName =
+ aListItem.IsHTMLElement(nsGkAtoms::dt) ? nsGkAtoms::dd
+ : nsGkAtoms::dt;
+ MOZ_DIAGNOSTIC_ASSERT(itemOffset != -1);
+ EditorDOMPoint atNextListItem(list, aListItem.GetNextSibling(),
+ itemOffset + 1);
+ RefPtr<Element> newListItem = CreateNodeWithTransaction(
+ MOZ_KnownLive(*nextDefinitionListItemTagName), atNextListItem);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!newListItem) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = DeleteNodeWithTransaction(aListItem);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ rv = CollapseSelectionToStartOf(*newListItem);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+ }
+
+ RefPtr<Element> brElement;
+ nsresult rv = CopyLastEditableChildStylesWithTransaction(
+ MOZ_KnownLive(*prevItem->AsElement()), aListItem,
+ address_of(brElement));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::CopyLastEditableChildStylesWithTransaction() "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+ if (brElement) {
+ EditorRawDOMPoint atBRNode(brElement);
+ if (NS_WARN_IF(!atBRNode.IsSetAndValid())) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = CollapseSelectionTo(atBRNode);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+ } else {
+ WSScanResult forwardScanFromStartOfListItemResult =
+ WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
+ *this, EditorRawDOMPoint(&aListItem, 0));
+ if (forwardScanFromStartOfListItemResult.Failed()) {
+ NS_WARNING(
+ "WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() failed");
+ return NS_ERROR_FAILURE;
+ }
+ if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
+ forwardScanFromStartOfListItemResult.ReachedBRElement() ||
+ forwardScanFromStartOfListItemResult.ReachedHRElement()) {
+ EditorRawDOMPoint atFoundElement(
+ forwardScanFromStartOfListItemResult.RawPointAtContent());
+ if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = CollapseSelectionTo(atFoundElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+
+ // XXX This may be not meaningful position if it reached block element
+ // in aListItem.
+ nsresult rv = CollapseSelectionTo(
+ forwardScanFromStartOfListItemResult.RawPoint());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+ }
+ }
+
+ rv = CollapseSelectionToStartOf(aListItem);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::MoveNodesIntoNewBlockquoteElement(
+ nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ // The idea here is to put the nodes into a minimal number of blockquotes.
+ // When the user blockquotes something, they expect one blockquote. That
+ // may not be possible (for instance, if they have two table cells selected,
+ // you need two blockquotes inside the cells).
+ RefPtr<Element> curBlock;
+ nsCOMPtr<nsINode> prevParent;
+
+ for (auto& content : aArrayOfContents) {
+ // If the node is a table element or list item, dive inside
+ if (HTMLEditUtils::IsAnyTableElementButNotTable(content) ||
+ HTMLEditUtils::IsListItem(content)) {
+ // Forget any previous block
+ curBlock = nullptr;
+ // Recursion time
+ AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
+ HTMLEditor::GetChildNodesOf(*content, childContents);
+ nsresult rv = MoveNodesIntoNewBlockquoteElement(childContents);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodesIntoNewBlockquoteElement() failed");
+ return rv;
+ }
+ }
+
+ // If the node has different parent than previous node, further nodes in a
+ // new parent
+ if (prevParent) {
+ if (prevParent != content->GetParentNode()) {
+ // Forget any previous blockquote node we were using
+ curBlock = nullptr;
+ prevParent = content->GetParentNode();
+ }
+ } else {
+ prevParent = content->GetParentNode();
+ }
+
+ // If no curBlock, make one
+ if (!curBlock) {
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote,
+ EditorDOMPoint(content));
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms:"
+ ":blockquote) failed");
+ return splitNodeResult.Rv();
+ }
+ // XXX Perhaps, we should insert the new `<blockquote>` element after
+ // moving all nodes into it since the movement does not cause
+ // running mutation event listeners.
+ curBlock = CreateNodeWithTransaction(*nsGkAtoms::blockquote,
+ splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curBlock) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::blockquote) "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+ // remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = curBlock;
+ // note: doesn't matter if we set mNewBlockElement multiple times.
+ }
+
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to/ keep it alive.
+ nsresult rv =
+ MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::RemoveBlockContainerElements(
+ nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ // Intent of this routine is to be used for converting to/from headers,
+ // paragraphs, pre, and address. Those blocks that pretty much just contain
+ // inline things...
+ RefPtr<Element> blockElement;
+ nsCOMPtr<nsIContent> firstContent, lastContent;
+ for (auto& content : aArrayOfContents) {
+ // If curNode is an <address>, <p>, <hn>, or <pre>, remove it.
+ if (HTMLEditUtils::IsFormatNode(content)) {
+ // Process any partial progress saved
+ if (blockElement) {
+ SplitRangeOffFromNodeResult removeMiddleContainerResult =
+ SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ *blockElement, *firstContent, *lastContent);
+ if (removeMiddleContainerResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
+ "failed");
+ return removeMiddleContainerResult.Rv();
+ }
+ firstContent = lastContent = blockElement = nullptr;
+ }
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+ // Remove current block
+ nsresult rv = RemoveBlockContainerWithTransaction(
+ MOZ_KnownLive(*content->AsElement()));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>?
+ if (content->IsAnyOfHTMLElements(
+ nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td,
+ nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) ||
+ HTMLEditUtils::IsAnyListElement(content)) {
+ // Process any partial progress saved
+ if (blockElement) {
+ SplitRangeOffFromNodeResult removeMiddleContainerResult =
+ SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ *blockElement, *firstContent, *lastContent);
+ if (removeMiddleContainerResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
+ "failed");
+ return removeMiddleContainerResult.Rv();
+ }
+ firstContent = lastContent = blockElement = nullptr;
+ }
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+ // Recursion time
+ AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
+ HTMLEditor::GetChildNodesOf(*content, childContents);
+ nsresult rv = RemoveBlockContainerElements(childContents);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveBlockContainerElements() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsInlineElement(content)) {
+ if (blockElement) {
+ // If so, is this node a descendant?
+ if (EditorUtils::IsDescendantOf(*content, *blockElement)) {
+ // Then we don't need to do anything different for this node
+ lastContent = content;
+ continue;
+ }
+ // Otherwise, we have progressed beyond end of blockElement, so let's
+ // handle it now. We need to remove the portion of blockElement that
+ // contains [firstContent - lastContent].
+ SplitRangeOffFromNodeResult removeMiddleContainerResult =
+ SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ *blockElement, *firstContent, *lastContent);
+ if (removeMiddleContainerResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
+ "failed");
+ return removeMiddleContainerResult.Rv();
+ }
+ firstContent = lastContent = blockElement = nullptr;
+ // Fall out and handle content
+ }
+ blockElement = HTMLEditUtils::GetAncestorBlockElement(content);
+ if (!blockElement || !HTMLEditUtils::IsFormatNode(blockElement) ||
+ !EditorUtils::IsEditableContent(*blockElement, EditorType::HTML)) {
+ // Not a block kind that we care about.
+ blockElement = nullptr;
+ } else {
+ firstContent = lastContent = content;
+ }
+ continue;
+ }
+
+ if (blockElement) {
+ // Some node that is already sans block style. Skip over it and process
+ // any partial progress saved.
+ SplitRangeOffFromNodeResult removeMiddleContainerResult =
+ SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ *blockElement, *firstContent, *lastContent);
+ if (removeMiddleContainerResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
+ "failed");
+ return removeMiddleContainerResult.Rv();
+ }
+ firstContent = lastContent = blockElement = nullptr;
+ continue;
+ }
+ }
+ // Process any partial progress saved
+ if (blockElement) {
+ SplitRangeOffFromNodeResult removeMiddleContainerResult =
+ SplitRangeOffFromBlockAndRemoveMiddleContainer(
+ *blockElement, *firstContent, *lastContent);
+ if (removeMiddleContainerResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::SplitRangeOffFromBlockAndRemoveMiddleContainer() "
+ "failed");
+ return removeMiddleContainerResult.Rv();
+ }
+ firstContent = lastContent = blockElement = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::CreateOrChangeBlockContainerElement(
+ nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, nsAtom& aBlockTag) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ // Intent of this routine is to be used for converting to/from headers,
+ // paragraphs, pre, and address. Those blocks that pretty much just contain
+ // inline things...
+ nsCOMPtr<Element> newBlock;
+ nsCOMPtr<Element> curBlock;
+ for (auto& content : aArrayOfContents) {
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsInContentNode())) {
+ // If given node has been removed from the document, let's ignore it
+ // since the following code may need its parent replace it with new
+ // block.
+ curBlock = nullptr;
+ newBlock = nullptr;
+ continue;
+ }
+
+ // Is it already the right kind of block, or an uneditable block?
+ if (content->IsHTMLElement(&aBlockTag) ||
+ (!EditorUtils::IsEditableContent(content, EditorType::HTML) &&
+ HTMLEditUtils::IsBlockElement(content))) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ // Do nothing to this block
+ continue;
+ }
+
+ // If content is a address, p, header, address, or pre, replace it with a
+ // new block of correct type.
+ // XXX: pre can't hold everything the others can
+ if (HTMLEditUtils::IsMozDiv(content) ||
+ HTMLEditUtils::IsFormatNode(content)) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ newBlock = ReplaceContainerAndCloneAttributesWithTransaction(
+ MOZ_KnownLive(*content->AsElement()), aBlockTag);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!newBlock) {
+ NS_WARNING(
+ "EditorBase::ReplaceContainerAndCloneAttributesWithTransaction() "
+ "failed");
+ return NS_ERROR_FAILURE;
+ }
+ // If the new block element was moved to different element or removed by
+ // the web app via mutation event listener, we should stop handling this
+ // action since we cannot handle each of a lot of edge cases.
+ if (NS_WARN_IF(newBlock->GetParentNode() != atContent.GetContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsTable(content) ||
+ HTMLEditUtils::IsAnyListElement(content) ||
+ content->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::tr,
+ nsGkAtoms::td, nsGkAtoms::li,
+ nsGkAtoms::blockquote, nsGkAtoms::div)) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ // Recursion time
+ AutoTArray<OwningNonNull<nsIContent>, 24> childContents;
+ HTMLEditor::GetChildNodesOf(*content, childContents);
+ if (!childContents.IsEmpty()) {
+ nsresult rv =
+ CreateOrChangeBlockContainerElement(childContents, aBlockTag);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::CreateOrChangeBlockContainerElement() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ // Make sure we can put a block here
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+ // If current handling node has been moved from the container by a
+ // mutation event listener when we need to do something more for it,
+ // we should stop handling this action since we cannot handle each of
+ // a lot of edge cases.
+ if (NS_WARN_IF(atContent.HasChildMovedFromContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ EditorDOMPoint splitPoint = splitNodeResult.SplitPoint();
+ RefPtr<Element> theBlock =
+ CreateNodeWithTransaction(aBlockTag, splitPoint);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!theBlock) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // If the new block element was moved to different element or removed by
+ // the web app via mutation event listener, we should stop handling this
+ // action since we cannot handle each of a lot of edge cases.
+ if (NS_WARN_IF(theBlock->GetParentNode() != splitPoint.GetContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = std::move(theBlock);
+ continue;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::br)) {
+ // If the node is a break, we honor it by putting further nodes in a new
+ // parent
+ if (curBlock) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
+ // alive.
+ nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*content));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ // The break is the first (or even only) node we encountered. Create a
+ // block for it.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() failed");
+ return splitNodeResult.Rv();
+ }
+ // If current handling node has been moved from the container by a
+ // mutation event listener when we need to do something more for it,
+ // we should stop handling this action since we cannot handle each of
+ // a lot of edge cases.
+ if (NS_WARN_IF(atContent.HasChildMovedFromContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ EditorDOMPoint splitPoint = splitNodeResult.SplitPoint();
+ curBlock = CreateNodeWithTransaction(aBlockTag, splitPoint);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curBlock) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // If the new block element was moved to different element or removed by
+ // the web app via mutation event listener, we should stop handling this
+ // action since we cannot handle each of a lot of edge cases.
+ if (NS_WARN_IF(curBlock->GetParentNode() != splitPoint.GetContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = curBlock;
+ // Note: doesn't matter if we set mNewBlockElement multiple times.
+ //
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
+ // alive.
+ nsresult rv =
+ MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsInlineElement(content)) {
+ // If content is inline, pull it into curBlock. Note: it's assumed that
+ // consecutive inline nodes in aNodeArray are actually members of the
+ // same block parent. This happens to be true now as a side effect of
+ // how aNodeArray is contructed, but some additional logic should be
+ // added here if that should change
+ //
+ // If content is a non editable, drop it if we are going to <pre>.
+ if (&aBlockTag == nsGkAtoms::pre &&
+ !EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ // Do nothing to this block
+ continue;
+ }
+
+ // If no curBlock, make one
+ if (!curBlock) {
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() "
+ "failed");
+ return splitNodeResult.Rv();
+ }
+ // If current handling node has been moved from the container by a
+ // mutation event listener when we need to do something more for it,
+ // we should stop handling this action since we cannot handle each of
+ // a lot of edge cases.
+ if (NS_WARN_IF(atContent.HasChildMovedFromContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+ EditorDOMPoint splitPoint = splitNodeResult.SplitPoint();
+ curBlock = CreateNodeWithTransaction(aBlockTag, splitPoint);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!curBlock) {
+ NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ // If the new block element was moved to different element or removed
+ // by the web app via mutation event listener, we should stop handling
+ // this action since we cannot handle each of a lot of edge cases.
+ if (NS_WARN_IF(curBlock->GetParentNode() !=
+ splitPoint.GetContainer())) {
+ return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
+ }
+
+ // Update container of content.
+ atContent.Set(content);
+
+ // Remember our new block for postprocessing
+ TopLevelEditSubActionDataRef().mNewBlockElement = curBlock;
+ // Note: doesn't matter if we set mNewBlockElement multiple times.
+ }
+
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ // This is possible due to mutation events, let's not assert
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // XXX If content is a br, replace it with a return if going to <pre>
+
+ // This is a continuation of some inline nodes that belong together in
+ // the same block item. Use curBlock.
+ //
+ // MOZ_KnownLive because 'aArrayOfContents' is guaranteed to keep it
+ // alive. We could try to make that a rvalue ref and create a const array
+ // on the stack here, but callers are passing in auto arrays, and we don't
+ // want to introduce copies..
+ nsresult rv =
+ MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *curBlock);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+SplitNodeResult HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(
+ nsAtom& aTag, const EditorDOMPoint& aStartOfDeepestRightNode) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
+ return SplitNodeResult(NS_ERROR_INVALID_ARG);
+ }
+ MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
+
+ RefPtr<Element> host = GetActiveEditingHost();
+ if (NS_WARN_IF(!host)) {
+ return SplitNodeResult(NS_ERROR_FAILURE);
+ }
+
+ // The point must be descendant of editing host.
+ if (aStartOfDeepestRightNode.GetContainer() != host &&
+ !EditorUtils::IsDescendantOf(*aStartOfDeepestRightNode.GetContainer(),
+ *host)) {
+ NS_WARNING("aStartOfDeepestRightNode was not in editing host");
+ return SplitNodeResult(NS_ERROR_INVALID_ARG);
+ }
+
+ // Look for a node that can legally contain the tag.
+ EditorDOMPoint pointToInsert(aStartOfDeepestRightNode);
+ for (; pointToInsert.IsSet();
+ pointToInsert.Set(pointToInsert.GetContainer())) {
+ // We cannot split active editing host and its ancestor. So, there is
+ // no element to contain the specified element.
+ if (pointToInsert.GetChild() == host) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction() reached "
+ "editing host");
+ return SplitNodeResult(NS_ERROR_FAILURE);
+ }
+
+ if (HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(), aTag)) {
+ // Found an ancestor node which can contain the element.
+ break;
+ }
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(pointToInsert.IsSet());
+
+ // If the point itself can contain the tag, we don't need to split any
+ // ancestor nodes. In this case, we should return the given split point
+ // as is.
+ if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
+ return SplitNodeResult(aStartOfDeepestRightNode);
+ }
+
+ SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
+ MOZ_KnownLive(*pointToInsert.GetChild()), aStartOfDeepestRightNode,
+ SplitAtEdges::eAllowToCreateEmptyContainer);
+ if (NS_WARN_IF(Destroyed())) {
+ return SplitNodeResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(splitNodeResult.Succeeded(),
+ "HTMLEditor::SplitNodeDeepWithTransaction(SplitAtEdges::"
+ "eAllowToCreateEmptyContainer) failed");
+ return splitNodeResult;
+}
+
+nsresult HTMLEditor::JoinNearestEditableNodesWithTransaction(
+ nsIContent& aNodeLeft, nsIContent& aNodeRight,
+ EditorDOMPoint* aNewFirstChildOfRightNode) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aNewFirstChildOfRightNode);
+
+ // Caller responsible for left and right node being the same type
+ if (NS_WARN_IF(!aNodeLeft.GetParentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+ // If they don't have the same parent, first move the right node to after
+ // the left one
+ if (aNodeLeft.GetParentNode() != aNodeRight.GetParentNode()) {
+ nsresult rv =
+ MoveNodeWithTransaction(aNodeRight, EditorDOMPoint(&aNodeLeft));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ EditorDOMPoint ret(&aNodeRight, aNodeLeft.Length());
+
+ // Separate join rules for differing blocks
+ if (HTMLEditUtils::IsAnyListElement(&aNodeLeft) || aNodeLeft.IsText()) {
+ // For lists, merge shallow (wouldn't want to combine list items)
+ nsresult rv = JoinNodesWithTransaction(aNodeLeft, aNodeRight);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::JoinNodesWithTransaction failed");
+ if (NS_SUCCEEDED(rv)) {
+ // XXX `ret` may point invalid point if mutation event listener changed
+ // the previous siblings...
+ *aNewFirstChildOfRightNode = std::move(ret);
+ }
+ return rv;
+ }
+
+ // Remember the last left child, and first right child
+ nsCOMPtr<nsIContent> lastLeft = GetLastEditableChild(aNodeLeft);
+ if (NS_WARN_IF(!lastLeft)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContent> firstRight = GetFirstEditableChild(aNodeRight);
+ if (NS_WARN_IF(!firstRight)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // For list items, divs, etc., merge smart
+ nsresult rv = JoinNodesWithTransaction(aNodeLeft, aNodeRight);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
+ return rv;
+ }
+
+ if ((lastLeft->IsText() || lastLeft->IsElement()) &&
+ HTMLEditUtils::CanContentsBeJoined(*lastLeft, *firstRight,
+ StyleDifference::CompareIfElements)) {
+ nsresult rv = JoinNearestEditableNodesWithTransaction(
+ *lastLeft, *firstRight, aNewFirstChildOfRightNode);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
+ return rv;
+ }
+ // XXX `ret` may point invalid point if mutation event listener changed
+ // the previous siblings...
+ *aNewFirstChildOfRightNode = std::move(ret);
+ return NS_OK;
+}
+
+Element* HTMLEditor::GetMostAncestorMailCiteElement(nsINode& aNode) const {
+ Element* mailCiteElement = nullptr;
+ bool isPlaintextEditor = IsPlaintextEditor();
+ for (nsINode* node = &aNode; node; node = node->GetParentNode()) {
+ if ((isPlaintextEditor && node->IsHTMLElement(nsGkAtoms::pre)) ||
+ HTMLEditUtils::IsMailCite(node)) {
+ mailCiteElement = node->AsElement();
+ }
+ if (node->IsHTMLElement(nsGkAtoms::body)) {
+ break;
+ }
+ }
+ return mailCiteElement;
+}
+
+nsresult HTMLEditor::CacheInlineStyles(nsIContent& aContent) {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ nsresult rv = GetInlineStyles(
+ aContent, *TopLevelEditSubActionDataRef().mCachedInlineStyles);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::GetInlineStyles() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::GetInlineStyles(nsIContent& aContent,
+ AutoStyleCacheArray& aStyleCacheArray) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aStyleCacheArray.IsEmpty());
+
+ bool useCSS = IsCSSEnabled();
+
+ for (nsStaticAtom* property :
+ {nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::face,
+ nsGkAtoms::size, nsGkAtoms::color, nsGkAtoms::tt, nsGkAtoms::em,
+ nsGkAtoms::strong, nsGkAtoms::dfn, nsGkAtoms::code, nsGkAtoms::samp,
+ nsGkAtoms::var, nsGkAtoms::cite, nsGkAtoms::abbr, nsGkAtoms::acronym,
+ nsGkAtoms::backgroundColor, nsGkAtoms::sub, nsGkAtoms::sup}) {
+ nsStaticAtom *tag, *attribute;
+ if (property == nsGkAtoms::face || property == nsGkAtoms::size ||
+ property == nsGkAtoms::color) {
+ tag = nsGkAtoms::font;
+ attribute = property;
+ } else {
+ tag = property;
+ attribute = nullptr;
+ }
+ // If type-in state is set, don't intervene
+ bool typeInSet, unused;
+ mTypeInState->GetTypingState(typeInSet, unused, tag, attribute, nullptr);
+ if (typeInSet) {
+ continue;
+ }
+ bool isSet = false;
+ nsString value; // Don't use nsAutoString here because it requires memcpy
+ // at creating new StyleCache instance.
+ // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
+ if (!useCSS || (property == nsGkAtoms::size)) {
+ isSet = IsTextPropertySetByContent(&aContent, tag, attribute, nullptr,
+ &value);
+ } else {
+ isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
+ aContent, MOZ_KnownLive(tag), MOZ_KnownLive(attribute), value);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ }
+ if (isSet) {
+ aStyleCacheArray.AppendElement(StyleCache(tag, attribute, value));
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::ReapplyCachedStyles() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ // The idea here is to examine our cached list of styles and see if any have
+ // been removed. If so, add typeinstate for them, so that they will be
+ // reinserted when new content is added.
+
+ if (TopLevelEditSubActionDataRef().mCachedInlineStyles->IsEmpty() ||
+ !SelectionRefPtr()->RangeCount()) {
+ return NS_OK;
+ }
+
+ // remember if we are in css mode
+ bool useCSS = IsCSSEnabled();
+
+ const RangeBoundary& atStartOfSelection =
+ SelectionRefPtr()->GetRangeAt(0)->StartRef();
+ nsCOMPtr<nsIContent> startContainerContent =
+ atStartOfSelection.Container() &&
+ atStartOfSelection.Container()->IsContent()
+ ? atStartOfSelection.Container()->AsContent()
+ : nullptr;
+ if (NS_WARN_IF(!startContainerContent)) {
+ return NS_OK;
+ }
+
+ AutoStyleCacheArray styleCacheArrayAtInsertionPoint;
+ nsresult rv =
+ GetInlineStyles(*startContainerContent, styleCacheArrayAtInsertionPoint);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::GetInlineStyles() failed, but ignored");
+ return NS_OK;
+ }
+
+ for (StyleCache& styleCacheBeforeEdit :
+ *TopLevelEditSubActionDataRef().mCachedInlineStyles) {
+ bool isFirst = false, isAny = false, isAll = false;
+ nsAutoString currentValue;
+ if (useCSS) {
+ // check computed style first in css case
+ isAny = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
+ *startContainerContent, MOZ_KnownLive(styleCacheBeforeEdit.Tag()),
+ MOZ_KnownLive(styleCacheBeforeEdit.GetAttribute()), currentValue);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ }
+ if (!isAny) {
+ // then check typeinstate and html style
+ nsresult rv = GetInlinePropertyBase(
+ MOZ_KnownLive(*styleCacheBeforeEdit.Tag()),
+ MOZ_KnownLive(styleCacheBeforeEdit.GetAttribute()),
+ &styleCacheBeforeEdit.Value(), &isFirst, &isAny, &isAll,
+ &currentValue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::GetInlinePropertyBase() failed");
+ return rv;
+ }
+ }
+ // This style has disappeared through deletion. Let's add the styles to
+ // mTypeInState when same style isn't applied to the node already.
+ if (isAny && !IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
+ continue;
+ }
+ AutoStyleCacheArray::index_type index =
+ styleCacheArrayAtInsertionPoint.IndexOf(
+ styleCacheBeforeEdit.Tag(), styleCacheBeforeEdit.GetAttribute());
+ if (index == AutoStyleCacheArray::NoIndex ||
+ styleCacheBeforeEdit.Value() !=
+ styleCacheArrayAtInsertionPoint.ElementAt(index).Value()) {
+ mTypeInState->SetProp(styleCacheBeforeEdit.Tag(),
+ styleCacheBeforeEdit.GetAttribute(),
+ styleCacheBeforeEdit.Value());
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
+ const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoTArray<OwningNonNull<Element>, 64> arrayOfEmptyElements;
+ DOMIterator iter;
+ if (NS_FAILED(iter.Init(aStartRef, aEndRef))) {
+ NS_WARNING("DOMIterator::Init() failed");
+ return NS_ERROR_FAILURE;
+ }
+ iter.AppendNodesToArray(
+ +[](nsINode& aNode, void* aSelf) {
+ MOZ_ASSERT(Element::FromNode(&aNode));
+ MOZ_ASSERT(aSelf);
+ Element* element = aNode.AsElement();
+ if (!EditorUtils::IsEditableContent(*element, EditorType::HTML) ||
+ (!HTMLEditUtils::IsListItem(element) &&
+ !HTMLEditUtils::IsTableCellOrCaption(*element))) {
+ return false;
+ }
+ return static_cast<HTMLEditor*>(aSelf)->IsEmptyNode(*element, false,
+ false);
+ },
+ arrayOfEmptyElements, this);
+
+ // Put padding <br> elements for empty <li> and <td>.
+ for (auto& emptyElement : arrayOfEmptyElements) {
+ // Need to put br at END of node. It may have empty containers in it and
+ // still pass the "IsEmptyNode" test, and we want the br's to be after
+ // them. Also, we want the br to be after the selection if the selection
+ // is in this node.
+ EditorDOMPoint endOfNode(EditorDOMPoint::AtEndOf(emptyElement));
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(endOfNode);
+ if (createPaddingBRResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
+ "failed");
+ return createPaddingBRResult.Rv();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::EnsureCaretInBlockElement(Element& aElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
+ EditorRawDOMPoint atCaret(EditorBase::GetStartPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!atCaret.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Use ranges and RangeUtils::CompareNodeToRange() to compare selection
+ // start to new block.
+ RefPtr<StaticRange> staticRange =
+ StaticRange::Create(atCaret.ToRawRangeBoundary(),
+ atCaret.ToRawRangeBoundary(), IgnoreErrors());
+ if (!staticRange) {
+ NS_WARNING("StaticRange::Create() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool nodeBefore, nodeAfter;
+ nsresult rv = RangeUtils::CompareNodeToRange(&aElement, staticRange,
+ &nodeBefore, &nodeAfter);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("RangeUtils::CompareNodeToRange() failed");
+ return rv;
+ }
+
+ if (nodeBefore && nodeAfter) {
+ return NS_OK; // selection is inside block
+ }
+
+ if (nodeBefore) {
+ // selection is after block. put at end of block.
+ nsIContent* lastEditableContent = GetLastEditableChild(aElement);
+ if (!lastEditableContent) {
+ lastEditableContent = &aElement;
+ }
+ EditorRawDOMPoint endPoint;
+ if (lastEditableContent->IsText() ||
+ HTMLEditUtils::IsContainerNode(*lastEditableContent)) {
+ endPoint.SetToEndOf(lastEditableContent);
+ } else {
+ endPoint.SetAfter(lastEditableContent);
+ if (NS_WARN_IF(!endPoint.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ nsresult rv = CollapseSelectionTo(endPoint);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+
+ // selection is before block. put at start of block.
+ nsIContent* firstEditableContent = GetFirstEditableChild(aElement);
+ if (!firstEditableContent) {
+ firstEditableContent = &aElement;
+ }
+ EditorRawDOMPoint atStartOfBlock;
+ if (firstEditableContent->IsText() ||
+ HTMLEditUtils::IsContainerNode(*firstEditableContent)) {
+ atStartOfBlock.Set(firstEditableContent);
+ } else {
+ atStartOfBlock.Set(firstEditableContent, 0);
+ }
+ rv = CollapseSelectionTo(atStartOfBlock);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+}
+
+void HTMLEditor::SetSelectionInterlinePosition() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
+ // Get the (collapsed) selection location
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return;
+ }
+
+ EditorDOMPoint atCaret(firstRange->StartRef());
+ if (NS_WARN_IF(!atCaret.IsSet())) {
+ return;
+ }
+ MOZ_ASSERT(atCaret.IsSetAndValid());
+
+ // First, let's check to see if we are after a `<br>`. We take care of this
+ // special-case first so that we don't accidentally fall through into one of
+ // the other conditionals.
+ // XXX Although I don't understand "interline position", if caret is
+ // immediately after non-editable contents, but previous editable
+ // content is `<br>`, does this do right thing?
+ if (nsIContent* previousEditableContentInBlock =
+ GetPreviousEditableHTMLNodeInBlock(atCaret)) {
+ if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br)) {
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ return;
+ }
+ }
+
+ if (!atCaret.GetChild()) {
+ return;
+ }
+
+ // If caret is immediately after a block, set interline position to "right".
+ // XXX Although I don't understand "interline position", if caret is
+ // immediately after non-editable contents, but previous editable
+ // content is a block, does this do right thing?
+ if (nsIContent* previousEditableContentInBlockAtCaret =
+ GetPriorHTMLSibling(atCaret.GetChild())) {
+ if (HTMLEditUtils::IsBlockElement(*previousEditableContentInBlockAtCaret)) {
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ return;
+ }
+ }
+
+ // If caret is immediately before a block, set interline position to "left".
+ // XXX Although I don't understand "interline position", if caret is
+ // immediately before non-editable contents, but next editable
+ // content is a block, does this do right thing?
+ if (nsIContent* nextEditableContentInBlockAtCaret =
+ GetNextHTMLSibling(atCaret.GetChild())) {
+ if (HTMLEditUtils::IsBlockElement(*nextEditableContentInBlockAtCaret)) {
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(false, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(false) failed, but ignored");
+ }
+ }
+}
+
+nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
+ nsIEditor::EDirection aDirectionAndAmount) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(SelectionRefPtr()->IsCollapsed());
+
+ EditorDOMPoint point(EditorBase::GetStartPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!point.IsInContentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If selection start is not editable, climb up the tree until editable one.
+ while (!EditorUtils::IsEditableContent(*point.ContainerAsContent(),
+ EditorType::HTML)) {
+ point.Set(point.GetContainer());
+ if (NS_WARN_IF(!point.IsInContentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // If caret is in empty block element, we need to insert a `<br>` element
+ // because the block should have one-line height.
+ if (RefPtr<Element> blockElement =
+ HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *point.ContainerAsContent())) {
+ if (blockElement &&
+ EditorUtils::IsEditableContent(*blockElement, EditorType::HTML) &&
+ IsEmptyNode(*blockElement, false, false) &&
+ HTMLEditUtils::CanNodeContain(*point.GetContainer(), *nsGkAtoms::br)) {
+ Element* bodyOrDocumentElement = GetRoot();
+ if (NS_WARN_IF(!bodyOrDocumentElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (point.GetContainer() == bodyOrDocumentElement) {
+ // Our root node is completely empty. Don't add a <br> here.
+ // AfterEditInner() will add one for us when it calls
+ // TextEditor::MaybeCreatePaddingBRElementForEmptyEditor().
+ // XXX This kind of dependency between methods makes us spaghetti.
+ // Let's handle it here later.
+ // XXX This looks odd check. If active editing host is not a
+ // `<body>`, what are we doing?
+ return NS_OK;
+ }
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
+ NS_WARNING_ASSERTION(
+ createPaddingBRResult.Succeeded(),
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
+ "failed");
+ return createPaddingBRResult.Rv();
+ }
+ }
+
+ // XXX Perhaps, we should do something if we're in a data node but not
+ // a text node.
+ if (point.IsInTextNode()) {
+ return NS_OK;
+ }
+
+ // Do we need to insert a padding <br> element for empty last line? We do
+ // if we are:
+ // 1) prior node is in same block where selection is AND
+ // 2) prior node is a br AND
+ // 3) that br is not visible
+
+ if (nsCOMPtr<nsIContent> previousEditableContent =
+ GetPreviousEditableHTMLNode(point)) {
+ RefPtr<Element> blockElementAtCaret =
+ HTMLEditUtils::GetInclusiveAncestorBlockElement(
+ *point.ContainerAsContent());
+ RefPtr<Element> blockElementParentAtPreviousEditableContent =
+ HTMLEditUtils::GetAncestorBlockElement(*previousEditableContent);
+ // If previous editable content of caret is in same block and a `<br>`
+ // element, we need to adjust interline position.
+ if (blockElementAtCaret &&
+ blockElementAtCaret == blockElementParentAtPreviousEditableContent &&
+ previousEditableContent &&
+ previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
+ // If it's an invisible `<br>` element, we need to insert a padding
+ // `<br>` element for making empty line have one-line height.
+ if (!IsVisibleBRElement(previousEditableContent)) {
+ CreateElementResult createPaddingBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
+ if (createPaddingBRResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::"
+ "InsertPaddingBRElementForEmptyLastLineWithTransaction() failed");
+ return createPaddingBRResult.Rv();
+ }
+ point.Set(createPaddingBRResult.GetNewNode());
+ // Selection stays *before* padding `<br>` element for empty last
+ // line, sticking to it.
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ nsresult rv = CollapseSelectionTo(point);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+ }
+ }
+ // If it's a visible `<br>` element and next editable content is a
+ // padding `<br>` element, we need to set interline position.
+ else if (nsIContent* nextEditableContentInBlock =
+ GetNextEditableHTMLNodeInBlock(*previousEditableContent)) {
+ if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
+ *nextEditableContentInBlock)) {
+ // Make it stick to the padding `<br>` element so that it will be
+ // on blank line.
+ IgnoredErrorResult ignoredError;
+ SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "Selection::SetInterlinePosition(true) failed, but ignored");
+ }
+ }
+ }
+ }
+
+ // If previous editable content in same block is `<br>`, text node, `<img>`
+ // or `<hr>`, current caret position is fine.
+ if (nsIContent* previousEditableContentInBlock =
+ GetPreviousEditableHTMLNodeInBlock(point)) {
+ if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
+ previousEditableContentInBlock->IsText() ||
+ HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
+ previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
+ return NS_OK;
+ }
+ }
+ // If next editable content in same block is `<br>`, text node, `<img>` or
+ // `<hr>`, current caret position is fine.
+ if (nsIContent* nextEditableContentInBlock =
+ GetNextEditableHTMLNodeInBlock(point)) {
+ if (nextEditableContentInBlock->IsText() ||
+ nextEditableContentInBlock->IsAnyOfHTMLElements(
+ nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
+ return NS_OK;
+ }
+ }
+
+ // Otherwise, look for a near editable content towards edit action direction.
+
+ // If there is no editable content, keep current caret position.
+ nsIContent* nearEditableContent =
+ FindNearEditableContent(point, aDirectionAndAmount);
+ if (!nearEditableContent) {
+ return NS_OK;
+ }
+
+ EditorDOMPoint pointToPutCaret =
+ GetGoodCaretPointFor(*nearEditableContent, aDirectionAndAmount);
+ if (!pointToPutCaret.IsSet()) {
+ NS_WARNING("HTMLEditor::GetGoodCaretPointFor() failed");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = CollapseSelectionTo(pointToPutCaret);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionTo() failed");
+ return rv;
+}
+
+template <typename PT, typename CT>
+nsIContent* HTMLEditor::FindNearEditableContent(
+ const EditorDOMPointBase<PT, CT>& aPoint,
+ nsIEditor::EDirection aDirection) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aPoint.IsSetAndValid());
+
+ nsIContent* editableContent = nullptr;
+ if (aDirection == nsIEditor::ePrevious) {
+ editableContent = GetPreviousEditableHTMLNode(aPoint);
+ if (!editableContent) {
+ return nullptr; // Not illegal.
+ }
+ } else {
+ editableContent = GetNextEditableHTMLNode(aPoint);
+ if (NS_WARN_IF(!editableContent)) {
+ // Perhaps, illegal because the node pointed by aPoint isn't editable
+ // and nobody of previous nodes is editable.
+ return nullptr;
+ }
+ }
+
+ // scan in the right direction until we find an eligible text node,
+ // but don't cross any breaks, images, or table elements.
+ // XXX This comment sounds odd. editableContent may have already crossed
+ // breaks and/or images if they are non-editable.
+ while (editableContent && !editableContent->IsText() &&
+ !editableContent->IsHTMLElement(nsGkAtoms::br) &&
+ !HTMLEditUtils::IsImage(editableContent)) {
+ if (aDirection == nsIEditor::ePrevious) {
+ editableContent = GetPreviousEditableHTMLNode(*editableContent);
+ if (NS_WARN_IF(!editableContent)) {
+ return nullptr;
+ }
+ } else {
+ editableContent = GetNextEditableHTMLNode(*editableContent);
+ if (NS_WARN_IF(!editableContent)) {
+ return nullptr;
+ }
+ }
+ }
+
+ // don't cross any table elements
+ if (HTMLEditor::NodesInDifferentTableElements(*editableContent,
+ *aPoint.GetContainer())) {
+ return nullptr;
+ }
+
+ // otherwise, ok, we have found a good spot to put the selection
+ return editableContent;
+}
+
+// static
+bool HTMLEditor::NodesInDifferentTableElements(nsINode& aNode1,
+ nsINode& aNode2) {
+ nsINode* parentNode1;
+ for (parentNode1 = &aNode1;
+ parentNode1 && !HTMLEditUtils::IsAnyTableElement(parentNode1);
+ parentNode1 = parentNode1->GetParentNode()) {
+ }
+ nsINode* parentNode2;
+ for (parentNode2 = &aNode2;
+ parentNode2 && !HTMLEditUtils::IsAnyTableElement(parentNode2);
+ parentNode2 = parentNode2->GetParentNode()) {
+ }
+ // XXX Despite of the name, this returns true if only one node is in a
+ // table related element.
+ return parentNode1 != parentNode2;
+}
+
+nsresult HTMLEditor::RemoveEmptyNodesIn(nsRange& aRange) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aRange.IsPositioned());
+
+ // Some general notes on the algorithm used here: the goal is to examine all
+ // the nodes in aRange, and remove the empty ones. We do this by
+ // using a content iterator to traverse all the nodes in the range, and
+ // placing the empty nodes into an array. After finishing the iteration,
+ // we delete the empty nodes in the array. (They cannot be deleted as we
+ // find them because that would invalidate the iterator.)
+ //
+ // Since checking to see if a node is empty can be costly for nodes with
+ // many descendants, there are some optimizations made. I rely on the fact
+ // that the iterator is post-order: it will visit children of a node before
+ // visiting the parent node. So if I find that a child node is not empty, I
+ // know that its parent is not empty without even checking. So I put the
+ // parent on a "skipList" which is just a voidArray of nodes I can skip the
+ // empty check on. If I encounter a node on the skiplist, i skip the
+ // processing for that node and replace its slot in the skiplist with that
+ // node's parent.
+ //
+ // An interesting idea is to go ahead and regard parent nodes that are NOT
+ // on the skiplist as being empty (without even doing the IsEmptyNode check)
+ // on the theory that if they weren't empty, we would have encountered a
+ // non-empty child earlier and thus put this parent node on the skiplist.
+ //
+ // Unfortunately I can't use that strategy here, because the range may
+ // include some children of a node while excluding others. Thus I could
+ // find all the _examined_ children empty, but still not have an empty
+ // parent.
+
+ PostContentIterator postOrderIter;
+ nsresult rv = postOrderIter.Init(&aRange);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("PostContentIterator::Init() failed");
+ return rv;
+ }
+
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfEmptyContents,
+ arrayOfEmptyCites, skipList;
+
+ // Check for empty nodes
+ for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
+ MOZ_ASSERT(postOrderIter.GetCurrentNode()->IsContent());
+
+ nsIContent* content = postOrderIter.GetCurrentNode()->AsContent();
+ nsIContent* parentContent = content->GetParent();
+
+ size_t idx = skipList.IndexOf(content);
+ if (idx != skipList.NoIndex) {
+ // This node is on our skip list. Skip processing for this node, and
+ // replace its value in the skip list with the value of its parent
+ if (parentContent) {
+ skipList[idx] = parentContent;
+ }
+ continue;
+ }
+
+ bool isCandidate = false;
+ bool isMailCite = false;
+ if (content->IsElement()) {
+ if (content->IsHTMLElement(nsGkAtoms::body)) {
+ // Don't delete the body
+ } else if ((isMailCite = HTMLEditUtils::IsMailCite(content)) ||
+ content->IsHTMLElement(nsGkAtoms::a) ||
+ HTMLEditUtils::IsInlineStyle(content) ||
+ HTMLEditUtils::IsAnyListElement(content) ||
+ content->IsHTMLElement(nsGkAtoms::div)) {
+ // Only consider certain nodes to be empty for purposes of removal
+ isCandidate = true;
+ } else if (HTMLEditUtils::IsFormatNode(content) ||
+ HTMLEditUtils::IsListItem(content) ||
+ content->IsHTMLElement(nsGkAtoms::blockquote)) {
+ // These node types are candidates if selection is not in them. If
+ // it is one of these, don't delete if selection inside. This is so
+ // we can create empty headings, etc., for the user to type into.
+ isCandidate = !StartOrEndOfSelectionRangesIsIn(*content);
+ }
+ }
+
+ bool isEmptyNode = false;
+ if (isCandidate) {
+ // We delete mailcites even if they have a solo br in them. Other
+ // nodes we require to be empty.
+ isEmptyNode = IsEmptyNode(*content, isMailCite, true);
+ if (isEmptyNode) {
+ if (isMailCite) {
+ // mailcites go on a separate list from other empty nodes
+ arrayOfEmptyCites.AppendElement(*content);
+ } else {
+ arrayOfEmptyContents.AppendElement(*content);
+ }
+ }
+ }
+
+ if (!isEmptyNode && parentContent) {
+ // put parent on skip list
+ skipList.AppendElement(*parentContent);
+ }
+ }
+
+ // now delete the empty nodes
+ for (OwningNonNull<nsIContent>& emptyContent : arrayOfEmptyContents) {
+ // XXX Shouldn't we check whether it's removable from its parent??
+ if (HTMLEditUtils::IsSimplyEditableNode(emptyContent)) {
+ // MOZ_KnownLive because 'arrayOfEmptyContents' is guaranteed to keep it
+ // alive.
+ rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyContent));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+ }
+
+ // Now delete the empty mailcites. This is a separate step because we want
+ // to pull out any br's and preserve them.
+ for (OwningNonNull<nsIContent>& emptyCite : arrayOfEmptyCites) {
+ if (!IsEmptyNode(emptyCite, false, true)) {
+ // We are deleting a cite that has just a `<br>`. We want to delete cite,
+ // but preserve `<br>`.
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(EditorDOMPoint(emptyCite));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!brElement) {
+ NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+ // MOZ_KnownLive because 'arrayOfEmptyCites' is guaranteed to keep it alive.
+ rv = DeleteNodeWithTransaction(MOZ_KnownLive(emptyCite));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool HTMLEditor::StartOrEndOfSelectionRangesIsIn(nsIContent& aContent) const {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); ++i) {
+ const nsRange* range = SelectionRefPtr()->GetRangeAt(i);
+ nsINode* startContainer = range->GetStartContainer();
+ if (startContainer) {
+ if (&aContent == startContainer) {
+ return true;
+ }
+ if (EditorUtils::IsDescendantOf(*startContainer, aContent)) {
+ return true;
+ }
+ }
+ nsINode* endContainer = range->GetEndContainer();
+ if (startContainer == endContainer) {
+ continue;
+ }
+ if (endContainer) {
+ if (&aContent == endContainer) {
+ return true;
+ }
+ if (EditorUtils::IsDescendantOf(*endContainer, aContent)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsresult HTMLEditor::LiftUpListItemElement(
+ Element& aListItemElement,
+ LiftUpFromAllParentListElements aLiftUpFromAllParentListElements) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (!HTMLEditUtils::IsListItem(&aListItemElement)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(!aListItemElement.GetParentElement()) ||
+ NS_WARN_IF(!aListItemElement.GetParentElement()->GetParentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if it's first or last list item, don't need to split the list
+ // otherwise we do.
+ bool isFirstListItem = IsFirstEditableChild(&aListItemElement);
+ bool isLastListItem = IsLastEditableChild(&aListItemElement);
+
+ Element* leftListElement = aListItemElement.GetParentElement();
+ if (NS_WARN_IF(!leftListElement)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If it's at middle of parent list element, split the parent list element.
+ // Then, aListItem becomes the first list item of the right list element.
+ if (!isFirstListItem && !isLastListItem) {
+ EditorDOMPoint atListItemElement(&aListItemElement);
+ if (NS_WARN_IF(!atListItemElement.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(atListItemElement.IsSetAndValid());
+ ErrorResult error;
+ nsCOMPtr<nsIContent> maybeLeftListContent =
+ SplitNodeWithTransaction(atListItemElement, error);
+ if (NS_WARN_IF(Destroyed())) {
+ error = NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (error.Failed()) {
+ NS_WARNING_ASSERTION(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED),
+ "HTMLEditor::SplitNodeWithTransaction() failed");
+ return error.StealNSResult();
+ }
+ if (!maybeLeftListContent->IsElement()) {
+ NS_WARNING(
+ "HTMLEditor::SplitNodeWithTransaction() didn't return left list "
+ "element");
+ return NS_ERROR_FAILURE;
+ }
+ leftListElement = maybeLeftListContent->AsElement();
+ }
+
+ // In most cases, insert the list item into the new left list node..
+ EditorDOMPoint pointToInsertListItem(leftListElement);
+ if (NS_WARN_IF(!pointToInsertListItem.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // But when the list item was the first child of the right list, it should
+ // be inserted between the both list elements. This allows user to hit
+ // Enter twice at a list item breaks the parent list node.
+ if (!isFirstListItem) {
+ DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced,
+ "Failed to advance offset to right list node");
+ }
+
+ nsresult rv =
+ MoveNodeWithTransaction(aListItemElement, pointToInsertListItem);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
+ return rv;
+ }
+
+ // Unwrap list item contents if they are no longer in a list
+ // XXX If the parent list element is a child of another list element
+ // (although invalid tree), the list item element won't be unwrapped.
+ // That makes the parent ancestor element tree valid, but might be
+ // unexpected result.
+ // XXX If aListItemElement is <dl> or <dd> and current parent is <ul> or <ol>,
+ // the list items won't be unwrapped. If aListItemElement is <li> and its
+ // current parent is <dl>, there is same issue.
+ if (!HTMLEditUtils::IsAnyListElement(pointToInsertListItem.GetContainer()) &&
+ HTMLEditUtils::IsListItem(&aListItemElement)) {
+ nsresult rv = RemoveBlockContainerWithTransaction(aListItemElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return rv;
+ }
+ if (aLiftUpFromAllParentListElements == LiftUpFromAllParentListElements::No) {
+ return NS_OK;
+ }
+ // XXX If aListItemElement is moved to unexpected element by mutation event
+ // listener, shouldn't we stop calling this?
+ rv = LiftUpListItemElement(aListItemElement,
+ LiftUpFromAllParentListElements::Yes);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::LiftUpListItemElement("
+ "LiftUpFromAllParentListElements::Yes) failed");
+ return rv;
+}
+
+nsresult HTMLEditor::DestroyListStructureRecursively(Element& aListElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
+
+ // XXX If mutation event listener inserts new child into `aListElement`,
+ // this becomes infinite loop so that we should set limit of the
+ // loop count from original child count.
+ while (aListElement.GetFirstChild()) {
+ OwningNonNull<nsIContent> child = *aListElement.GetFirstChild();
+
+ if (HTMLEditUtils::IsListItem(child)) {
+ // XXX Using LiftUpListItemElement() is too expensive for this purpose.
+ // Looks like the reason why this method uses it is, only this loop
+ // wants to work with first child of aListElement. However, what it
+ // actually does is removing <li> as container. Perhaps, we should
+ // decide destination first, and then, move contents in `child`.
+ // XXX If aListElement is is a child of another list element (although
+ // it's invalid tree), this moves the list item to outside of
+ // aListElement's parent. Is that really intentional behavior?
+ nsresult rv = LiftUpListItemElement(
+ MOZ_KnownLive(*child->AsElement()),
+ HTMLEditor::LiftUpFromAllParentListElements::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::LiftUpListItemElement(LiftUpFromAllParentListElements:"
+ ":Yes) failed");
+ return rv;
+ }
+ continue;
+ }
+
+ if (HTMLEditUtils::IsAnyListElement(child)) {
+ nsresult rv =
+ DestroyListStructureRecursively(MOZ_KnownLive(*child->AsElement()));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DestroyListStructureRecursively() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ // Delete any non-list items for now
+ // XXX This is not HTML5 aware. HTML5 allows all list elements to have
+ // <script> and <template> and <dl> element to have <div> to group
+ // some <dt> and <dd> elements. So, this may break valid children.
+ nsresult rv = DeleteNodeWithTransaction(*child);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ }
+
+ // Delete the now-empty list
+ nsresult rv = RemoveBlockContainerWithTransaction(aListElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::RemoveBlockContainerWithTransaction() failed");
+ return rv;
+}
+
+nsresult HTMLEditor::EnsureSelectionInBodyOrDocumentElement() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ RefPtr<Element> bodyOrDocumentElement = GetRoot();
+ if (NS_WARN_IF(!bodyOrDocumentElement)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorRawDOMPoint atCaret(EditorBase::GetStartPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!atCaret.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX This does wrong things. Web apps can put any elements as sibling
+ // of `<body>` element. Therefore, this collapses `Selection` into
+ // the `<body>` element which `HTMLDocument.body` is set to. So,
+ // this makes users impossible to modify content outside of the
+ // `<body>` element even if caret is in an editing host.
+
+ // Check that selection start container is inside the <body> element.
+ // XXXsmaug this code is insane.
+ nsINode* temp = atCaret.GetContainer();
+ while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
+ temp = temp->GetParentOrShadowHostNode();
+ }
+
+ // If we aren't in the <body> element, force the issue.
+ if (!temp) {
+ nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
+ return NS_OK;
+ }
+
+ EditorRawDOMPoint selectionEndPoint(
+ EditorBase::GetEndPoint(*SelectionRefPtr()));
+ if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // check that selNode is inside body
+ // XXXsmaug this code is insane.
+ temp = selectionEndPoint.GetContainer();
+ while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
+ temp = temp->GetParentOrShadowHostNode();
+ }
+
+ // If we aren't in the <body> element, force the issue.
+ if (!temp) {
+ nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
+ }
+
+ return NS_OK;
+}
+
+nsresult HTMLEditor::InsertPaddingBRElementForEmptyLastLineIfNeeded(
+ Element& aElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (!HTMLEditUtils::IsBlockElement(aElement)) {
+ return NS_OK;
+ }
+
+ if (!IsEmptyNode(aElement)) {
+ return NS_OK;
+ }
+
+ CreateElementResult createBRResult =
+ InsertPaddingBRElementForEmptyLastLineWithTransaction(
+ EditorDOMPoint(&aElement, 0));
+ NS_WARNING_ASSERTION(
+ createBRResult.Succeeded(),
+ "HTMLEditor::InsertPaddingBRElementForEmptyLastLineWithTransaction() "
+ "failed");
+ return createBRResult.Rv();
+}
+
+nsresult HTMLEditor::InsertBRElementIfEmptyBlockElement(Element& aElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ if (!HTMLEditUtils::IsBlockElement(aElement)) {
+ return NS_OK;
+ }
+
+ if (!IsEmptyNode(aElement)) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> brElement =
+ InsertBRElementWithTransaction(EditorDOMPoint(&aElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(brElement,
+ "HTMELditor::InsertBRElementWithTransaction() failed");
+ return brElement ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult HTMLEditor::RemoveAlignFromDescendants(Element& aElement,
+ const nsAString& aAlignType,
+ EditTarget aEditTarget) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(!aElement.IsHTMLElement(nsGkAtoms::table));
+
+ bool useCSS = IsCSSEnabled();
+
+ // Let's remove all alignment hints in the children of aNode; it can
+ // be an ALIGN attribute (in case we just remove it) or a CENTER
+ // element (here we have to remove the container and keep its
+ // children). We break on tables and don't look at their children.
+ nsCOMPtr<nsIContent> nextSibling;
+ for (nsIContent* content =
+ aEditTarget == EditTarget::NodeAndDescendantsExceptTable
+ ? &aElement
+ : aElement.GetFirstChild();
+ content; content = nextSibling) {
+ // Get the next sibling before removing content from the DOM tree.
+ // XXX If next sibling is removed from the parent and/or inserted to
+ // different parent, we will behave unexpectedly. I think that
+ // we should create child list and handle it with checking whether
+ // it's still a child of expected parent.
+ nextSibling = aEditTarget == EditTarget::NodeAndDescendantsExceptTable
+ ? nullptr
+ : content->GetNextSibling();
+
+ if (content->IsHTMLElement(nsGkAtoms::center)) {
+ OwningNonNull<Element> centerElement = *content->AsElement();
+ nsresult rv = RemoveAlignFromDescendants(
+ centerElement, aAlignType, EditTarget::OnlyDescendantsExceptTable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
+ "OnlyDescendantsExceptTable) failed");
+ return rv;
+ }
+
+ // We may have to insert a `<br>` element before first child of the
+ // `<center>` element because it should be first element of a hard line
+ // even after removing the `<center>` element.
+ rv = EnsureHardLineBeginsWithFirstChildOf(centerElement);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed");
+ return rv;
+ }
+
+ // We may have to insert a `<br>` element after last child of the
+ // `<center>` element because it should be last element of a hard line
+ // even after removing the `<center>` element.
+ rv = EnsureHardLineEndsWithLastChildOf(centerElement);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed");
+ return rv;
+ }
+
+ rv = RemoveContainerWithTransaction(centerElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
+ return rv;
+ }
+ continue;
+ }
+
+ if (!HTMLEditUtils::IsBlockElement(*content) &&
+ !content->IsHTMLElement(nsGkAtoms::hr)) {
+ continue;
+ }
+
+ OwningNonNull<Element> blockOrHRElement = *content->AsElement();
+ if (HTMLEditUtils::SupportsAlignAttr(blockOrHRElement)) {
+ nsresult rv =
+ RemoveAttributeWithTransaction(blockOrHRElement, *nsGkAtoms::align);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::align) "
+ "failed");
+ return rv;
+ }
+ }
+ if (useCSS) {
+ if (blockOrHRElement->IsAnyOfHTMLElements(nsGkAtoms::table,
+ nsGkAtoms::hr)) {
+ nsresult rv = SetAttributeOrEquivalent(
+ blockOrHRElement, nsGkAtoms::align, aAlignType, false);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "EditorBase::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
+ return rv;
+ }
+ } else {
+ nsStyledElement* styledBlockOrHRElement =
+ nsStyledElement::FromNode(blockOrHRElement);
+ if (NS_WARN_IF(!styledBlockOrHRElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ // MOZ_KnownLive(*styledBlockOrHRElement): It's `blockOrHRElement
+ // which is OwningNonNull.
+ nsAutoString dummyCssValue;
+ nsresult rv = mCSSEditUtils->RemoveCSSInlineStyleWithTransaction(
+ MOZ_KnownLive(*styledBlockOrHRElement), nsGkAtoms::textAlign,
+ dummyCssValue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "CSSEditUtils::RemoveCSSInlineStyleWithTransaction(nsGkAtoms::"
+ "textAlign) failed");
+ return rv;
+ }
+ }
+ }
+ if (!blockOrHRElement->IsHTMLElement(nsGkAtoms::table)) {
+ // unless this is a table, look at children
+ nsresult rv = RemoveAlignFromDescendants(
+ blockOrHRElement, aAlignType, EditTarget::OnlyDescendantsExceptTable);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::RemoveAlignFromDescendants(EditTarget::"
+ "OnlyDescendantsExceptTable) failed");
+ return rv;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult HTMLEditor::EnsureHardLineBeginsWithFirstChildOf(
+ Element& aRemovingContainerElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsIContent* firstEditableChild =
+ GetFirstEditableChild(aRemovingContainerElement);
+ if (!firstEditableChild) {
+ return NS_OK;
+ }
+
+ if (HTMLEditUtils::IsBlockElement(*firstEditableChild) ||
+ firstEditableChild->IsHTMLElement(nsGkAtoms::br)) {
+ return NS_OK;
+ }
+
+ nsIContent* previousEditableContent =
+ GetPriorHTMLSibling(&aRemovingContainerElement);
+ if (!previousEditableContent) {
+ return NS_OK;
+ }
+
+ if (HTMLEditUtils::IsBlockElement(*previousEditableContent) ||
+ previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> brElement = InsertBRElementWithTransaction(
+ EditorDOMPoint(&aRemovingContainerElement, 0));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(brElement,
+ "HTMLEditor::InsertBRElementWithTransaction() failed");
+ return brElement ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult HTMLEditor::EnsureHardLineEndsWithLastChildOf(
+ Element& aRemovingContainerElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsIContent* firstEditableContent =
+ GetLastEditableChild(aRemovingContainerElement);
+ if (!firstEditableContent) {
+ return NS_OK;
+ }
+
+ if (HTMLEditUtils::IsBlockElement(*firstEditableContent) ||
+ firstEditableContent->IsHTMLElement(nsGkAtoms::br)) {
+ return NS_OK;
+ }
+
+ nsIContent* nextEditableContent =
+ GetPriorHTMLSibling(&aRemovingContainerElement);
+ if (!nextEditableContent) {
+ return NS_OK;
+ }
+
+ if (HTMLEditUtils::IsBlockElement(*nextEditableContent) ||
+ nextEditableContent->IsHTMLElement(nsGkAtoms::br)) {
+ return NS_OK;
+ }
+
+ RefPtr<Element> brElement = InsertBRElementWithTransaction(
+ EditorDOMPoint::AtEndOf(aRemovingContainerElement));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(brElement,
+ "HTMLEditor::InsertBRElementWithTransaction() failed");
+ return brElement ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult HTMLEditor::SetBlockElementAlign(Element& aBlockOrHRElement,
+ const nsAString& aAlignType,
+ EditTarget aEditTarget) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(HTMLEditUtils::IsBlockElement(aBlockOrHRElement) ||
+ aBlockOrHRElement.IsHTMLElement(nsGkAtoms::hr));
+ MOZ_ASSERT(IsCSSEnabled() ||
+ HTMLEditUtils::SupportsAlignAttr(aBlockOrHRElement));
+
+ if (!aBlockOrHRElement.IsHTMLElement(nsGkAtoms::table)) {
+ nsresult rv =
+ RemoveAlignFromDescendants(aBlockOrHRElement, aAlignType, aEditTarget);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::RemoveAlignFromDescendants() failed");
+ return rv;
+ }
+ }
+ nsresult rv = SetAttributeOrEquivalent(&aBlockOrHRElement, nsGkAtoms::align,
+ aAlignType, false);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::align) failed");
+ return rv;
+}
+
+nsresult HTMLEditor::ChangeMarginStart(Element& aElement,
+ ChangeMargin aChangeMargin) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ nsStaticAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ nsAutoString value;
+ DebugOnly<nsresult> rvIgnored =
+ CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "CSSEditUtils::GetSpecifiedProperty() failed, but ignored");
+ float f;
+ RefPtr<nsAtom> unit;
+ CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
+ if (!f) {
+ nsAutoString defaultLengthUnit;
+ CSSEditUtils::GetDefaultLengthUnit(defaultLengthUnit);
+ unit = NS_Atomize(defaultLengthUnit);
+ }
+ int8_t multiplier = aChangeMargin == ChangeMargin::Increase ? 1 : -1;
+ if (nsGkAtoms::in == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
+ } else if (nsGkAtoms::cm == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
+ } else if (nsGkAtoms::mm == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
+ } else if (nsGkAtoms::pt == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
+ } else if (nsGkAtoms::pc == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
+ } else if (nsGkAtoms::em == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
+ } else if (nsGkAtoms::ex == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
+ } else if (nsGkAtoms::px == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
+ } else if (nsGkAtoms::percentage == unit) {
+ f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
+ }
+
+ if (0 < f) {
+ if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
+ nsAutoString newValue;
+ newValue.AppendFloat(f);
+ newValue.Append(nsDependentAtomString(unit));
+ // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
+ // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
+ // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
+ nsresult rv = mCSSEditUtils->SetCSSPropertyWithTransaction(
+ MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty),
+ newValue);
+ if (rv == NS_ERROR_EDITOR_DESTROYED) {
+ NS_WARNING(
+ "CSSEditUtils::SetCSSPropertyWithTransaction() destroyed the "
+ "editor");
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "CSSEditUtils::SetCSSPropertyWithTransaction() failed, but ignored");
+ }
+ return NS_OK;
+ }
+
+ if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
+ // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
+ // be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method.
+ // MOZ_KnownLive(merginProperty): It's nsStaticAtom.
+ nsresult rv = mCSSEditUtils->RemoveCSSPropertyWithTransaction(
+ MOZ_KnownLive(*styledElement), MOZ_KnownLive(marginProperty), value);
+ if (rv == NS_ERROR_EDITOR_DESTROYED) {
+ NS_WARNING(
+ "CSSEditUtils::RemoveCSSPropertyWithTransaction() destroyed the "
+ "editor");
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "CSSEditUtils::RemoveCSSPropertyWithTransaction() failed, but ignored");
+ }
+
+ // Remove unnecessary divs
+ if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
+ HTMLEditor::HasAttributes(&aElement)) {
+ return NS_OK;
+ }
+ // Don't touch editiong host nor node which is outside of it.
+ Element* editingHost = GetActiveEditingHost();
+ if (&aElement == editingHost ||
+ !aElement.IsInclusiveDescendantOf(editingHost)) {
+ return NS_OK;
+ }
+
+ nsresult rv = RemoveContainerWithTransaction(aElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::RemoveContainerWithTransaction() failed");
+ return rv;
+}
+
+EditActionResult HTMLEditor::SetSelectionToAbsoluteAsSubAction() {
+ MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eSetPositionToAbsolute, nsIEditor::eNext,
+ ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditgor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ RefPtr<Element> focusElement = GetSelectionContainerElement();
+ if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
+ TopLevelEditSubActionDataRef().mNewBlockElement = std::move(focusElement);
+ return EditActionHandled();
+ }
+
+ if (!SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = MaybeExtendSelectionToHardLineEdgesForBlockEditAction();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() "
+ "failed");
+ return EditActionHandled(rv);
+ }
+ }
+
+ // XXX Is this right thing?
+ TopLevelEditSubActionDataRef().mNewBlockElement = nullptr;
+
+ RefPtr<Element> divElement;
+ rv = MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
+ address_of(divElement));
+ // MoveSelectedContentsToDivElementToMakeItAbsolutePosition() may restore
+ // selection with AutoSelectionRestorer. Therefore, the editor might have
+ // already been destroyed now.
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition()"
+ " failed");
+ return EditActionHandled(rv);
+ }
+
+ if (IsSelectionRangeContainerNotContent()) {
+ NS_WARNING("Mutation event listener might have changed the selection");
+ return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return EditActionHandled(rv);
+ }
+
+ if (!divElement) {
+ return EditActionHandled();
+ }
+
+ rv = SetPositionToAbsoluteOrStatic(*divElement, true);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
+
+ TopLevelEditSubActionDataRef().mNewBlockElement = std::move(divElement);
+ return EditActionHandled(rv);
+}
+
+nsresult HTMLEditor::MoveSelectedContentsToDivElementToMakeItAbsolutePosition(
+ RefPtr<Element>* aTargetElement) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+ MOZ_ASSERT(aTargetElement);
+
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ AutoTArray<RefPtr<nsRange>, 4> arrayOfRanges;
+ GetSelectionRangesExtendedToHardLineStartAndEnd(
+ arrayOfRanges, EditSubAction::eSetPositionToAbsolute);
+
+ // Use these ranges to construct a list of nodes to act on.
+ AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
+ nsresult rv = SplitInlinesAndCollectEditTargetNodes(
+ arrayOfRanges, arrayOfContents, EditSubAction::eSetPositionToAbsolute,
+ CollectNonEditableNodes::Yes);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "HTMLEditor::SplitInlinesAndCollectEditTargetNodes("
+ "eSetPositionToAbsolute, CollectNonEditableNodes::Yes) failed");
+ return rv;
+ }
+
+ // If there is no visible and editable nodes in the edit targets, make an
+ // empty block.
+ // XXX Isn't this odd if there are only non-editable visible nodes?
+ if (IsEmptyOneHardLine(arrayOfContents)) {
+ const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
+ if (NS_WARN_IF(!firstRange)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EditorDOMPoint atCaret(firstRange->StartRef());
+ if (NS_WARN_IF(!atCaret.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure we can put a block here.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, atCaret);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms::"
+ "div) failed");
+ return splitNodeResult.Rv();
+ }
+ RefPtr<Element> newDivElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!newDivElement) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ // Delete anything that was in the list of nodes
+ // XXX We don't need to remove items from the array.
+ while (!arrayOfContents.IsEmpty()) {
+ OwningNonNull<nsIContent>& curNode = arrayOfContents[0];
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
+ nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(*curNode));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
+ return rv;
+ }
+ arrayOfContents.RemoveElementAt(0);
+ }
+ // Don't restore the selection
+ restoreSelectionLater.Abort();
+ nsresult rv = CollapseSelectionToStartOf(*newDivElement);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::CollapseSelectionToStartOf() failed");
+ *aTargetElement = std::move(newDivElement);
+ return rv;
+ }
+
+ // `<div>` element to be positioned absolutely. This may have already
+ // existed or newly created by this method.
+ RefPtr<Element> targetDivElement;
+ // Newly created list element for moving selected list item elements into
+ // targetDivElement. I.e., this is created in the `<div>` element.
+ RefPtr<Element> createdListElement;
+ // If we handle a parent list item element, this is set to it. In such case,
+ // we should handle its children again.
+ RefPtr<Element> handledListItemElement;
+ for (OwningNonNull<nsIContent>& content : arrayOfContents) {
+ // Here's where we actually figure out what to do.
+ EditorDOMPoint atContent(content);
+ if (NS_WARN_IF(!atContent.IsSet())) {
+ return NS_ERROR_FAILURE; // XXX not continue??
+ }
+
+ // Ignore all non-editable nodes. Leave them be.
+ if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
+ continue;
+ }
+
+ // If current node is a child of a list element, we need another list
+ // element in absolute-positioned `<div>` element to avoid non-selected
+ // list items are moved into the `<div>` element.
+ if (HTMLEditUtils::IsAnyListElement(atContent.GetContainer())) {
+ // If we cannot move current node to created list element, we need a
+ // list element in the target `<div>` element for the destination.
+ // Therefore, duplicate same list element into the target `<div>`
+ // element.
+ nsIContent* previousEditableContent =
+ createdListElement ? GetPriorHTMLSibling(content) : nullptr;
+ if (!createdListElement ||
+ (previousEditableContent &&
+ previousEditableContent != createdListElement)) {
+ nsAtom* ULOrOLOrDLTagName =
+ atContent.GetContainer()->NodeInfo()->NameAtom();
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(
+ MOZ_KnownLive(*ULOrOLOrDLTagName), atContent);
+ if (NS_WARN_IF(splitNodeResult.Failed())) {
+ return splitNodeResult.Rv();
+ }
+ if (!targetDivElement) {
+ targetDivElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_WARN_IF(!targetDivElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ createdListElement = CreateNodeWithTransaction(
+ MOZ_KnownLive(*ULOrOLOrDLTagName),
+ EditorDOMPoint::AtEndOf(targetDivElement));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_WARN_IF(!createdListElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ // Move current node (maybe, assumed as a list item element) into the
+ // new list element in the target `<div>` element to be positioned
+ // absolutely.
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
+ nsresult rv = MoveNodeToEndWithTransaction(MOZ_KnownLive(content),
+ *createdListElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ continue;
+ }
+
+ // If contents in a list item element is selected, we should move current
+ // node into the target `<div>` element with the list item element itself
+ // because we want to keep indent level of the contents.
+ if (RefPtr<Element> listItemElement =
+ GetNearestAncestorListItemElement(content)) {
+ if (handledListItemElement == listItemElement) {
+ // Current node has already been moved into the `<div>` element.
+ continue;
+ }
+ // If we cannot move the list item element into created list element,
+ // we need another list element in the target `<div>` element.
+ nsIContent* previousEditableContent =
+ createdListElement ? GetPriorHTMLSibling(listItemElement) : nullptr;
+ if (!createdListElement ||
+ (previousEditableContent &&
+ previousEditableContent != createdListElement)) {
+ EditorDOMPoint atListItem(listItemElement);
+ if (NS_WARN_IF(!atListItem.IsSet())) {
+ return NS_ERROR_FAILURE;
+ }
+ // XXX If content is the listItemElement and not in a list element,
+ // we duplicate wrong element into the target `<div>` element.
+ nsAtom* containerName =
+ atListItem.GetContainer()->NodeInfo()->NameAtom();
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(
+ MOZ_KnownLive(*containerName), atListItem);
+ if (NS_WARN_IF(splitNodeResult.Failed())) {
+ return splitNodeResult.Rv();
+ }
+ if (!targetDivElement) {
+ targetDivElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, EditorDOMPoint(atListItem.GetContainer()));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_WARN_IF(!targetDivElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ // XXX So, createdListElement may be set to a non-list element.
+ createdListElement = CreateNodeWithTransaction(
+ MOZ_KnownLive(*containerName),
+ EditorDOMPoint::AtEndOf(targetDivElement));
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_WARN_IF(!createdListElement)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ // Move current list item element into the createdListElement (could be
+ // non-list element due to the above bug) in a candidate `<div>` element
+ // to be positioned absolutely.
+ nsresult rv =
+ MoveNodeToEndWithTransaction(*listItemElement, *createdListElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ handledListItemElement = std::move(listItemElement);
+ continue;
+ }
+
+ if (!targetDivElement) {
+ // If we meet a `<div>` element, use it as the absolute-position
+ // container.
+ // XXX This looks odd. If there are 2 or more `<div>` elements are
+ // selected, first found `<div>` element will have all other
+ // selected nodes.
+ if (content->IsHTMLElement(nsGkAtoms::div)) {
+ targetDivElement = content->AsElement();
+ MOZ_ASSERT(!createdListElement);
+ MOZ_ASSERT(!handledListItemElement);
+ continue;
+ }
+ // Otherwise, create new `<div>` element to be positioned absolutely
+ // and to contain all selected nodes.
+ SplitNodeResult splitNodeResult =
+ MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div,
+ atContent);
+ if (splitNodeResult.Failed()) {
+ NS_WARNING(
+ "HTMLEditor::MaybeSplitAncestorsForInsertWithTransaction(nsGkAtoms:"
+ ":div) failed");
+ return splitNodeResult.Rv();
+ }
+ targetDivElement = CreateNodeWithTransaction(
+ *nsGkAtoms::div, splitNodeResult.SplitPoint());
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (!targetDivElement) {
+ NS_WARNING(
+ "EditorBase::CreateNodeWithTransaction(nsGkAtoms::div) failed");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it alive.
+ nsresult rv =
+ MoveNodeToEndWithTransaction(MOZ_KnownLive(content), *targetDivElement);
+ if (NS_WARN_IF(Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
+ return rv;
+ }
+ // Forget createdListElement, if any
+ createdListElement = nullptr;
+ }
+ *aTargetElement = std::move(targetDivElement);
+ return NS_OK;
+}
+
+EditActionResult HTMLEditor::SetSelectionToStaticAsSubAction() {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this, EditSubAction::eSetPositionToStatic, nsIEditor::eNext,
+ ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ RefPtr<Element> element = GetAbsolutelyPositionedSelectionContainer();
+ if (!element) {
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING(
+ "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
+ "nullptr");
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ {
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ nsresult rv = SetPositionToAbsoluteOrStatic(*element, false);
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING("HTMLEditor::SetPositionToAbsoluteOrStatic() failed");
+ return EditActionHandled(rv);
+ }
+ }
+
+ // Restoring Selection might cause destroying the HTML editor.
+ return NS_WARN_IF(Destroyed()) ? EditActionHandled(NS_ERROR_EDITOR_DESTROYED)
+ : EditActionHandled(NS_OK);
+}
+
+EditActionResult HTMLEditor::AddZIndexAsSubAction(int32_t aChange) {
+ MOZ_ASSERT(IsEditActionDataAvailable());
+
+ AutoPlaceholderBatch treatAsOneTransaction(*this,
+ ScrollSelectionIntoView::Yes);
+ IgnoredErrorResult ignoredError;
+ AutoEditSubActionNotifier startToHandleEditSubAction(
+ *this,
+ aChange < 0 ? EditSubAction::eDecreaseZIndex
+ : EditSubAction::eIncreaseZIndex,
+ nsIEditor::eNext, ignoredError);
+ if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
+ return EditActionResult(ignoredError.StealNSResult());
+ }
+ NS_WARNING_ASSERTION(
+ !ignoredError.Failed(),
+ "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
+
+ EditActionResult result = CanHandleHTMLEditSubAction();
+ if (result.Failed() || result.Canceled()) {
+ NS_WARNING_ASSERTION(result.Succeeded(),
+ "HTMLEditor::CanHandleHTMLEditSubAction() failed");
+ return result;
+ }
+
+ nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
+ "failed, but ignored");
+
+ if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
+ nsresult rv = EnsureCaretNotAfterPaddingBRElement();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
+ "failed, but ignored");
+ if (NS_SUCCEEDED(rv)) {
+ nsresult rv = PrepareInlineStylesForCaret();
+ if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
+ }
+ }
+
+ RefPtr<Element> absolutelyPositionedElement =
+ GetAbsolutelyPositionedSelectionContainer();
+ if (!absolutelyPositionedElement) {
+ if (NS_WARN_IF(Destroyed())) {
+ return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
+ }
+ NS_WARNING(
+ "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() returned "
+ "nullptr");
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ nsStyledElement* absolutelyPositionedStyledElement =
+ nsStyledElement::FromNode(absolutelyPositionedElement);
+ if (NS_WARN_IF(!absolutelyPositionedStyledElement)) {
+ return EditActionHandled(NS_ERROR_FAILURE);
+ }
+
+ {
+ AutoSelectionRestorer restoreSelectionLater(*this);
+
+ // MOZ_KnownLive(*absolutelyPositionedStyledElement): It's
+ // absolutelyPositionedElement whose type is RefPtr.
+ Result<int32_t, nsresult> result = AddZIndexWithTransaction(
+ MOZ_KnownLive(*absolutelyPositionedStyledElement), aChange);
+ if (result.isErr()) {
+ NS_WARNING("HTMLEditor::AddZIndexWithTransaction() failed");
+ return EditActionHandled(result.unwrapErr());
+ }
+ }
+
+ // Restoring Selection might cause destroying the HTML editor.
+ return NS_WARN_IF(Destroyed()) ? EditActionHandled(NS_ERROR_EDITOR_DESTROYED)
+ : EditActionHandled(NS_OK);
+}
+
+nsresult HTMLEditor::OnDocumentModified() {
+ if (mPendingDocumentModifiedRunner) {
+ return NS_OK; // We've already posted same runnable into the queue.
+ }
+ mPendingDocumentModifiedRunner = NewRunnableMethod(
+ "HTMLEditor::OnModifyDocument", this, &HTMLEditor::OnModifyDocument);
+ nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner));
+ // Be aware, if OnModifyDocument() may be called synchronously, the
+ // editor might have been destroyed here.
+ return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
+}
+
+} // namespace mozilla