summaryrefslogtreecommitdiff
path: root/editor/libeditor/HTMLEditRules.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/HTMLEditRules.cpp')
-rw-r--r--editor/libeditor/HTMLEditRules.cpp8785
1 files changed, 8785 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp
new file mode 100644
index 0000000000..c2d61f767b
--- /dev/null
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -0,0 +1,8785 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=79: */
+/* 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 "HTMLEditRules.h"
+
+#include <stdlib.h>
+
+#include "HTMLEditUtils.h"
+#include "TextEditUtils.h"
+#include "WSRunObject.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CSSEditUtils.h"
+#include "mozilla/EditorUtils.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/mozalloc.h"
+#include "nsAutoPtr.h"
+#include "nsAString.h"
+#include "nsAlgorithm.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsIContentIterator.h"
+#include "nsID.h"
+#include "nsIDOMCharacterData.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMText.h"
+#include "nsIFrame.h"
+#include "nsIHTMLAbsPosEditor.h"
+#include "nsIHTMLDocument.h"
+#include "nsINode.h"
+#include "nsLiteralString.h"
+#include "nsRange.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsTextNode.h"
+#include "nsThreadUtils.h"
+#include "nsUnicharUtils.h"
+#include <algorithm>
+
+// Workaround for windows headers
+#ifdef SetProp
+#undef SetProp
+#endif
+
+class nsISupports;
+
+namespace mozilla {
+
+class RulesInfo;
+
+using namespace dom;
+
+//const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
+//const static char* kMOZEditorBogusNodeValue="TRUE";
+
+enum
+{
+ kLonely = 0,
+ kPrevSib = 1,
+ kNextSib = 2,
+ kBothSibs = 3
+};
+
+/********************************************************
+ * first some helpful functors we will use
+ ********************************************************/
+
+static bool IsBlockNode(const nsINode& node)
+{
+ return HTMLEditor::NodeIsBlockStatic(&node);
+}
+
+static bool IsInlineNode(const nsINode& node)
+{
+ return !IsBlockNode(node);
+}
+
+static bool
+IsStyleCachePreservingAction(EditAction action)
+{
+ return action == EditAction::deleteSelection ||
+ action == EditAction::insertBreak ||
+ action == EditAction::makeList ||
+ action == EditAction::indent ||
+ action == EditAction::outdent ||
+ action == EditAction::align ||
+ action == EditAction::makeBasicBlock ||
+ action == EditAction::removeList ||
+ action == EditAction::makeDefListItem ||
+ action == EditAction::insertElement ||
+ action == EditAction::insertQuotation;
+}
+
+class TableCellAndListItemFunctor final : public BoolDomIterFunctor
+{
+public:
+ // Used to build list of all li's, td's & th's iterator covers
+ virtual bool operator()(nsINode* aNode) const
+ {
+ return HTMLEditUtils::IsTableCell(aNode) ||
+ HTMLEditUtils::IsListItem(aNode);
+ }
+};
+
+class BRNodeFunctor final : public BoolDomIterFunctor
+{
+public:
+ virtual bool operator()(nsINode* aNode) const
+ {
+ return aNode->IsHTMLElement(nsGkAtoms::br);
+ }
+};
+
+class EmptyEditableFunctor final : public BoolDomIterFunctor
+{
+public:
+ explicit EmptyEditableFunctor(HTMLEditor* aHTMLEditor)
+ : mHTMLEditor(aHTMLEditor)
+ {}
+
+ virtual bool operator()(nsINode* aNode) const
+ {
+ if (mHTMLEditor->IsEditable(aNode) &&
+ (HTMLEditUtils::IsListItem(aNode) ||
+ HTMLEditUtils::IsTableCellOrCaption(*aNode))) {
+ bool bIsEmptyNode;
+ nsresult rv =
+ mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (bIsEmptyNode) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+protected:
+ HTMLEditor* mHTMLEditor;
+};
+
+/********************************************************
+ * mozilla::HTMLEditRules
+ ********************************************************/
+
+HTMLEditRules::HTMLEditRules()
+ : mHTMLEditor(nullptr)
+ , mListenerEnabled(false)
+ , mReturnInEmptyLIKillsList(false)
+ , mDidDeleteSelection(false)
+ , mDidRangedDelete(false)
+ , mRestoreContentEditableCount(false)
+ , mJoinOffset(0)
+{
+ InitFields();
+}
+
+void
+HTMLEditRules::InitFields()
+{
+ mHTMLEditor = nullptr;
+ mDocChangeRange = nullptr;
+ mListenerEnabled = true;
+ mReturnInEmptyLIKillsList = true;
+ mDidDeleteSelection = false;
+ mDidRangedDelete = false;
+ mRestoreContentEditableCount = false;
+ mUtilRange = nullptr;
+ mJoinOffset = 0;
+ mNewBlock = nullptr;
+ mRangeItem = new RangeItem();
+ // populate mCachedStyles
+ mCachedStyles[0] = StyleCache(nsGkAtoms::b, EmptyString(), EmptyString());
+ mCachedStyles[1] = StyleCache(nsGkAtoms::i, EmptyString(), EmptyString());
+ mCachedStyles[2] = StyleCache(nsGkAtoms::u, EmptyString(), EmptyString());
+ mCachedStyles[3] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("face"), EmptyString());
+ mCachedStyles[4] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("size"), EmptyString());
+ mCachedStyles[5] = StyleCache(nsGkAtoms::font, NS_LITERAL_STRING("color"), EmptyString());
+ mCachedStyles[6] = StyleCache(nsGkAtoms::tt, EmptyString(), EmptyString());
+ mCachedStyles[7] = StyleCache(nsGkAtoms::em, EmptyString(), EmptyString());
+ mCachedStyles[8] = StyleCache(nsGkAtoms::strong, EmptyString(), EmptyString());
+ mCachedStyles[9] = StyleCache(nsGkAtoms::dfn, EmptyString(), EmptyString());
+ mCachedStyles[10] = StyleCache(nsGkAtoms::code, EmptyString(), EmptyString());
+ mCachedStyles[11] = StyleCache(nsGkAtoms::samp, EmptyString(), EmptyString());
+ mCachedStyles[12] = StyleCache(nsGkAtoms::var, EmptyString(), EmptyString());
+ mCachedStyles[13] = StyleCache(nsGkAtoms::cite, EmptyString(), EmptyString());
+ mCachedStyles[14] = StyleCache(nsGkAtoms::abbr, EmptyString(), EmptyString());
+ mCachedStyles[15] = StyleCache(nsGkAtoms::acronym, EmptyString(), EmptyString());
+ mCachedStyles[16] = StyleCache(nsGkAtoms::backgroundColor, EmptyString(), EmptyString());
+ mCachedStyles[17] = StyleCache(nsGkAtoms::sub, EmptyString(), EmptyString());
+ mCachedStyles[18] = StyleCache(nsGkAtoms::sup, EmptyString(), EmptyString());
+}
+
+HTMLEditRules::~HTMLEditRules()
+{
+ // remove ourselves as a listener to edit actions
+ // In some cases, we have already been removed by
+ // ~HTMLEditor, in which case we will get a null pointer here
+ // which we ignore. But this allows us to add the ability to
+ // switch rule sets on the fly if we want.
+ if (mHTMLEditor) {
+ mHTMLEditor->RemoveEditActionListener(this);
+ }
+}
+
+NS_IMPL_ADDREF_INHERITED(HTMLEditRules, TextEditRules)
+NS_IMPL_RELEASE_INHERITED(HTMLEditRules, TextEditRules)
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLEditRules)
+ NS_INTERFACE_TABLE_INHERITED(HTMLEditRules, nsIEditActionListener)
+NS_INTERFACE_TABLE_TAIL_INHERITING(TextEditRules)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules,
+ mDocChangeRange, mUtilRange, mNewBlock,
+ mRangeItem)
+
+NS_IMETHODIMP
+HTMLEditRules::Init(TextEditor* aTextEditor)
+{
+ InitFields();
+
+ mHTMLEditor = static_cast<HTMLEditor*>(aTextEditor);
+
+ // call through to base class Init
+ nsresult rv = TextEditRules::Init(aTextEditor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // cache any prefs we care about
+ static const char kPrefName[] =
+ "editor.html.typing.returnInEmptyListItemClosesList";
+ nsAdoptingCString returnInEmptyLIKillsList =
+ Preferences::GetCString(kPrefName);
+
+ // only when "false", becomes FALSE. Otherwise (including empty), TRUE.
+ // XXX Why was this pref designed as a string and not bool?
+ mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");
+
+ // make a utility range for use by the listenter
+ nsCOMPtr<nsINode> node = mHTMLEditor->GetRoot();
+ if (!node) {
+ node = mHTMLEditor->GetDocument();
+ }
+
+ NS_ENSURE_STATE(node);
+
+ mUtilRange = new nsRange(node);
+
+ // set up mDocChangeRange to be whole doc
+ // temporarily turn off rules sniffing
+ AutoLockRulesSniffing lockIt(this);
+ if (!mDocChangeRange) {
+ mDocChangeRange = new nsRange(node);
+ }
+
+ if (node->IsElement()) {
+ ErrorResult rv;
+ mDocChangeRange->SelectNode(*node, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ AdjustSpecialBreaks();
+ }
+
+ // add ourselves as a listener to edit actions
+ return mHTMLEditor->AddEditActionListener(this);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DetachEditor()
+{
+ if (mHTMLEditor) {
+ mHTMLEditor->RemoveEditActionListener(this);
+ }
+ mHTMLEditor = nullptr;
+ return TextEditRules::DetachEditor();
+}
+
+NS_IMETHODIMP
+HTMLEditRules::BeforeEdit(EditAction action,
+ nsIEditor::EDirection aDirection)
+{
+ if (mLockRulesSniffing) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ AutoLockRulesSniffing lockIt(this);
+ mDidExplicitlySetInterline = false;
+
+ if (!mActionNesting) {
+ mActionNesting++;
+
+ // Clear our flag about if just deleted a range
+ mDidRangedDelete = false;
+
+ // Remember where our selection was before edit action took place:
+
+ // Get selection
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+
+ // Get the selection location
+ if (NS_WARN_IF(!selection) || !selection->RangeCount()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRangeItem->startNode = selection->GetRangeAt(0)->GetStartParent();
+ mRangeItem->startOffset = selection->GetRangeAt(0)->StartOffset();
+ mRangeItem->endNode = selection->GetRangeAt(0)->GetEndParent();
+ mRangeItem->endOffset = selection->GetRangeAt(0)->EndOffset();
+ nsCOMPtr<nsINode> selStartNode = mRangeItem->startNode;
+ nsCOMPtr<nsINode> selEndNode = mRangeItem->endNode;
+
+ // Register with range updater to track this as we perturb the doc
+ htmlEditor->mRangeUpdater.RegisterRangeItem(mRangeItem);
+
+ // Clear deletion state bool
+ mDidDeleteSelection = false;
+
+ // Clear out mDocChangeRange and mUtilRange
+ if (mDocChangeRange) {
+ // Clear out our accounting of what changed
+ mDocChangeRange->Reset();
+ }
+ if (mUtilRange) {
+ // Ditto for mUtilRange.
+ mUtilRange->Reset();
+ }
+
+ // Remember current inline styles for deletion and normal insertion ops
+ if (action == EditAction::insertText ||
+ action == EditAction::insertIMEText ||
+ action == EditAction::deleteSelection ||
+ IsStyleCachePreservingAction(action)) {
+ nsCOMPtr<nsINode> selNode =
+ aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
+ nsresult rv = CacheInlineStyles(GetAsDOMNode(selNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Stabilize the document against contenteditable count changes
+ nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
+ NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
+ if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
+ htmlDoc->ChangeContentEditableCount(nullptr, +1);
+ mRestoreContentEditableCount = true;
+ }
+
+ // Check that selection is in subtree defined by body node
+ ConfirmSelectionInBody();
+ // Let rules remember the top level action
+ mTheAction = action;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+HTMLEditRules::AfterEdit(EditAction action,
+ nsIEditor::EDirection aDirection)
+{
+ if (mLockRulesSniffing) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ AutoLockRulesSniffing lockIt(this);
+
+ MOZ_ASSERT(mActionNesting > 0);
+ nsresult rv = NS_OK;
+ mActionNesting--;
+ if (!mActionNesting) {
+ // Do all the tricky stuff
+ rv = AfterEditInner(action, aDirection);
+
+ // Free up selectionState range item
+ htmlEditor->mRangeUpdater.DropRangeItem(mRangeItem);
+
+ // Reset the contenteditable count to its previous value
+ if (mRestoreContentEditableCount) {
+ nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
+ NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
+ if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
+ htmlDoc->ChangeContentEditableCount(nullptr, -1);
+ }
+ mRestoreContentEditableCount = false;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::AfterEditInner(EditAction action,
+ nsIEditor::EDirection aDirection)
+{
+ ConfirmSelectionInBody();
+ if (action == EditAction::ignore) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ NS_ENSURE_STATE(selection);
+
+ nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent;
+ int32_t rangeStartOffset = 0, rangeEndOffset = 0;
+ // do we have a real range to act on?
+ bool bDamagedRange = false;
+ if (mDocChangeRange) {
+ mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent));
+ mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent));
+ mDocChangeRange->GetStartOffset(&rangeStartOffset);
+ mDocChangeRange->GetEndOffset(&rangeEndOffset);
+ if (rangeStartParent && rangeEndParent)
+ bDamagedRange = true;
+ }
+
+ if (bDamagedRange && !((action == EditAction::undo) ||
+ (action == EditAction::redo))) {
+ // 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.
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
+
+ // expand the "changed doc range" as needed
+ PromoteRange(*mDocChangeRange, action);
+
+ // 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 EditAction::loadHTML, EditAction::insertText, etc.
+ // That's why this is here rather than DidDeleteSelection().
+ if (action == EditAction::deleteSelection && mDidRangedDelete) {
+ nsresult rv = InsertBRIfNeeded(selection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // add in any needed <br>s, and remove any unneeded ones.
+ AdjustSpecialBreaks();
+
+ // merge any adjacent text nodes
+ if (action != EditAction::insertText &&
+ action != EditAction::insertIMEText) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // clean up any empty nodes in the selection
+ nsresult rv = RemoveEmptyNodes();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // attempt to transform any unneeded nbsp's into spaces after doing various operations
+ if (action == EditAction::insertText ||
+ action == EditAction::insertIMEText ||
+ action == EditAction::deleteSelection ||
+ action == EditAction::insertBreak ||
+ action == EditAction::htmlPaste ||
+ action == EditAction::loadHTML) {
+ rv = AdjustWhitespace(selection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // also do this for original selection endpoints.
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(mRangeItem->startNode);
+ NS_ENSURE_STATE(mRangeItem->endNode);
+ WSRunObject(mHTMLEditor, mRangeItem->startNode,
+ mRangeItem->startOffset).AdjustWhitespace();
+ // we only need to handle old selection endpoint if it was different from start
+ if (mRangeItem->startNode != mRangeItem->endNode ||
+ mRangeItem->startOffset != mRangeItem->endOffset) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject(mHTMLEditor, mRangeItem->endNode,
+ mRangeItem->endOffset).AdjustWhitespace();
+ }
+ }
+
+ // if we created a new block, make sure selection lands in it
+ if (mNewBlock) {
+ rv = PinSelectionToNewBlock(selection);
+ mNewBlock = nullptr;
+ }
+
+ // adjust selection for insert text, html paste, and delete actions
+ if (action == EditAction::insertText ||
+ action == EditAction::insertIMEText ||
+ action == EditAction::deleteSelection ||
+ action == EditAction::insertBreak ||
+ action == EditAction::htmlPaste ||
+ action == EditAction::loadHTML) {
+ rv = AdjustSelection(selection, aDirection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // check for any styles which were removed inappropriately
+ if (action == EditAction::insertText ||
+ action == EditAction::insertIMEText ||
+ action == EditAction::deleteSelection ||
+ IsStyleCachePreservingAction(action)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->mTypeInState->UpdateSelState(selection);
+ rv = ReapplyCachedStyles();
+ NS_ENSURE_SUCCESS(rv, rv);
+ ClearCachedStyles();
+ }
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+
+ nsresult rv =
+ mHTMLEditor->HandleInlineSpellCheck(action, selection,
+ GetAsDOMNode(mRangeItem->startNode),
+ mRangeItem->startOffset,
+ rangeStartParent, rangeStartOffset,
+ rangeEndParent, rangeEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // detect empty doc
+ rv = CreateBogusNodeIfNeeded(selection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // adjust selection HINT if needed
+ if (!mDidExplicitlySetInterline) {
+ CheckInterlinePosition(*selection);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillDoAction(Selection* aSelection,
+ RulesInfo* aInfo,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aInfo && aCancel && aHandled);
+
+ *aCancel = false;
+ *aHandled = false;
+
+ // my kingdom for dynamic cast
+ TextRulesInfo* info = static_cast<TextRulesInfo*>(aInfo);
+
+ // Deal with actions for which we don't need to check whether the selection is
+ // editable.
+ if (info->action == EditAction::outputText ||
+ info->action == EditAction::undo ||
+ info->action == EditAction::redo) {
+ return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
+ }
+
+ // Nothing to do if there's no selection to act on
+ if (!aSelection) {
+ return NS_OK;
+ }
+ NS_ENSURE_TRUE(aSelection->RangeCount(), NS_OK);
+
+ RefPtr<nsRange> range = aSelection->GetRangeAt(0);
+ nsCOMPtr<nsINode> selStartNode = range->GetStartParent();
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsModifiableNode(selStartNode)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> selEndNode = range->GetEndParent();
+
+ if (selStartNode != selEndNode) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsModifiableNode(selEndNode)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
+ *aCancel = true;
+ return NS_OK;
+ }
+ }
+
+ switch (info->action) {
+ case EditAction::insertText:
+ case EditAction::insertIMEText:
+ UndefineCaretBidiLevel(aSelection);
+ return WillInsertText(info->action, aSelection, aCancel, aHandled,
+ info->inString, info->outString, info->maxLength);
+ case EditAction::loadHTML:
+ return WillLoadHTML(aSelection, aCancel);
+ case EditAction::insertBreak:
+ UndefineCaretBidiLevel(aSelection);
+ return WillInsertBreak(*aSelection, aCancel, aHandled);
+ case EditAction::deleteSelection:
+ return WillDeleteSelection(aSelection, info->collapsedAction,
+ info->stripWrappers, aCancel, aHandled);
+ case EditAction::makeList:
+ return WillMakeList(aSelection, info->blockType, info->entireList,
+ info->bulletType, aCancel, aHandled);
+ case EditAction::indent:
+ return WillIndent(aSelection, aCancel, aHandled);
+ case EditAction::outdent:
+ return WillOutdent(*aSelection, aCancel, aHandled);
+ case EditAction::setAbsolutePosition:
+ return WillAbsolutePosition(*aSelection, aCancel, aHandled);
+ case EditAction::removeAbsolutePosition:
+ return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
+ case EditAction::align:
+ return WillAlign(*aSelection, *info->alignType, aCancel, aHandled);
+ case EditAction::makeBasicBlock:
+ return WillMakeBasicBlock(*aSelection, *info->blockType, aCancel,
+ aHandled);
+ case EditAction::removeList:
+ return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
+ case EditAction::makeDefListItem:
+ return WillMakeDefListItem(aSelection, info->blockType, info->entireList,
+ aCancel, aHandled);
+ case EditAction::insertElement:
+ WillInsert(*aSelection, aCancel);
+ return NS_OK;
+ case EditAction::decreaseZIndex:
+ return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
+ case EditAction::increaseZIndex:
+ return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
+ default:
+ return TextEditRules::WillDoAction(aSelection, aInfo,
+ aCancel, aHandled);
+ }
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidDoAction(Selection* aSelection,
+ RulesInfo* aInfo,
+ nsresult aResult)
+{
+ TextRulesInfo* info = static_cast<TextRulesInfo*>(aInfo);
+ switch (info->action) {
+ case EditAction::insertBreak:
+ return DidInsertBreak(aSelection, aResult);
+ case EditAction::deleteSelection:
+ return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
+ case EditAction::makeBasicBlock:
+ case EditAction::indent:
+ case EditAction::outdent:
+ case EditAction::align:
+ return DidMakeBasicBlock(aSelection, aInfo, aResult);
+ case EditAction::setAbsolutePosition: {
+ nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return DidAbsolutePosition();
+ }
+ default:
+ // pass through to TextEditRules
+ return TextEditRules::DidDoAction(aSelection, aInfo, aResult);
+ }
+}
+
+nsresult
+HTMLEditRules::GetListState(bool* aMixed,
+ bool* aOL,
+ bool* aUL,
+ bool* aDL)
+{
+ NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
+ *aMixed = false;
+ *aOL = false;
+ *aUL = false;
+ *aDL = false;
+ bool bNonList = false;
+
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
+ TouchContent::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Examine list type for nodes in selection.
+ for (const auto& curNode : arrayOfNodes) {
+ if (!curNode->IsElement()) {
+ bNonList = true;
+ } else if (curNode->IsHTMLElement(nsGkAtoms::ul)) {
+ *aUL = true;
+ } else if (curNode->IsHTMLElement(nsGkAtoms::ol)) {
+ *aOL = true;
+ } else if (curNode->IsHTMLElement(nsGkAtoms::li)) {
+ if (dom::Element* parent = curNode->GetParentElement()) {
+ if (parent->IsHTMLElement(nsGkAtoms::ul)) {
+ *aUL = true;
+ } else if (parent->IsHTMLElement(nsGkAtoms::ol)) {
+ *aOL = true;
+ }
+ }
+ } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::dl,
+ nsGkAtoms::dt,
+ nsGkAtoms::dd)) {
+ *aDL = true;
+ } else {
+ bNonList = true;
+ }
+ }
+
+ // hokey arithmetic with booleans
+ if ((*aUL + *aOL + *aDL + bNonList) > 1) {
+ *aMixed = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::GetListItemState(bool* aMixed,
+ bool* aLI,
+ bool* aDT,
+ bool* aDD)
+{
+ NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
+ *aMixed = false;
+ *aLI = false;
+ *aDT = false;
+ *aDD = false;
+ bool bNonList = false;
+
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
+ TouchContent::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // examine list type for nodes in selection
+ for (const auto& node : arrayOfNodes) {
+ if (!node->IsElement()) {
+ bNonList = true;
+ } else if (node->IsAnyOfHTMLElements(nsGkAtoms::ul,
+ nsGkAtoms::ol,
+ nsGkAtoms::li)) {
+ *aLI = true;
+ } else if (node->IsHTMLElement(nsGkAtoms::dt)) {
+ *aDT = true;
+ } else if (node->IsHTMLElement(nsGkAtoms::dd)) {
+ *aDD = true;
+ } else if (node->IsHTMLElement(nsGkAtoms::dl)) {
+ // need to look inside dl and see which types of items it has
+ bool bDT, bDD;
+ GetDefinitionListItemTypes(node->AsElement(), &bDT, &bDD);
+ *aDT |= bDT;
+ *aDD |= bDD;
+ } else {
+ bNonList = true;
+ }
+ }
+
+ // hokey arithmetic with booleans
+ if (*aDT + *aDD + bNonList > 1) {
+ *aMixed = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::GetAlignment(bool* aMixed,
+ nsIHTMLEditor::EAlignment* aAlign)
+{
+ MOZ_ASSERT(aMixed && aAlign);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // For now, just return first alignment. We'll lie about if it's mixed.
+ // This is for efficiency given that our current ui doesn't care if it's
+ // mixed.
+ // cmanske: NOT TRUE! We would like to pay attention to mixed state in Format
+ // | Align submenu!
+
+ // This routine assumes that alignment is done ONLY via divs
+
+ // Default alignment is left
+ *aMixed = false;
+ *aAlign = nsIHTMLEditor::eLeft;
+
+ // Get selection
+ NS_ENSURE_STATE(htmlEditor->GetSelection());
+ OwningNonNull<Selection> selection = *htmlEditor->GetSelection();
+
+ // Get selection location
+ NS_ENSURE_TRUE(htmlEditor->GetRoot(), NS_ERROR_FAILURE);
+ OwningNonNull<Element> root = *htmlEditor->GetRoot();
+
+ int32_t rootOffset = root->GetParentNode() ?
+ root->GetParentNode()->IndexOf(root) : -1;
+
+ NS_ENSURE_STATE(selection->GetRangeAt(0) &&
+ selection->GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> parent = *selection->GetRangeAt(0)->GetStartParent();
+ int32_t offset = selection->GetRangeAt(0)->StartOffset();
+
+ // Is the selection collapsed?
+ nsCOMPtr<nsINode> nodeToExamine;
+ if (selection->Collapsed() || parent->GetAsText()) {
+ // If selection is collapsed, we want to look at 'parent' and its ancestors
+ // for divs with alignment on them. If we are in a text node, then that is
+ // the node of interest.
+ nodeToExamine = parent;
+ } else if (parent->IsHTMLElement(nsGkAtoms::html) && offset == rootOffset) {
+ // If we have selected the body, let's look at the first editable node
+ nodeToExamine = htmlEditor->GetNextNode(parent, offset, true);
+ } else {
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+ GetPromotedRanges(selection, arrayOfRanges, EditAction::align);
+
+ // Use these ranges to construct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
+ EditAction::align, TouchContent::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nodeToExamine = arrayOfNodes.SafeElementAt(0);
+ }
+
+ NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
+
+ NS_NAMED_LITERAL_STRING(typeAttrName, "align");
+ nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(*nodeToExamine);
+
+ NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
+
+ if (htmlEditor->IsCSSEnabled() &&
+ htmlEditor->mCSSEditUtils->IsCSSEditableProperty(blockParent, nullptr,
+ &typeAttrName)) {
+ // We are in CSS mode and we know how to align this element with CSS
+ nsAutoString value;
+ // Let's get the value(s) of text-align or margin-left/margin-right
+ htmlEditor->mCSSEditUtils->GetCSSEquivalentToHTMLInlineStyleSet(
+ blockParent, nullptr, &typeAttrName, value, CSSEditUtils::eComputed);
+ if (value.EqualsLiteral("center") ||
+ value.EqualsLiteral("-moz-center") ||
+ value.EqualsLiteral("auto auto")) {
+ *aAlign = nsIHTMLEditor::eCenter;
+ return NS_OK;
+ }
+ if (value.EqualsLiteral("right") ||
+ value.EqualsLiteral("-moz-right") ||
+ value.EqualsLiteral("auto 0px")) {
+ *aAlign = nsIHTMLEditor::eRight;
+ return NS_OK;
+ }
+ if (value.EqualsLiteral("justify")) {
+ *aAlign = nsIHTMLEditor::eJustify;
+ return NS_OK;
+ }
+ *aAlign = nsIHTMLEditor::eLeft;
+ return NS_OK;
+ }
+
+ // Check up the ladder for divs with alignment
+ bool isFirstNodeToExamine = true;
+ for (; nodeToExamine; nodeToExamine = nodeToExamine->GetParentNode()) {
+ if (!isFirstNodeToExamine &&
+ nodeToExamine->IsHTMLElement(nsGkAtoms::table)) {
+ // The node to examine is a table and this is not the first node we
+ // examine; let's break here to materialize the 'inline-block' behaviour
+ // of html tables regarding to text alignment
+ return NS_OK;
+ }
+ if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(nodeToExamine))) {
+ // Check for alignment
+ nsAutoString typeAttrVal;
+ nodeToExamine->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align,
+ typeAttrVal);
+ ToLowerCase(typeAttrVal);
+ if (!typeAttrVal.IsEmpty()) {
+ if (typeAttrVal.EqualsLiteral("center")) {
+ *aAlign = nsIHTMLEditor::eCenter;
+ } else if (typeAttrVal.EqualsLiteral("right")) {
+ *aAlign = nsIHTMLEditor::eRight;
+ } else if (typeAttrVal.EqualsLiteral("justify")) {
+ *aAlign = nsIHTMLEditor::eJustify;
+ } else {
+ *aAlign = nsIHTMLEditor::eLeft;
+ }
+ return NS_OK;
+ }
+ }
+ isFirstNodeToExamine = false;
+ }
+ return NS_OK;
+}
+
+static nsIAtom& MarginPropertyAtomForIndent(CSSEditUtils& aHTMLCSSUtils,
+ nsINode& aNode)
+{
+ nsAutoString direction;
+ aHTMLCSSUtils.GetComputedProperty(aNode, *nsGkAtoms::direction, direction);
+ return direction.EqualsLiteral("rtl") ?
+ *nsGkAtoms::marginRight : *nsGkAtoms::marginLeft;
+}
+
+nsresult
+HTMLEditRules::GetIndentState(bool* aCanIndent,
+ bool* aCanOutdent)
+{
+ // XXX Looks like that this is implementation of
+ // nsIHTMLEditor::getIndentState() however nobody calls this method
+ // even with the interface method.
+ NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
+ *aCanIndent = true;
+ *aCanOutdent = false;
+
+ // get selection
+ NS_ENSURE_STATE(mHTMLEditor && mHTMLEditor->GetSelection());
+ OwningNonNull<Selection> selection = *mHTMLEditor->GetSelection();
+
+ // contruct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ nsresult rv = GetNodesFromSelection(*selection, EditAction::indent,
+ arrayOfNodes, TouchContent::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // examine nodes in selection for blockquotes or list elements;
+ // these we can outdent. Note that we return true for canOutdent
+ // if *any* of the selection is outdentable, rather than all of it.
+ NS_ENSURE_STATE(mHTMLEditor);
+ bool useCSS = mHTMLEditor->IsCSSEnabled();
+ for (auto& curNode : Reversed(arrayOfNodes)) {
+ if (HTMLEditUtils::IsNodeThatCanOutdent(GetAsDOMNode(curNode))) {
+ *aCanOutdent = true;
+ break;
+ } else if (useCSS) {
+ // we are in CSS mode, indentation is done using the margin-left (or margin-right) property
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsIAtom& marginProperty =
+ MarginPropertyAtomForIndent(*mHTMLEditor->mCSSEditUtils, curNode);
+ nsAutoString value;
+ // retrieve its specified value
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->mCSSEditUtils->GetSpecifiedProperty(*curNode,
+ marginProperty, value);
+ float f;
+ nsCOMPtr<nsIAtom> unit;
+ // get its number part and its unit
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->mCSSEditUtils->ParseLength(value, &f, getter_AddRefs(unit));
+ // if the number part is strictly positive, outdent is possible
+ if (0 < f) {
+ *aCanOutdent = true;
+ break;
+ }
+ }
+ }
+
+ if (!*aCanOutdent) {
+ // if we haven't found something to outdent yet, also check the parents
+ // of selection endpoints. We might have a blockquote or list item
+ // in the parent hierarchy.
+
+ // gather up info we need for test
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIDOMNode> parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot());
+ NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
+
+ // test start parent hierarchy
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent),
+ &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (parent && parent != root) {
+ if (HTMLEditUtils::IsNodeThatCanOutdent(parent)) {
+ *aCanOutdent = true;
+ break;
+ }
+ tmp = parent;
+ tmp->GetParentNode(getter_AddRefs(parent));
+ }
+
+ // test end parent hierarchy
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent),
+ &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (parent && parent != root) {
+ if (HTMLEditUtils::IsNodeThatCanOutdent(parent)) {
+ *aCanOutdent = true;
+ break;
+ }
+ tmp = parent;
+ tmp->GetParentNode(getter_AddRefs(parent));
+ }
+ }
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::GetParagraphState(bool* aMixed,
+ nsAString& outFormat)
+{
+ // This routine is *heavily* tied to our ui choices in the paragraph
+ // style popup. I can't see a way around that.
+ NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
+ *aMixed = true;
+ outFormat.Truncate(0);
+
+ bool bMixed = false;
+ // using "x" as an uninitialized value, since "" is meaningful
+ nsAutoString formatStr(NS_LITERAL_STRING("x"));
+
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ nsresult rv = GetParagraphFormatNodes(arrayOfNodes, TouchContent::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // post process list. We need to replace any block nodes that are not format
+ // nodes with their content. This is so we only have to look "up" the hierarchy
+ // to find format nodes, instead of both up and down.
+ for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
+ auto& curNode = arrayOfNodes[i];
+ nsAutoString format;
+ // if it is a known format node we have it easy
+ if (IsBlockNode(curNode) && !HTMLEditUtils::IsFormatNode(curNode)) {
+ // arrayOfNodes.RemoveObject(curNode);
+ rv = AppendInnerFormatNodes(arrayOfNodes, curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // we might have an empty node list. if so, find selection parent
+ // and put that on the list
+ if (arrayOfNodes.IsEmpty()) {
+ nsCOMPtr<nsINode> selNode;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ NS_ENSURE_STATE(selection);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode),
+ &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER);
+ arrayOfNodes.AppendElement(*selNode);
+ }
+
+ // remember root node
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIDOMElement> rootElem = do_QueryInterface(mHTMLEditor->GetRoot());
+ NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
+
+ // loop through the nodes in selection and examine their paragraph format
+ for (auto& curNode : Reversed(arrayOfNodes)) {
+ nsAutoString format;
+ // if it is a known format node we have it easy
+ if (HTMLEditUtils::IsFormatNode(curNode)) {
+ GetFormatString(GetAsDOMNode(curNode), format);
+ } else if (IsBlockNode(curNode)) {
+ // this is a div or some other non-format block.
+ // we should ignore it. Its children were appended to this list
+ // by AppendInnerFormatNodes() call above. We will get needed
+ // info when we examine them instead.
+ continue;
+ } else {
+ nsCOMPtr<nsIDOMNode> node, tmp = GetAsDOMNode(curNode);
+ tmp->GetParentNode(getter_AddRefs(node));
+ while (node) {
+ if (node == rootElem) {
+ format.Truncate(0);
+ break;
+ } else if (HTMLEditUtils::IsFormatNode(node)) {
+ GetFormatString(node, format);
+ break;
+ }
+ // else keep looking up
+ tmp = node;
+ tmp->GetParentNode(getter_AddRefs(node));
+ }
+ }
+
+ // if this is the first node, we've found, remember it as the format
+ if (formatStr.EqualsLiteral("x")) {
+ formatStr = format;
+ }
+ // else make sure it matches previously found format
+ else if (format != formatStr) {
+ bMixed = true;
+ break;
+ }
+ }
+
+ *aMixed = bMixed;
+ outFormat = formatStr;
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::AppendInnerFormatNodes(nsTArray<OwningNonNull<nsINode>>& aArray,
+ nsINode* aNode)
+{
+ MOZ_ASSERT(aNode);
+
+ // we only need to place any one inline inside this node onto
+ // the list. They are all the same for purposes of determining
+ // paragraph style. We use foundInline to track this as we are
+ // going through the children in the loop below.
+ bool foundInline = false;
+ for (nsIContent* child = aNode->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ bool isBlock = IsBlockNode(*child);
+ bool isFormat = HTMLEditUtils::IsFormatNode(child);
+ if (isBlock && !isFormat) {
+ // if it's a div, etc., recurse
+ AppendInnerFormatNodes(aArray, child);
+ } else if (isFormat) {
+ aArray.AppendElement(*child);
+ } else if (!foundInline) {
+ // if this is the first inline we've found, use it
+ foundInline = true;
+ aArray.AppendElement(*child);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::GetFormatString(nsIDOMNode* aNode,
+ nsAString& outFormat)
+{
+ NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+
+ if (HTMLEditUtils::IsFormatNode(aNode)) {
+ nsCOMPtr<nsIAtom> atom = EditorBase::GetTag(aNode);
+ atom->ToString(outFormat);
+ } else {
+ outFormat.Truncate();
+ }
+ return NS_OK;
+}
+
+void
+HTMLEditRules::WillInsert(Selection& aSelection,
+ bool* aCancel)
+{
+ MOZ_ASSERT(aCancel);
+
+ TextEditRules::WillInsert(aSelection, aCancel);
+
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Adjust selection to prevent insertion after a moz-BR. This next only
+ // works for collapsed selections right now, because selection is a pain to
+ // work with when not collapsed. (no good way to extend start or end of
+ // selection), so we ignore those types of selections.
+ if (!aSelection.Collapsed()) {
+ return;
+ }
+
+ // If we are after a mozBR in the same block, then move selection to be
+ // before it
+ NS_ENSURE_TRUE_VOID(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> selNode = *aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t selOffset = aSelection.GetRangeAt(0)->StartOffset();
+
+ // Get prior node
+ nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorHTMLNode(selNode,
+ selOffset);
+ if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
+ nsCOMPtr<Element> block1 = htmlEditor->GetBlock(selNode);
+ nsCOMPtr<Element> block2 = htmlEditor->GetBlockNodeParent(priorNode);
+
+ if (block1 && block1 == block2) {
+ // If we are here then the selection is right after a mozBR that is in
+ // the same block as the selection. We need to move the selection start
+ // to be before the mozBR.
+ selNode = priorNode->GetParentNode();
+ selOffset = selNode->IndexOf(priorNode);
+ nsresult rv = aSelection.Collapse(selNode, selOffset);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ if (mDidDeleteSelection &&
+ (mTheAction == EditAction::insertText ||
+ mTheAction == EditAction::insertIMEText ||
+ mTheAction == EditAction::deleteSelection)) {
+ nsresult rv = ReapplyCachedStyles();
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ // For most actions we want to clear the cached styles, but there are
+ // exceptions
+ if (!IsStyleCachePreservingAction(mTheAction)) {
+ ClearCachedStyles();
+ }
+}
+
+nsresult
+HTMLEditRules::WillInsertText(EditAction aAction,
+ Selection* aSelection,
+ bool* aCancel,
+ bool* aHandled,
+ const nsAString* inString,
+ nsAString* outString,
+ int32_t aMaxLength)
+{
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (inString->IsEmpty() && aAction != EditAction::insertIMEText) {
+ // HACK: this is a fix for bug 19395
+ // I can't outlaw all empty insertions
+ // because IME transaction depend on them
+ // There is more work to do to make the
+ // world safe for IME.
+ *aCancel = true;
+ *aHandled = false;
+ return NS_OK;
+ }
+
+ // initialize out param
+ *aCancel = false;
+ *aHandled = true;
+ // 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 (!aSelection->Collapsed()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ WillInsert(*aSelection, aCancel);
+ // initialize out param
+ // we want to ignore result of WillInsert()
+ *aCancel = false;
+
+ // we need to get the doc
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIDocument> doc = mHTMLEditor->GetDocument();
+ NS_ENSURE_STATE(doc);
+
+ // for every property that is set, insert a new inline style node
+ nsresult rv = CreateStyleForInsertText(*aSelection, *doc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the (collapsed) selection location
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(aSelection->GetRangeAt(0));
+ nsCOMPtr<nsINode> selNode = aSelection->GetRangeAt(0)->GetStartParent();
+ int32_t selOffset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_STATE(selNode);
+
+ // dont put text in places that can't have it
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsTextNode(selNode) &&
+ (!mHTMLEditor || !mHTMLEditor->CanContainTag(*selNode,
+ *nsGkAtoms::textTagName))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aAction == EditAction::insertIMEText) {
+ // Right now the WSRunObject code bails on empty strings, but IME needs
+ // the InsertTextImpl() call to still happen since empty strings are meaningful there.
+ NS_ENSURE_STATE(mHTMLEditor);
+ // If there is one or more IME selections, its minimum offset should be
+ // the insertion point.
+ int32_t IMESelectionOffset =
+ mHTMLEditor->GetIMESelectionStartOffsetIn(selNode);
+ if (IMESelectionOffset >= 0) {
+ selOffset = IMESelectionOffset;
+ }
+ if (inString->IsEmpty()) {
+ rv = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode),
+ &selOffset, doc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ WSRunObject wsObj(mHTMLEditor, selNode, selOffset);
+ rv = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ // aAction == kInsertText
+ else {
+ // find where we are
+ nsCOMPtr<nsINode> curNode = selNode;
+ int32_t curOffset = selOffset;
+
+ // is our text going to be PREformatted?
+ // We remember this so that we know how to handle tabs.
+ bool isPRE;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->IsPreformatted(GetAsDOMNode(selNode), &isPRE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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.
+ AutoLockListener lockit(&mListenerEnabled);
+
+ // don't spaz my selection in subtransactions
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
+ nsAutoString tString(*inString);
+ const char16_t *unicodeBuf = tString.get();
+ int32_t pos = 0;
+ NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
+
+ // 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 (unicodeBuf && pos != -1 &&
+ pos < static_cast<int32_t>(inString->Length())) {
+ int32_t oldPos = pos;
+ int32_t subStrLen;
+ pos = tString.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 = tString.Length() - oldPos;
+ pos = tString.Length();
+ }
+
+ nsDependentSubstring subStr(tString, oldPos, subStrLen);
+
+ // is it a return?
+ if (subStr.Equals(newlineStr)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> br =
+ mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset,
+ nsIEditor::eNone);
+ NS_ENSURE_STATE(br);
+ pos++;
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode),
+ &curOffset, doc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ } else {
+ NS_NAMED_LITERAL_STRING(tabStr, "\t");
+ NS_NAMED_LITERAL_STRING(spacesStr, " ");
+ char specialChars[] = {TAB, nsCRT::LF, 0};
+ while (unicodeBuf && pos != -1 &&
+ pos < static_cast<int32_t>(inString->Length())) {
+ int32_t oldPos = pos;
+ int32_t subStrLen;
+ pos = tString.FindCharInSet(specialChars, oldPos);
+
+ if (pos != -1) {
+ subStrLen = pos - oldPos;
+ // if first char is newline, then use just it
+ if (!subStrLen) {
+ subStrLen = 1;
+ }
+ } else {
+ subStrLen = tString.Length() - oldPos;
+ pos = tString.Length();
+ }
+
+ nsDependentSubstring subStr(tString, oldPos, subStrLen);
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObj(mHTMLEditor, curNode, curOffset);
+
+ // is it a tab?
+ if (subStr.Equals(tabStr)) {
+ rv =
+ wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pos++;
+ }
+ // is it a return?
+ else if (subStr.Equals(newlineStr)) {
+ nsCOMPtr<Element> br = wsObj.InsertBreak(address_of(curNode),
+ &curOffset,
+ nsIEditor::eNone);
+ NS_ENSURE_TRUE(br, NS_ERROR_FAILURE);
+ pos++;
+ } else {
+ rv = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ aSelection->SetInterlinePosition(false);
+ if (curNode) aSelection->Collapse(curNode, curOffset);
+ // manually update the doc changed range so that AfterEdit will clean up
+ // the correct portion of the document.
+ if (!mDocChangeRange) {
+ mDocChangeRange = new nsRange(selNode);
+ }
+ rv = mDocChangeRange->SetStart(selNode, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (curNode) {
+ rv = mDocChangeRange->SetEnd(curNode, curOffset);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = mDocChangeRange->SetEnd(selNode, selOffset);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillLoadHTML(Selection* aSelection,
+ bool* aCancel)
+{
+ NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
+
+ *aCancel = false;
+
+ // Delete mBogusNode if it exists. If we really need one,
+ // it will be added during post-processing in AfterEditInner().
+
+ if (mBogusNode) {
+ mTextEditor->DeleteNode(mBogusNode);
+ mBogusNode = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillInsertBreak(Selection& aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aCancel && aHandled);
+ *aCancel = false;
+ *aHandled = false;
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // If the selection isn't collapsed, delete it.
+ if (!aSelection.Collapsed()) {
+ nsresult rv =
+ htmlEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ WillInsert(aSelection, aCancel);
+
+ // Initialize out param. We want to ignore result of WillInsert().
+ *aCancel = false;
+
+ // Split any mailcites in the way. Should we abort this if we encounter
+ // table cell boundaries?
+ if (IsMailEditor()) {
+ nsresult rv = SplitMailCites(&aSelection, aHandled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aHandled) {
+ return NS_OK;
+ }
+ }
+
+ // Smart splitting rules
+ NS_ENSURE_TRUE(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent(),
+ NS_ERROR_FAILURE);
+ OwningNonNull<nsINode> node = *aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+ // Do nothing if the node is read-only
+ if (!htmlEditor->IsModifiableNode(node)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ // Identify the block
+ nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(node);
+ NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
+
+ // If the active editing host is an inline element, or if the active editing
+ // host is the block parent itself, just append a br.
+ nsCOMPtr<Element> host = htmlEditor->GetActiveEditingHost();
+ if (!EditorUtils::IsDescendantOf(blockParent, host)) {
+ nsresult rv = StandardBreakImpl(node, offset, aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aHandled = true;
+ return NS_OK;
+ }
+
+ // 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.)
+ bool isEmpty;
+ IsEmptyBlock(*blockParent, &isEmpty);
+ if (isEmpty) {
+ nsCOMPtr<Element> br = htmlEditor->CreateBR(blockParent,
+ blockParent->Length());
+ NS_ENSURE_STATE(br);
+ }
+
+ nsCOMPtr<Element> listItem = IsInListItem(blockParent);
+ if (listItem && listItem != host) {
+ ReturnInListItem(aSelection, *listItem, node, offset);
+ *aHandled = true;
+ return NS_OK;
+ } else if (HTMLEditUtils::IsHeader(*blockParent)) {
+ // Headers: close (or split) header
+ ReturnInHeader(aSelection, *blockParent, node, offset);
+ *aHandled = true;
+ return NS_OK;
+ } else if (blockParent->IsHTMLElement(nsGkAtoms::p)) {
+ // Paragraphs: special rules to look for <br>s
+ nsresult rv =
+ ReturnInParagraph(&aSelection, GetAsDOMNode(blockParent),
+ GetAsDOMNode(node), offset, aCancel, aHandled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Fall through, we may not have handled it in ReturnInParagraph()
+ }
+
+ // If not already handled then do the standard thing
+ if (!(*aHandled)) {
+ *aHandled = true;
+ return StandardBreakImpl(node, offset, aSelection);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::StandardBreakImpl(nsINode& aNode,
+ int32_t aOffset,
+ Selection& aSelection)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ nsCOMPtr<Element> brNode;
+ bool bAfterBlock = false;
+ bool bBeforeBlock = false;
+ nsCOMPtr<nsINode> node = &aNode;
+
+ if (IsPlaintextEditor()) {
+ brNode = htmlEditor->CreateBR(node, aOffset);
+ NS_ENSURE_STATE(brNode);
+ } else {
+ WSRunObject wsObj(htmlEditor, node, aOffset);
+ int32_t visOffset = 0;
+ WSType wsType;
+ nsCOMPtr<nsINode> visNode;
+ wsObj.PriorVisibleNode(node, aOffset, address_of(visNode),
+ &visOffset, &wsType);
+ if (wsType & WSType::block) {
+ bAfterBlock = true;
+ }
+ wsObj.NextVisibleNode(node, aOffset, address_of(visNode),
+ &visOffset, &wsType);
+ if (wsType & WSType::block) {
+ bBeforeBlock = true;
+ }
+ nsCOMPtr<nsIDOMNode> linkDOMNode;
+ if (htmlEditor->IsInLink(GetAsDOMNode(node), address_of(linkDOMNode))) {
+ // Split the link
+ nsCOMPtr<Element> linkNode = do_QueryInterface(linkDOMNode);
+ NS_ENSURE_STATE(linkNode || !linkDOMNode);
+ nsCOMPtr<nsINode> linkParent = linkNode->GetParentNode();
+ aOffset = htmlEditor->SplitNodeDeep(*linkNode, *node->AsContent(),
+ aOffset,
+ HTMLEditor::EmptyContainers::no);
+ NS_ENSURE_STATE(aOffset != -1);
+ node = linkParent;
+ }
+ brNode = wsObj.InsertBreak(address_of(node), &aOffset, nsIEditor::eNone);
+ NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
+ }
+ node = brNode->GetParentNode();
+ NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
+ int32_t offset = node->IndexOf(brNode);
+ if (bAfterBlock && bBeforeBlock) {
+ // 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.
+ aSelection.SetInterlinePosition(true);
+ nsresult rv = aSelection.Collapse(node, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ WSRunObject wsObj(htmlEditor, node, offset + 1);
+ nsCOMPtr<nsINode> secondBR;
+ int32_t visOffset = 0;
+ WSType wsType;
+ wsObj.NextVisibleNode(node, offset + 1, address_of(secondBR),
+ &visOffset, &wsType);
+ if (wsType == WSType::br) {
+ // 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.
+ nsCOMPtr<nsINode> brParent = secondBR->GetParentNode();
+ int32_t brOffset = brParent ? brParent->IndexOf(secondBR) : -1;
+ if (brParent != node || brOffset != offset + 1) {
+ nsresult rv =
+ htmlEditor->MoveNode(secondBR->AsContent(), node, offset + 1);
+ NS_ENSURE_SUCCESS(rv, 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.
+ nsCOMPtr<nsIContent> siblingNode = brNode->GetNextSibling();
+ if (siblingNode && IsBlockNode(*siblingNode)) {
+ aSelection.SetInterlinePosition(false);
+ } else {
+ aSelection.SetInterlinePosition(true);
+ }
+ nsresult rv = aSelection.Collapse(node, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::DidInsertBreak(Selection* aSelection,
+ nsresult aResult)
+{
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::SplitMailCites(Selection* aSelection,
+ bool* aHandled)
+{
+ NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIContent> leftCite, rightCite;
+ nsCOMPtr<nsINode> selNode;
+ nsCOMPtr<Element> citeNode;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode),
+ &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ citeNode = GetTopEnclosingMailCite(*selNode);
+ if (citeNode) {
+ // 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.
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObj(mHTMLEditor, selNode, selOffset);
+ nsCOMPtr<nsINode> visNode;
+ int32_t visOffset=0;
+ WSType wsType;
+ wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode),
+ &visOffset, &wsType);
+ if (wsType == WSType::br) {
+ // ok, we are just before a break. is it inside the mailquote?
+ if (visNode != citeNode && citeNode->Contains(visNode)) {
+ // it is. so lets reset our selection to be just after it.
+ NS_ENSURE_STATE(mHTMLEditor);
+ selNode = mHTMLEditor->GetNodeLocation(visNode, &selOffset);
+ ++selOffset;
+ }
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(selNode->IsContent());
+ int32_t newOffset = mHTMLEditor->SplitNodeDeep(*citeNode,
+ *selNode->AsContent(), selOffset, HTMLEditor::EmptyContainers::no,
+ getter_AddRefs(leftCite), getter_AddRefs(rightCite));
+ NS_ENSURE_STATE(newOffset != -1);
+
+ // Add an invisible <br> to the end of the left part if it was a <span> of
+ // style="display: block". This is important, since when serialising the
+ // cite to plain text, the span which caused the visual break is discarded.
+ // So the added <br> will guarantee that the serialiser will insert a
+ // break where the user saw one.
+ if (leftCite &&
+ leftCite->IsHTMLElement(nsGkAtoms::span) &&
+ leftCite->GetPrimaryFrame()->IsFrameOfType(nsIFrame::eBlockFrame)) {
+ nsCOMPtr<nsINode> lastChild = leftCite->GetLastChild();
+ if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
+ // We ignore the result here.
+ nsCOMPtr<Element> invisBR =
+ mHTMLEditor->CreateBR(leftCite, leftCite->Length());
+ }
+ }
+
+ selNode = citeNode->GetParentNode();
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> brNode = mHTMLEditor->CreateBR(selNode, newOffset);
+ NS_ENSURE_STATE(brNode);
+
+ // want selection before the break, and on same line
+ aSelection->SetInterlinePosition(true);
+ rv = aSelection->Collapse(selNode, newOffset);
+ NS_ENSURE_SUCCESS(rv, 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 (IsInlineNode(*citeNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObj(mHTMLEditor, selNode, newOffset);
+ nsCOMPtr<nsINode> visNode;
+ int32_t visOffset=0;
+ WSType wsType;
+ wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode),
+ &visOffset, &wsType);
+ if (wsType == WSType::normalWS || wsType == WSType::text ||
+ wsType == WSType::special) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1);
+ wsObjAfterBR.NextVisibleNode(selNode, newOffset + 1,
+ address_of(visNode), &visOffset, &wsType);
+ if (wsType == WSType::normalWS || wsType == WSType::text ||
+ wsType == WSType::special ||
+ // In case we're at the very end.
+ wsType == WSType::thisBlock) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ brNode = mHTMLEditor->CreateBR(selNode, newOffset);
+ NS_ENSURE_STATE(brNode);
+ }
+ }
+ }
+
+ // delete any empty cites
+ bool bEmptyCite = false;
+ if (leftCite) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (bEmptyCite) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(leftCite);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ if (rightCite) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (bEmptyCite) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(rightCite);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ *aHandled = true;
+ }
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::WillDeleteSelection(Selection* aSelection,
+ nsIEditor::EDirection aAction,
+ nsIEditor::EStripWrappers aStripWrappers,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
+ aStripWrappers == nsIEditor::eNoStrip);
+
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ // Initialize out params
+ *aCancel = false;
+ *aHandled = false;
+
+ // Remember that we did a selection deletion. Used by CreateStyleForInsertText()
+ mDidDeleteSelection = true;
+
+ // If there is only bogus content, cancel the operation
+ if (mBogusNode) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ // First check for table selection mode. If so, hand off to table editor.
+ nsCOMPtr<nsIDOMElement> cell;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
+ if (NS_SUCCEEDED(rv) && cell) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteTableCellContents();
+ *aHandled = true;
+ return rv;
+ }
+ cell = nullptr;
+
+ // origCollapsed is used later to determine whether we should join blocks. We
+ // don't really care about bCollapsed because it will be modified by
+ // ExtendSelectionForDelete later. JoinBlocks should happen if the original
+ // selection is collapsed and the cursor is at the end of a block element, in
+ // which case ExtendSelectionForDelete would always make the selection not
+ // collapsed.
+ bool bCollapsed = aSelection->Collapsed();
+ bool join = false;
+ bool origCollapsed = bCollapsed;
+
+ nsCOMPtr<nsINode> selNode;
+ int32_t selOffset;
+
+ NS_ENSURE_STATE(aSelection->GetRangeAt(0));
+ nsCOMPtr<nsINode> startNode = aSelection->GetRangeAt(0)->GetStartParent();
+ int32_t startOffset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
+
+ if (bCollapsed) {
+ // If we are inside an empty block, delete it.
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> host = mHTMLEditor->GetActiveEditingHost();
+ NS_ENSURE_TRUE(host, NS_ERROR_FAILURE);
+ rv = CheckForEmptyBlock(startNode, host, aSelection, aAction, aHandled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aHandled) {
+ return NS_OK;
+ }
+
+ // Test for distance between caret and text that will be deleted
+ rv = CheckBidiLevelForDeletion(aSelection, GetAsDOMNode(startNode),
+ startOffset, aAction, aCancel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aCancel) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We should delete nothing.
+ if (aAction == nsIEditor::eNone) {
+ return NS_OK;
+ }
+
+ // ExtendSelectionForDelete() may have changed the selection, update it
+ NS_ENSURE_STATE(aSelection->GetRangeAt(0));
+ startNode = aSelection->GetRangeAt(0)->GetStartParent();
+ startOffset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
+
+ bCollapsed = aSelection->Collapsed();
+ }
+
+ if (bCollapsed) {
+ // What's in the direction we are deleting?
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObj(mHTMLEditor, startNode, startOffset);
+ nsCOMPtr<nsINode> visNode;
+ int32_t visOffset;
+ WSType wsType;
+
+ // Find next visible node
+ if (aAction == nsIEditor::eNext) {
+ wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode),
+ &visOffset, &wsType);
+ } else {
+ wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode),
+ &visOffset, &wsType);
+ }
+
+ if (!visNode) {
+ // Can't find anything to delete!
+ *aCancel = true;
+ // XXX This is the result of mHTMLEditor->GetFirstSelectedCell().
+ // The value could be both an error and NS_OK.
+ return rv;
+ }
+
+ if (wsType == WSType::normalWS) {
+ // We found some visible ws to delete. Let ws code handle it.
+ *aHandled = true;
+ if (aAction == nsIEditor::eNext) {
+ rv = wsObj.DeleteWSForward();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = wsObj.DeleteWSBackward();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ return InsertBRIfNeeded(aSelection);
+ }
+
+ if (wsType == WSType::text) {
+ // Found normal text to delete.
+ OwningNonNull<Text> nodeAsText = *visNode->GetAsText();
+ int32_t so = visOffset;
+ int32_t eo = visOffset + 1;
+ if (aAction == nsIEditor::ePrevious) {
+ if (!so) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ so--;
+ eo--;
+ // Bug 1068979: delete both codepoints if surrogate pair
+ if (so > 0) {
+ const nsTextFragment *text = nodeAsText->GetText();
+ if (NS_IS_LOW_SURROGATE(text->CharAt(so)) &&
+ NS_IS_HIGH_SURROGATE(text->CharAt(so - 1))) {
+ so--;
+ }
+ }
+ } else {
+ RefPtr<nsRange> range = aSelection->GetRangeAt(0);
+ NS_ENSURE_STATE(range);
+
+ NS_ASSERTION(range->GetStartParent() == visNode,
+ "selection start not in visNode");
+ NS_ASSERTION(range->GetEndParent() == visNode,
+ "selection end not in visNode");
+
+ so = range->StartOffset();
+ eo = range->EndOffset();
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = WSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode),
+ &so, address_of(visNode), &eo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ *aHandled = true;
+ rv = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo),
+ DeprecatedAbs(eo - so));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX When Backspace key is pressed, Chromium removes following empty
+ // text nodes when removing the last character of the non-empty text
+ // node. However, Edge never removes empty text nodes even if
+ // selection is in the following empty text node(s). For now, we
+ // should keep our traditional behavior same as Edge for backward
+ // compatibility.
+ // XXX When Delete key is pressed, Edge removes all preceding empty
+ // text nodes when removing the first character of the non-empty
+ // text node. Chromium removes only selected empty text node and
+ // following empty text nodes and the first character of the
+ // non-empty text node. For now, we should keep our traditional
+ // behavior same as Chromium for backward compatibility.
+
+ DeleteNodeIfCollapsedText(nodeAsText);
+
+ rv = InsertBRIfNeeded(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remember that we did a ranged delete for the benefit of
+ // AfterEditInner().
+ mDidRangedDelete = true;
+
+ return NS_OK;
+ }
+
+ if (wsType == WSType::special || wsType == WSType::br ||
+ visNode->IsHTMLElement(nsGkAtoms::hr)) {
+ // Short circuit for invisible breaks. delete them and recurse.
+ if (visNode->IsHTMLElement(nsGkAtoms::br) &&
+ (!mHTMLEditor || !mHTMLEditor->IsVisBreak(visNode))) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(visNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return WillDeleteSelection(aSelection, aAction, aStripWrappers,
+ aCancel, aHandled);
+ }
+
+ // Special handling for backspace when positioned after <hr>
+ if (aAction == nsIEditor::ePrevious &&
+ visNode->IsHTMLElement(nsGkAtoms::hr)) {
+ // Only if the caret is positioned at the end-of-hr-line position, we
+ // want to delete the <hr>.
+ //
+ // In other words, we only want to delete, if our selection position
+ // (indicated by startNode and startOffset) is the position directly
+ // after the <hr>, on the same line as the <hr>.
+ //
+ // To detect this case we check:
+ // startNode == parentOfVisNode
+ // and
+ // startOffset -1 == visNodeOffsetToVisNodeParent
+ // and
+ // interline position is false (left)
+ //
+ // In any other case we set the position to startnode -1 and
+ // interlineposition to false, only moving the caret to the
+ // end-of-hr-line position.
+ bool moveOnly = true;
+
+ selNode = visNode->GetParentNode();
+ selOffset = selNode ? selNode->IndexOf(visNode) : -1;
+
+ bool interLineIsRight;
+ rv = aSelection->GetInterlinePosition(&interLineIsRight);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (startNode == selNode && startOffset - 1 == selOffset &&
+ !interLineIsRight) {
+ moveOnly = false;
+ }
+
+ if (moveOnly) {
+ // Go to the position after the <hr>, but to the end of the <hr> line
+ // by setting the interline position to left.
+ ++selOffset;
+ aSelection->Collapse(selNode, selOffset);
+ aSelection->SetInterlinePosition(false);
+ mDidExplicitlySetInterline = true;
+ *aHandled = true;
+
+ // There is one exception to the move only case. If the <hr> is
+ // followed by a <br> we want to delete the <br>.
+
+ WSType otherWSType;
+ nsCOMPtr<nsINode> otherNode;
+ int32_t otherOffset;
+
+ wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
+ &otherOffset, &otherWSType);
+
+ if (otherWSType == WSType::br) {
+ // Delete the <br>
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIContent> otherContent(do_QueryInterface(otherNode));
+ rv = WSRunObject::PrepareToDeleteNode(mHTMLEditor, otherContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(otherNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+ // Else continue with normal delete code
+ }
+
+ // Found break or image, or hr.
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(visNode->IsContent());
+ rv = WSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode->AsContent());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Remember sibling to visnode, if any
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIContent> sibling = mHTMLEditor->GetPriorHTMLSibling(visNode);
+ // Delete the node, and join like nodes if appropriate
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(visNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // We did something, so let's say so.
+ *aHandled = true;
+ // Is there a prior node and are they siblings?
+ nsCOMPtr<nsINode> stepbrother;
+ if (sibling) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ stepbrother = mHTMLEditor->GetNextHTMLSibling(sibling);
+ }
+ // Are they both text nodes? If so, join them!
+ if (startNode == stepbrother && startNode->GetAsText() &&
+ sibling->GetAsText()) {
+ EditorDOMPoint pt = JoinNodesSmart(*sibling, *startNode->AsContent());
+ NS_ENSURE_STATE(pt.node);
+ // Fix up selection
+ rv = aSelection->Collapse(pt.node, pt.offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = InsertBRIfNeeded(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ if (wsType == WSType::otherBlock) {
+ // Make sure it's not a table element. If so, cancel the operation
+ // (translation: users cannot backspace or delete across table cells)
+ if (HTMLEditUtils::IsTableElement(visNode)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ // Next to a block. See if we are between a block and a br. If so, we
+ // really want to delete the br. Else join content at selection to the
+ // block.
+ bool bDeletedBR = false;
+ WSType otherWSType;
+ nsCOMPtr<nsINode> otherNode;
+ int32_t otherOffset;
+
+ // Find node in other direction
+ if (aAction == nsIEditor::eNext) {
+ wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode),
+ &otherOffset, &otherWSType);
+ } else {
+ wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
+ &otherOffset, &otherWSType);
+ }
+
+ // First find the adjacent node in the block
+ nsCOMPtr<nsIContent> leafNode;
+ nsCOMPtr<nsINode> leftNode, rightNode;
+ if (aAction == nsIEditor::ePrevious) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ leafNode = mHTMLEditor->GetLastEditableLeaf(*visNode);
+ leftNode = leafNode;
+ rightNode = startNode;
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ leafNode = mHTMLEditor->GetFirstEditableLeaf(*visNode);
+ leftNode = startNode;
+ rightNode = leafNode;
+ }
+
+ if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(otherNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // XXX Only in this case, setting "handled" to true only when it
+ // succeeds?
+ *aHandled = true;
+ bDeletedBR = true;
+ }
+
+ // Don't cross table boundaries
+ if (leftNode && rightNode &&
+ InDifferentTableElements(leftNode, rightNode)) {
+ return NS_OK;
+ }
+
+ if (bDeletedBR) {
+ // Put selection at edge of block and we are done.
+ NS_ENSURE_STATE(leafNode);
+ EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
+ NS_ENSURE_STATE(newSel.node);
+ aSelection->Collapse(newSel.node, newSel.offset);
+ return NS_OK;
+ }
+
+ // Else we are joining content to block
+
+ nsCOMPtr<nsINode> selPointNode = startNode;
+ int32_t selPointOffset = startOffset;
+ {
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
+ address_of(selPointNode), &selPointOffset);
+ NS_ENSURE_STATE(leftNode && leftNode->IsContent() &&
+ rightNode && rightNode->IsContent());
+ *aHandled = true;
+ rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
+ aCancel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ aSelection->Collapse(selPointNode, selPointOffset);
+ return NS_OK;
+ }
+
+ if (wsType == WSType::thisBlock) {
+ // At edge of our block. Look beside it and see if we can join to an
+ // adjacent block
+
+ // Make sure it's not a table element. If so, cancel the operation
+ // (translation: users cannot backspace or delete across table cells)
+ if (HTMLEditUtils::IsTableElement(visNode)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ // First find the relevant nodes
+ nsCOMPtr<nsINode> leftNode, rightNode;
+ if (aAction == nsIEditor::ePrevious) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ leftNode = mHTMLEditor->GetPriorHTMLNode(visNode);
+ rightNode = startNode;
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rightNode = mHTMLEditor->GetNextHTMLNode(visNode);
+ leftNode = startNode;
+ }
+
+ // Nothing to join
+ if (!leftNode || !rightNode) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ // Don't cross table boundaries -- cancel it
+ if (InDifferentTableElements(leftNode, rightNode)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> selPointNode = startNode;
+ int32_t selPointOffset = startOffset;
+ {
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
+ address_of(selPointNode), &selPointOffset);
+ NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent());
+ *aHandled = true;
+ rv = JoinBlocks(*leftNode->AsContent(), *rightNode->AsContent(),
+ aCancel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ aSelection->Collapse(selPointNode, selPointOffset);
+ return NS_OK;
+ }
+ }
+
+
+ // Else we have a non-collapsed selection. First adjust the selection.
+ rv = ExpandSelectionForDeletion(*aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remember that we did a ranged delete for the benefit of AfterEditInner().
+ mDidRangedDelete = true;
+
+ // Refresh start and end points
+ NS_ENSURE_STATE(aSelection->GetRangeAt(0));
+ startNode = aSelection->GetRangeAt(0)->GetStartParent();
+ startOffset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
+ nsCOMPtr<nsINode> endNode = aSelection->GetRangeAt(0)->GetEndParent();
+ int32_t endOffset = aSelection->GetRangeAt(0)->EndOffset();
+ NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
+
+ // Figure out if the endpoints are in nodes that can be merged. Adjust
+ // surrounding whitespace in preparation to delete selection.
+ if (!IsPlaintextEditor()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
+ rv = WSRunObject::PrepareToDeleteRange(mHTMLEditor,
+ address_of(startNode), &startOffset,
+ address_of(endNode), &endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ {
+ // Track location of where we are deleting
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
+ address_of(startNode), &startOffset);
+ AutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
+ address_of(endNode), &endOffset);
+ // We are handling all ranged deletions directly now.
+ *aHandled = true;
+
+ if (endNode == startNode) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Figure out mailcite ancestors
+ nsCOMPtr<Element> startCiteNode = GetTopEnclosingMailCite(*startNode);
+ nsCOMPtr<Element> endCiteNode = GetTopEnclosingMailCite(*endNode);
+
+ // If we only have a mailcite at one of the two endpoints, set the
+ // directionality of the deletion so that the selection will end up
+ // outside the mailcite.
+ if (startCiteNode && !endCiteNode) {
+ aAction = nsIEditor::eNext;
+ } else if (!startCiteNode && endCiteNode) {
+ aAction = nsIEditor::ePrevious;
+ }
+
+ // Figure out block parents
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> leftParent = mHTMLEditor->GetBlock(*startNode);
+ nsCOMPtr<Element> rightParent = mHTMLEditor->GetBlock(*endNode);
+
+ // Are endpoint block parents the same? Use default deletion
+ if (leftParent && leftParent == rightParent) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
+ } else {
+ // Deleting across blocks. Are the blocks of same type?
+ NS_ENSURE_STATE(leftParent && rightParent);
+
+ // Are the blocks siblings?
+ nsCOMPtr<nsINode> leftBlockParent = leftParent->GetParentNode();
+ nsCOMPtr<nsINode> rightBlockParent = rightParent->GetParentNode();
+
+ // MOOSE: this could conceivably screw up a table.. fix me.
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (leftBlockParent == rightBlockParent &&
+ mHTMLEditor->NodesSameType(GetAsDOMNode(leftParent),
+ GetAsDOMNode(rightParent)) &&
+ // XXX What's special about these three types of block?
+ (leftParent->IsHTMLElement(nsGkAtoms::p) ||
+ HTMLEditUtils::IsListItem(leftParent) ||
+ HTMLEditUtils::IsHeader(*leftParent))) {
+ // First delete the selection
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Join blocks
+ NS_ENSURE_STATE(mHTMLEditor);
+ EditorDOMPoint pt =
+ mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent);
+ NS_ENSURE_STATE(pt.node);
+ // Fix up selection
+ rv = aSelection->Collapse(pt.node, pt.offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // Else blocks not same type, or not siblings. Delete everything
+ // except table elements.
+ join = true;
+
+ uint32_t rangeCount = aSelection->RangeCount();
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ OwningNonNull<nsRange> range = *aSelection->GetRangeAt(rangeIdx);
+
+ // Build a list of nodes in the range
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ TrivialFunctor functor;
+ DOMSubtreeIterator iter;
+ nsresult rv = iter.Init(*range);
+ NS_ENSURE_SUCCESS(rv, rv);
+ iter.AppendList(functor, arrayOfNodes);
+
+ // Now that we have the list, delete non-table elements
+ int32_t listCount = arrayOfNodes.Length();
+ for (int32_t j = 0; j < listCount; j++) {
+ nsCOMPtr<nsINode> somenode = do_QueryInterface(arrayOfNodes[0]);
+ NS_ENSURE_STATE(somenode);
+ DeleteNonTableElements(somenode);
+ arrayOfNodes.RemoveElementAt(0);
+ // If something visible is deleted, no need to join. Visible means
+ // all nodes except non-visible textnodes and breaks.
+ if (join && origCollapsed) {
+ if (!somenode->IsContent()) {
+ join = false;
+ continue;
+ }
+ nsCOMPtr<nsIContent> content = somenode->AsContent();
+ if (content->NodeType() == nsIDOMNode::TEXT_NODE) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->IsVisTextNode(content, &join, true);
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ join = content->IsHTMLElement(nsGkAtoms::br) &&
+ !mHTMLEditor->IsVisBreak(somenode);
+ }
+ }
+ }
+ }
+
+ // Check endpoints for possible text deletion. We can assume that if
+ // text node is found, we can delete to end or to begining as
+ // appropriate, since the case where both sel endpoints in same text
+ // node was already handled (we wouldn't be here)
+ if (startNode->GetAsText() &&
+ startNode->Length() > static_cast<uint32_t>(startOffset)) {
+ // Delete to last character
+ OwningNonNull<nsGenericDOMDataNode> dataNode =
+ *static_cast<nsGenericDOMDataNode*>(startNode.get());
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteText(dataNode, startOffset,
+ startNode->Length() - startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (endNode->GetAsText() && endOffset) {
+ // Delete to first character
+ NS_ENSURE_STATE(mHTMLEditor);
+ OwningNonNull<nsGenericDOMDataNode> dataNode =
+ *static_cast<nsGenericDOMDataNode*>(endNode.get());
+ rv = mHTMLEditor->DeleteText(dataNode, 0, endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (join) {
+ rv = JoinBlocks(*leftParent, *rightParent, aCancel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ // We might have left only collapsed whitespace in the start/end nodes
+ {
+ AutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
+ address_of(startNode), &startOffset);
+ AutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
+ address_of(endNode), &endOffset);
+
+ DeleteNodeIfCollapsedText(*startNode);
+ DeleteNodeIfCollapsedText(*endNode);
+ }
+
+ // If we're joining blocks: if deleting forward the selection should be
+ // collapsed to the end of the selection, if deleting backward the selection
+ // should be collapsed to the beginning of the selection. But if we're not
+ // joining then the selection should collapse to the beginning of the
+ // selection if we'redeleting forward, because the end of the selection will
+ // still be in the next block. And same thing for deleting backwards
+ // (selection should collapse to the end, because the beginning will still be
+ // in the first block). See Bug 507936
+ if (aAction == (join ? nsIEditor::eNext : nsIEditor::ePrevious)) {
+ rv = aSelection->Collapse(endNode, endOffset);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = aSelection->Collapse(startNode, startOffset);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * If aNode is a text node that contains only collapsed whitespace, delete it.
+ * It doesn't serve any useful purpose, and we don't want it to confuse code
+ * that doesn't correctly skip over it.
+ *
+ * If deleting the node fails (like if it's not editable), the caller should
+ * proceed as usual, so don't return any errors.
+ */
+void
+HTMLEditRules::DeleteNodeIfCollapsedText(nsINode& aNode)
+{
+ if (!aNode.GetAsText()) {
+ return;
+ }
+ bool empty;
+ nsresult rv = mHTMLEditor->IsVisTextNode(aNode.AsContent(), &empty, false);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (empty) {
+ mHTMLEditor->DeleteNode(&aNode);
+ }
+}
+
+
+/**
+ * InsertBRIfNeeded() determines if a br is needed for current selection to not
+ * be spastic. If so, it inserts one. Callers responsibility to only call
+ * with collapsed selection.
+ *
+ * @param aSelection The collapsed selection.
+ */
+nsresult
+HTMLEditRules::InsertBRIfNeeded(Selection* aSelection)
+{
+ NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
+
+ // get selection
+ nsCOMPtr<nsINode> node;
+ int32_t offset;
+ nsresult rv =
+ mTextEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(node), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ // inline elements don't need any br
+ if (!IsBlockNode(*node)) {
+ return NS_OK;
+ }
+
+ // examine selection
+ NS_ENSURE_STATE(mHTMLEditor);
+ WSRunObject wsObj(mHTMLEditor, node, offset);
+ if (((wsObj.mStartReason & WSType::block) ||
+ (wsObj.mStartReason & WSType::br)) &&
+ (wsObj.mEndReason & WSType::block)) {
+ // if we are tucked between block boundaries then insert a br
+ // first check that we are allowed to
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (mHTMLEditor->CanContainTag(*node, *nsGkAtoms::br)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> br =
+ mHTMLEditor->CreateBR(node, offset, nsIEditor::ePrevious);
+ return br ? NS_OK : NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * GetGoodSelPointForNode() finds where at a node you would want to set the
+ * selection if you were trying to have a caret next to it. Always returns a
+ * valid value (unless mHTMLEditor has gone away).
+ *
+ * @param aNode The node
+ * @param aAction Which edge to find:
+ * eNext/eNextWord/eToEndOfLine indicates beginning,
+ * ePrevious/PreviousWord/eToBeginningOfLine ending.
+ */
+EditorDOMPoint
+HTMLEditRules::GetGoodSelPointForNode(nsINode& aNode,
+ nsIEditor::EDirection aAction)
+{
+ MOZ_ASSERT(aAction == nsIEditor::eNext ||
+ aAction == nsIEditor::eNextWord ||
+ aAction == nsIEditor::ePrevious ||
+ aAction == nsIEditor::ePreviousWord ||
+ aAction == nsIEditor::eToBeginningOfLine ||
+ aAction == nsIEditor::eToEndOfLine);
+
+ bool isPreviousAction = (aAction == nsIEditor::ePrevious ||
+ aAction == nsIEditor::ePreviousWord ||
+ aAction == nsIEditor::eToBeginningOfLine);
+
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ if (aNode.GetAsText() || mHTMLEditor->IsContainer(&aNode) ||
+ NS_WARN_IF(!aNode.GetParentNode())) {
+ return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
+ }
+
+ EditorDOMPoint ret;
+ ret.node = aNode.GetParentNode();
+ ret.offset = ret.node ? ret.node->IndexOf(&aNode) : -1;
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ if ((!aNode.IsHTMLElement(nsGkAtoms::br) ||
+ mHTMLEditor->IsVisBreak(&aNode)) && isPreviousAction) {
+ ret.offset++;
+ }
+ return ret;
+}
+
+
+/**
+ * This method is used to join two block elements. The right element is always
+ * joined to the left element. If the elements are the same type and not
+ * nested within each other, JoinNodesSmart is called (example, joining two
+ * list items together into one). If the elements are not the same type, or
+ * one is a descendant of the other, we instead destroy the right block placing
+ * its children into leftblock. DTD containment rules are followed throughout.
+ */
+nsresult
+HTMLEditRules::JoinBlocks(nsIContent& aLeftNode,
+ nsIContent& aRightNode,
+ bool* aCanceled)
+{
+ MOZ_ASSERT(aCanceled);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ nsCOMPtr<Element> leftBlock = htmlEditor->GetBlock(aLeftNode);
+ nsCOMPtr<Element> rightBlock = htmlEditor->GetBlock(aRightNode);
+
+ // Sanity checks
+ NS_ENSURE_TRUE(leftBlock && rightBlock, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_STATE(leftBlock != rightBlock);
+
+ if (HTMLEditUtils::IsTableElement(leftBlock) ||
+ HTMLEditUtils::IsTableElement(rightBlock)) {
+ // Do not try to merge table elements
+ *aCanceled = true;
+ return NS_OK;
+ }
+
+ // Make sure we don't try to move things into HR's, which look like blocks
+ // but aren't containers
+ if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) {
+ leftBlock = htmlEditor->GetBlockNodeParent(leftBlock);
+ }
+ if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) {
+ rightBlock = htmlEditor->GetBlockNodeParent(rightBlock);
+ }
+ NS_ENSURE_STATE(leftBlock && rightBlock);
+
+ // Bail if both blocks the same
+ if (leftBlock == rightBlock) {
+ *aCanceled = true;
+ return NS_OK;
+ }
+
+ // Joining a list item to its parent is a NOP.
+ if (HTMLEditUtils::IsList(leftBlock) &&
+ HTMLEditUtils::IsListItem(rightBlock) &&
+ rightBlock->GetParentNode() == leftBlock) {
+ return NS_OK;
+ }
+
+ // Special rule here: if we are trying to join list items, and they are in
+ // different lists, join the lists instead.
+ bool mergeLists = false;
+ nsIAtom* existingList = nsGkAtoms::_empty;
+ int32_t offset;
+ nsCOMPtr<Element> leftList, rightList;
+ if (HTMLEditUtils::IsListItem(leftBlock) &&
+ HTMLEditUtils::IsListItem(rightBlock)) {
+ leftList = leftBlock->GetParentElement();
+ rightList = rightBlock->GetParentElement();
+ if (leftList && rightList && leftList != rightList &&
+ !EditorUtils::IsDescendantOf(leftList, rightBlock, &offset) &&
+ !EditorUtils::IsDescendantOf(rightList, leftBlock, &offset)) {
+ // There are some special complications if the lists are descendants of
+ // the other lists' items. Note that it is okay for them to be
+ // descendants of the other lists themselves, which is the usual case for
+ // sublists in our implementation.
+ leftBlock = leftList;
+ rightBlock = rightList;
+ mergeLists = true;
+ existingList = leftList->NodeInfo()->NameAtom();
+ }
+ }
+
+ AutoTransactionsConserveSelection dontSpazMySelection(htmlEditor);
+
+ int32_t rightOffset = 0;
+ int32_t leftOffset = -1;
+
+ // offset below is where you find yourself in rightBlock when you traverse
+ // upwards from leftBlock
+ if (EditorUtils::IsDescendantOf(leftBlock, rightBlock, &rightOffset)) {
+ // Tricky case. Left block is inside right block. Do ws adjustment. This
+ // just destroys non-visible ws at boundaries we will be joining.
+ rightOffset++;
+ nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
+ WSRunObject::kBlockEnd,
+ leftBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ // We can't just track rightBlock because it's an Element.
+ nsCOMPtr<nsINode> trackingRightBlock(rightBlock);
+ AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater,
+ address_of(trackingRightBlock), &rightOffset);
+ rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
+ WSRunObject::kAfterBlock,
+ rightBlock, rightOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (trackingRightBlock->IsElement()) {
+ rightBlock = trackingRightBlock->AsElement();
+ } else {
+ NS_ENSURE_STATE(trackingRightBlock->GetParentElement());
+ rightBlock = trackingRightBlock->GetParentElement();
+ }
+ }
+ // Do br adjustment.
+ nsCOMPtr<Element> brNode =
+ CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
+ if (mergeLists) {
+ // The idea here is to take all children in rightList that are past
+ // offset, and pull them into leftlist.
+ for (nsCOMPtr<nsIContent> child = rightList->GetChildAt(offset);
+ child; child = rightList->GetChildAt(rightOffset)) {
+ rv = htmlEditor->MoveNode(child, leftList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+ }
+ if (brNode) {
+ htmlEditor->DeleteNode(brNode);
+ }
+ // Offset below is where you find yourself in leftBlock when you traverse
+ // upwards from rightBlock
+ } else if (EditorUtils::IsDescendantOf(rightBlock, leftBlock, &leftOffset)) {
+ // Tricky case. Right block is inside left block. Do ws adjustment. This
+ // just destroys non-visible ws at boundaries we will be joining.
+ nsresult rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
+ WSRunObject::kBlockStart,
+ rightBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ // We can't just track leftBlock because it's an Element, so track
+ // something else.
+ nsCOMPtr<nsINode> trackingLeftBlock(leftBlock);
+ AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater,
+ address_of(trackingLeftBlock), &leftOffset);
+ rv = WSRunObject::ScrubBlockBoundary(htmlEditor,
+ WSRunObject::kBeforeBlock,
+ leftBlock, leftOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (trackingLeftBlock->IsElement()) {
+ leftBlock = trackingLeftBlock->AsElement();
+ } else {
+ NS_ENSURE_STATE(trackingLeftBlock->GetParentElement());
+ leftBlock = trackingLeftBlock->GetParentElement();
+ }
+ }
+ // Do br adjustment.
+ nsCOMPtr<Element> brNode =
+ CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, leftOffset);
+ if (mergeLists) {
+ MoveContents(*rightList, *leftList, &leftOffset);
+ } else {
+ // Left block is a parent of right block, and the parent of the previous
+ // visible content. Right block is a child and contains the contents we
+ // want to move.
+
+ int32_t previousContentOffset;
+ nsCOMPtr<nsINode> previousContentParent;
+
+ if (&aLeftNode == leftBlock) {
+ // We are working with valid HTML, aLeftNode is a block node, and is
+ // therefore allowed to contain rightBlock. This is the simple case,
+ // we will simply move the content in rightBlock out of its block.
+ previousContentParent = leftBlock;
+ previousContentOffset = leftOffset;
+ } else {
+ // We try to work as well as possible with HTML that's already invalid.
+ // Although "right block" is a block, and a block must not be contained
+ // in inline elements, reality is that broken documents do exist. The
+ // DIRECT parent of "left NODE" might be an inline element. Previous
+ // versions of this code skipped inline parents until the first block
+ // parent was found (and used "left block" as the destination).
+ // However, in some situations this strategy moves the content to an
+ // unexpected position. (see bug 200416) The new idea is to make the
+ // moving content a sibling, next to the previous visible content.
+
+ previousContentParent = aLeftNode.GetParentNode();
+ previousContentOffset = previousContentParent ?
+ previousContentParent->IndexOf(&aLeftNode) : -1;
+
+ // We want to move our content just after the previous visible node.
+ previousContentOffset++;
+ }
+
+ // Because we don't want the moving content to receive the style of the
+ // previous content, we split the previous content's style.
+
+ nsCOMPtr<Element> editorRoot = htmlEditor->GetEditorRoot();
+ if (!editorRoot || &aLeftNode != editorRoot) {
+ nsCOMPtr<nsIContent> splittedPreviousContent;
+ rv = htmlEditor->SplitStyleAbovePoint(
+ address_of(previousContentParent),
+ &previousContentOffset,
+ nullptr, nullptr, nullptr,
+ getter_AddRefs(splittedPreviousContent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (splittedPreviousContent) {
+ previousContentParent = splittedPreviousContent->GetParentNode();
+ previousContentOffset = previousContentParent ?
+ previousContentParent->IndexOf(splittedPreviousContent) : -1;
+ }
+ }
+
+ NS_ENSURE_TRUE(previousContentParent, NS_ERROR_NULL_POINTER);
+
+ rv = MoveBlock(*previousContentParent->AsElement(), *rightBlock,
+ previousContentOffset, rightOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (brNode) {
+ htmlEditor->DeleteNode(brNode);
+ }
+ } else {
+ // Normal case. Blocks are siblings, or at least close enough. An example
+ // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The
+ // first li and the p are not true siblings, but we still want to join them
+ // if you backspace from li into p.
+
+ // Adjust whitespace at block boundaries
+ nsresult rv =
+ WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Do br adjustment.
+ nsCOMPtr<Element> brNode =
+ CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
+ if (mergeLists || leftBlock->NodeInfo()->NameAtom() ==
+ rightBlock->NodeInfo()->NameAtom()) {
+ // Nodes are same type. merge them.
+ EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock);
+ if (pt.node && mergeLists) {
+ nsCOMPtr<Element> newBlock;
+ ConvertListType(rightBlock, getter_AddRefs(newBlock),
+ existingList, nsGkAtoms::li);
+ }
+ } else {
+ // Nodes are dissimilar types.
+ rv = MoveBlock(*leftBlock, *rightBlock, leftOffset, rightOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (brNode) {
+ rv = htmlEditor->DeleteNode(brNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Moves the content from aRightBlock starting from aRightOffset into
+ * aLeftBlock at aLeftOffset. Note that the "block" might merely be inline
+ * nodes between <br>s, or between blocks, etc. DTD containment rules are
+ * followed throughout.
+ */
+nsresult
+HTMLEditRules::MoveBlock(Element& aLeftBlock,
+ Element& aRightBlock,
+ int32_t aLeftOffset,
+ int32_t aRightOffset)
+{
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
+ nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset),
+ EditAction::makeList, arrayOfNodes,
+ TouchContent::yes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
+ // get the node to act on
+ if (IsBlockNode(arrayOfNodes[i])) {
+ // For block nodes, move their contents only, then delete block.
+ rv = MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock,
+ &aLeftOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(arrayOfNodes[i]);
+ } else {
+ // Otherwise move the content as is, checking against the DTD.
+ rv = MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock,
+ &aLeftOffset);
+ }
+ }
+
+ // XXX We're only checking return value of the last iteration
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+/**
+ * This method is used to move node aNode to (aDestElement, aInOutDestOffset).
+ * DTD containment rules are followed throughout. aInOutDestOffset is updated
+ * to point _after_ inserted content.
+ */
+nsresult
+HTMLEditRules::MoveNodeSmart(nsIContent& aNode,
+ Element& aDestElement,
+ int32_t* aInOutDestOffset)
+{
+ MOZ_ASSERT(aInOutDestOffset);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Check if this node can go into the destination node
+ if (htmlEditor->CanContain(aDestElement, aNode)) {
+ // If it can, move it there
+ nsresult rv =
+ htmlEditor->MoveNode(&aNode, &aDestElement, *aInOutDestOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aInOutDestOffset != -1) {
+ (*aInOutDestOffset)++;
+ }
+ } else {
+ // If it can't, move its children (if any), and then delete it.
+ if (aNode.IsElement()) {
+ nsresult rv =
+ MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsresult rv = htmlEditor->DeleteNode(&aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+/**
+ * Moves the _contents_ of aElement to (aDestElement, aInOutDestOffset). DTD
+ * containment rules are followed throughout. aInOutDestOffset is updated to
+ * point _after_ inserted content.
+ */
+nsresult
+HTMLEditRules::MoveContents(Element& aElement,
+ Element& aDestElement,
+ int32_t* aInOutDestOffset)
+{
+ MOZ_ASSERT(aInOutDestOffset);
+
+ NS_ENSURE_TRUE(&aElement != &aDestElement, NS_ERROR_ILLEGAL_VALUE);
+
+ while (aElement.GetFirstChild()) {
+ nsresult rv = MoveNodeSmart(*aElement.GetFirstChild(), aDestElement,
+ aInOutDestOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::DeleteNonTableElements(nsINode* aNode)
+{
+ MOZ_ASSERT(aNode);
+ if (!HTMLEditUtils::IsTableElementButNotTable(aNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ return mHTMLEditor->DeleteNode(aNode->AsDOMNode());
+ }
+
+ for (int32_t i = aNode->GetChildCount() - 1; i >= 0; --i) {
+ nsresult rv = DeleteNonTableElements(aNode->GetChildAt(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::DidDeleteSelection(Selection* aSelection,
+ nsIEditor::EDirection aDir,
+ nsresult aResult)
+{
+ if (!aSelection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // find where we are
+ nsCOMPtr<nsINode> startNode;
+ int32_t startOffset;
+ nsresult rv = mTextEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(startNode),
+ &startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
+
+ // find any enclosing mailcite
+ nsCOMPtr<Element> citeNode = GetTopEnclosingMailCite(*startNode);
+ if (citeNode) {
+ bool isEmpty = true, seenBR = false;
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->IsEmptyNodeImpl(citeNode, &isEmpty, true, true, false,
+ &seenBR);
+ if (isEmpty) {
+ int32_t offset;
+ nsCOMPtr<nsINode> parent = EditorBase::GetNodeLocation(citeNode, &offset);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(citeNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (parent && seenBR) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> brNode = mHTMLEditor->CreateBR(parent, offset);
+ NS_ENSURE_STATE(brNode);
+ aSelection->Collapse(parent, offset);
+ }
+ }
+ }
+
+ // call through to base class
+ return TextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
+}
+
+nsresult
+HTMLEditRules::WillMakeList(Selection* aSelection,
+ const nsAString* aListType,
+ bool aEntireList,
+ const nsAString* aBulletType,
+ bool* aCancel,
+ bool* aHandled,
+ const nsAString* aItemType)
+{
+ if (!aSelection || !aListType || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ OwningNonNull<nsIAtom> listType = NS_Atomize(*aListType);
+
+ WillInsert(*aSelection, aCancel);
+
+ // initialize out param
+ // we want to ignore result of WillInsert()
+ *aCancel = false;
+ *aHandled = false;
+
+ // deduce what tag to use for list items
+ nsCOMPtr<nsIAtom> itemType;
+ if (aItemType) {
+ itemType = NS_Atomize(*aItemType);
+ NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY);
+ } else if (listType == nsGkAtoms::dl) {
+ itemType = nsGkAtoms::dd;
+ } else {
+ itemType = nsGkAtoms::li;
+ }
+
+ // 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
+
+ *aHandled = true;
+
+ nsresult rv = NormalizeSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetListActionNodes(arrayOfNodes,
+ aEntireList ? EntireList::yes : EntireList::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if all our nodes are <br>s, or empty inlines
+ bool bOnlyBreaks = true;
+ for (auto& curNode : arrayOfNodes) {
+ // if curNode is not a Break or empty inline, we're done
+ if (!TextEditUtils::IsBreak(curNode) &&
+ !IsEmptyInline(curNode)) {
+ bOnlyBreaks = false;
+ break;
+ }
+ }
+
+ // if no nodes, we make empty list. Ditto if the user tried to make a list
+ // of some # of breaks.
+ if (arrayOfNodes.IsEmpty() || bOnlyBreaks) {
+ // if only breaks, delete them
+ if (bOnlyBreaks) {
+ for (auto& node : arrayOfNodes) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // get selection location
+ NS_ENSURE_STATE(aSelection->RangeCount());
+ nsCOMPtr<nsINode> parent = aSelection->GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_STATE(parent);
+
+ // make sure we can put a list here
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->CanContainTag(*parent, listType)) {
+ *aCancel = true;
+ return NS_OK;
+ }
+ rv = SplitAsNeeded(listType, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> theList =
+ mHTMLEditor->CreateNode(listType, parent, offset);
+ NS_ENSURE_STATE(theList);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> theListItem =
+ mHTMLEditor->CreateNode(itemType, theList, 0);
+ NS_ENSURE_STATE(theListItem);
+
+ // remember our new block for postprocessing
+ mNewBlock = theListItem;
+ // put selection in new list item
+ *aHandled = true;
+ rv = aSelection->Collapse(theListItem, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ return 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.
+
+ LookInsideDivBQandList(arrayOfNodes);
+
+ // Ok, now go through all the nodes and put then in the list,
+ // or whatever is approriate. Wohoo!
+
+ uint32_t listCount = arrayOfNodes.Length();
+ nsCOMPtr<nsINode> curParent;
+ nsCOMPtr<Element> curList, prevListItem;
+
+ for (uint32_t i = 0; i < listCount; i++) {
+ // here's where we actually figure out what to do
+ nsCOMPtr<Element> newBlock;
+ NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
+ OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
+ int32_t offset;
+ curParent = EditorBase::GetNodeLocation(curNode, &offset);
+
+ // 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 && InDifferentTableElements(curList, curNode)) {
+ curList = nullptr;
+ }
+
+ // if curNode is a Break, delete it, and quit remembering prev list item
+ if (TextEditUtils::IsBreak(curNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ prevListItem = nullptr;
+ continue;
+ } else if (IsEmptyInline(curNode)) {
+ // if curNode is an empty inline container, delete it
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ if (HTMLEditUtils::IsList(curNode)) {
+ // do we have a curList already?
+ if (curList && !EditorUtils::IsDescendantOf(curNode, curList)) {
+ // move all of our children into curList. cheezy way to do it: move
+ // whole list and then RemoveContainer() on the list. ConvertListType
+ // first: that routine handles converting the list item types, if
+ // needed
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = ConvertListType(curNode->AsElement(), getter_AddRefs(newBlock),
+ listType, itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RemoveBlockContainer(*newBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // replace list with new list type
+ rv = ConvertListType(curNode->AsElement(), getter_AddRefs(newBlock),
+ listType, itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curList = newBlock;
+ }
+ prevListItem = nullptr;
+ continue;
+ }
+
+ if (HTMLEditUtils::IsListItem(curNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!curParent->IsHTMLElement(listType)) {
+ // list item is in wrong type of list. if we don't have a curList,
+ // split the old list and make a new list of correct type.
+ if (!curList || EditorUtils::IsDescendantOf(curNode, curList)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(curParent->IsContent());
+ ErrorResult rv;
+ nsCOMPtr<nsIContent> splitNode =
+ mHTMLEditor->SplitNode(*curParent->AsContent(), offset, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ newBlock = splitNode ? splitNode->AsElement() : nullptr;
+ int32_t offset;
+ nsCOMPtr<nsINode> parent = EditorBase::GetNodeLocation(curParent,
+ &offset);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curList = mHTMLEditor->CreateNode(listType, parent, offset);
+ NS_ENSURE_STATE(curList);
+ }
+ // move list item to new list
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // convert list item type if needed
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!curNode->IsHTMLElement(itemType)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ newBlock = mHTMLEditor->ReplaceContainer(curNode->AsElement(),
+ itemType);
+ NS_ENSURE_STATE(newBlock);
+ }
+ } else {
+ // item is in right type of list. But we might still have to move it.
+ // and we might need to convert list item types.
+ if (!curList) {
+ curList = curParent->AsElement();
+ } else if (curParent != curList) {
+ // move list item to new list
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!curNode->IsHTMLElement(itemType)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ newBlock = mHTMLEditor->ReplaceContainer(curNode->AsElement(),
+ itemType);
+ NS_ENSURE_STATE(newBlock);
+ }
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIDOMElement> curElement = do_QueryInterface(curNode);
+ NS_NAMED_LITERAL_STRING(typestr, "type");
+ if (aBulletType && !aBulletType->IsEmpty()) {
+ rv = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ rv = mHTMLEditor->RemoveAttribute(curElement, typestr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ continue;
+ }
+
+ // if we hit a div clear our prevListItem, insert divs contents
+ // into our node array, and remove the div
+ if (curNode->IsHTMLElement(nsGkAtoms::div)) {
+ prevListItem = nullptr;
+ int32_t j = i + 1;
+ GetInnerContent(*curNode, arrayOfNodes, &j);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RemoveContainer(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ listCount = arrayOfNodes.Length();
+ continue;
+ }
+
+ // need to make a list to put things in if we haven't already,
+ if (!curList) {
+ rv = SplitAsNeeded(listType, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curList = mHTMLEditor->CreateNode(listType, curParent, offset);
+ // remember our new block for postprocessing
+ mNewBlock = curList;
+ // curList is now the correct thing to put curNode in
+ prevListItem = nullptr;
+ }
+
+ // if curNode isn't a list item, we must wrap it in one
+ nsCOMPtr<Element> listItem;
+ if (!HTMLEditUtils::IsListItem(curNode)) {
+ if (IsInlineNode(curNode) && prevListItem) {
+ // this is a continuation of some inline nodes that belong together in
+ // the same list item. use prevListItem
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, prevListItem, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // don't wrap li around a paragraph. instead replace paragraph with li
+ if (curNode->IsHTMLElement(nsGkAtoms::p)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ listItem = mHTMLEditor->ReplaceContainer(curNode->AsElement(),
+ itemType);
+ NS_ENSURE_STATE(listItem);
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ listItem = mHTMLEditor->InsertContainerAbove(curNode, itemType);
+ NS_ENSURE_STATE(listItem);
+ }
+ if (IsInlineNode(curNode)) {
+ prevListItem = listItem;
+ } else {
+ prevListItem = nullptr;
+ }
+ }
+ } else {
+ listItem = curNode->AsElement();
+ }
+
+ if (listItem) {
+ // if we made a new list item, deal with it: tuck the listItem into the
+ // end of the active list
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(listItem, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::WillRemoveList(Selection* aSelection,
+ bool aOrdered,
+ bool* aCancel,
+ bool* aHandled)
+{
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ // initialize out param
+ *aCancel = false;
+ *aHandled = true;
+
+ nsresult rv = NormalizeSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+ GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::makeList);
+
+ // use these ranges to contruct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetListActionNodes(arrayOfNodes, EntireList::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove all non-editable nodes. Leave them be.
+ for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> testNode = arrayOfNodes[i];
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsEditable(testNode)) {
+ arrayOfNodes.RemoveElementAt(i);
+ }
+ }
+
+ // Only act on lists or list items in the array
+ for (auto& curNode : arrayOfNodes) {
+ // here's where we actually figure out what to do
+ if (HTMLEditUtils::IsListItem(curNode)) {
+ // unlist this listitem
+ bool bOutOfList;
+ do {
+ rv = PopListItem(GetAsDOMNode(curNode), &bOutOfList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (!bOutOfList); // keep popping it out until it's not in a list anymore
+ } else if (HTMLEditUtils::IsList(curNode)) {
+ // node is a list, move list items out
+ rv = RemoveListStructure(*curNode->AsElement());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillMakeDefListItem(Selection* aSelection,
+ const nsAString *aItemType,
+ bool aEntireList,
+ bool* aCancel,
+ bool* aHandled)
+{
+ // for now we let WillMakeList handle this
+ NS_NAMED_LITERAL_STRING(listType, "dl");
+ return WillMakeList(aSelection, &listType, aEntireList, nullptr, aCancel, aHandled, aItemType);
+}
+
+nsresult
+HTMLEditRules::WillMakeBasicBlock(Selection& aSelection,
+ const nsAString& aBlockType,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aCancel && aHandled);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ OwningNonNull<nsIAtom> blockType = NS_Atomize(aBlockType);
+
+ WillInsert(aSelection, aCancel);
+ // We want to ignore result of WillInsert()
+ *aCancel = false;
+ *aHandled = false;
+
+ nsresult rv = NormalizeSelection(&aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
+ AutoTransactionsConserveSelection dontSpazMySelection(htmlEditor);
+ *aHandled = true;
+
+ // Contruct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock,
+ arrayOfNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove all non-editable nodes. Leave them be.
+ for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
+ if (!htmlEditor->IsEditable(arrayOfNodes[i])) {
+ arrayOfNodes.RemoveElementAt(i);
+ }
+ }
+
+ // If nothing visible in list, make an empty block
+ if (ListIsEmptyLine(arrayOfNodes)) {
+ // Get selection location
+ NS_ENSURE_STATE(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> parent =
+ *aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+ if (blockType == nsGkAtoms::normal ||
+ blockType == nsGkAtoms::_empty) {
+ // We are removing blocks (going to "body text")
+ NS_ENSURE_TRUE(htmlEditor->GetBlock(parent), NS_ERROR_NULL_POINTER);
+ OwningNonNull<Element> curBlock = *htmlEditor->GetBlock(parent);
+ if (HTMLEditUtils::IsFormatNode(curBlock)) {
+ // 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> brNode =
+ htmlEditor->GetNextHTMLNode(parent, offset);
+ if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
+ rv = htmlEditor->DeleteNode(brNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Do the splits!
+ offset = htmlEditor->SplitNodeDeep(curBlock, *parent->AsContent(),
+ offset,
+ HTMLEditor::EmptyContainers::no);
+ NS_ENSURE_STATE(offset != -1);
+ // Put a br at the split point
+ brNode = htmlEditor->CreateBR(curBlock->GetParentNode(), offset);
+ NS_ENSURE_STATE(brNode);
+ // Put selection at the split point
+ *aHandled = true;
+ rv = aSelection.Collapse(curBlock->GetParentNode(), offset);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Else nothing to do!
+ } else {
+ // We are making a block. Consume a br, if needed.
+ nsCOMPtr<nsIContent> brNode =
+ htmlEditor->GetNextHTMLNode(parent, offset, true);
+ if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
+ rv = htmlEditor->DeleteNode(brNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // We don't need to act on this node any more
+ arrayOfNodes.RemoveElement(brNode);
+ }
+ // Make sure we can put a block here
+ rv = SplitAsNeeded(blockType, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<Element> block =
+ htmlEditor->CreateNode(blockType, parent, offset);
+ NS_ENSURE_STATE(block);
+ // Remember our new block for postprocessing
+ mNewBlock = block;
+ // Delete anything that was in the list of nodes
+ while (!arrayOfNodes.IsEmpty()) {
+ OwningNonNull<nsINode> curNode = arrayOfNodes[0];
+ rv = htmlEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ arrayOfNodes.RemoveElementAt(0);
+ }
+ // Put selection in new block
+ *aHandled = true;
+ rv = aSelection.Collapse(block, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+ // 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) {
+ rv = MakeBlockquote(arrayOfNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (blockType == nsGkAtoms::normal ||
+ blockType == nsGkAtoms::_empty) {
+ rv = RemoveBlockStyle(arrayOfNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = ApplyBlockStyle(arrayOfNodes, blockType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::DidMakeBasicBlock(Selection* aSelection,
+ RulesInfo* aInfo,
+ nsresult aResult)
+{
+ NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
+ // check for empty block. if so, put a moz br in it.
+ if (!aSelection->Collapsed()) {
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(aSelection->GetRangeAt(0) &&
+ aSelection->GetRangeAt(0)->GetStartParent());
+ nsresult rv =
+ InsertMozBRIfNeeded(*aSelection->GetRangeAt(0)->GetStartParent());
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillIndent(Selection* aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (mHTMLEditor->IsCSSEnabled()) {
+ nsresult rv = WillCSSIndent(aSelection, aCancel, aHandled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsresult rv = WillHTMLIndent(aSelection, aCancel, aHandled);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillCSSIndent(Selection* aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ WillInsert(*aSelection, aCancel);
+
+ // initialize out param
+ // we want to ignore result of WillInsert()
+ *aCancel = false;
+ *aHandled = true;
+
+ nsresult rv = NormalizeSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+ nsTArray<OwningNonNull<nsRange>> arrayOfRanges;
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+
+ // short circuit: detect case of collapsed selection inside an <li>.
+ // just sublist that <li>. This prevents bug 97797.
+
+ nsCOMPtr<Element> liNode;
+ if (aSelection->Collapsed()) {
+ nsCOMPtr<nsINode> node;
+ int32_t offset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(node), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<Element> block = mHTMLEditor->GetBlock(*node);
+ if (block && HTMLEditUtils::IsListItem(block)) {
+ liNode = block;
+ }
+ }
+
+ if (liNode) {
+ arrayOfNodes.AppendElement(*liNode);
+ } else {
+ // 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
+ rv = GetNodesFromSelection(*aSelection, EditAction::indent, arrayOfNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // if nothing visible in list, make an empty block
+ if (ListIsEmptyLine(arrayOfNodes)) {
+ // get selection location
+ NS_ENSURE_STATE(aSelection->RangeCount());
+ nsCOMPtr<nsINode> parent = aSelection->GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_STATE(parent);
+
+ // make sure we can put a block here
+ rv = SplitAsNeeded(*nsGkAtoms::div, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> theBlock = mHTMLEditor->CreateNode(nsGkAtoms::div,
+ parent, offset);
+ NS_ENSURE_STATE(theBlock);
+ // remember our new block for postprocessing
+ mNewBlock = theBlock;
+ ChangeIndentation(*theBlock, Change::plus);
+ // delete anything that was in the list of nodes
+ while (!arrayOfNodes.IsEmpty()) {
+ OwningNonNull<nsINode> curNode = arrayOfNodes[0];
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ arrayOfNodes.RemoveElementAt(0);
+ }
+ // put selection in new block
+ *aHandled = true;
+ rv = aSelection->Collapse(theBlock, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ return rv;
+ }
+
+ // Ok, now go through all the nodes and put them in a blockquote,
+ // or whatever is appropriate. Wohoo!
+ nsCOMPtr<nsINode> curParent;
+ nsCOMPtr<Element> curList, curQuote;
+ nsCOMPtr<nsIContent> sibling;
+ int32_t listCount = arrayOfNodes.Length();
+ for (int32_t i = 0; i < listCount; i++) {
+ // here's where we actually figure out what to do
+ NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
+ nsCOMPtr<nsIContent> curNode = arrayOfNodes[i]->AsContent();
+
+ // Ignore all non-editable nodes. Leave them be.
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsEditable(curNode)) {
+ continue;
+ }
+
+ curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // some logic for putting list items into nested lists...
+ if (HTMLEditUtils::IsList(curParent)) {
+ sibling = nullptr;
+
+ // Check for whether we should join a list that follows curNode.
+ // We do this if the next element is a list, and the list is of the
+ // same type (li/ol) as curNode was a part it.
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetNextHTMLSibling(curNode);
+ if (sibling && HTMLEditUtils::IsList(sibling) &&
+ curParent->NodeInfo()->NameAtom() ==
+ sibling->NodeInfo()->NameAtom() &&
+ curParent->NodeInfo()->NamespaceID() ==
+ sibling->NodeInfo()->NamespaceID()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, sibling, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ // Check for whether we should join a list that preceeds curNode.
+ // We do this if the previous element is a list, and the list is of
+ // the same type (li/ol) as curNode was a part of.
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(curNode);
+ if (sibling && HTMLEditUtils::IsList(sibling) &&
+ curParent->NodeInfo()->NameAtom() ==
+ sibling->NodeInfo()->NameAtom() &&
+ curParent->NodeInfo()->NamespaceID() ==
+ sibling->NodeInfo()->NamespaceID()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, sibling, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ sibling = nullptr;
+
+ // check to see if curList is still appropriate. Which it is if
+ // curNode is still right after it in the same list.
+ if (curList) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(curNode);
+ }
+
+ if (!curList || (sibling && sibling != curList)) {
+ // create a new nested list of correct type
+ rv =
+ SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
+ curParent, offset);
+ NS_ENSURE_STATE(curList);
+ // curList is now the correct thing to put curNode in
+ // remember our new block for postprocessing
+ mNewBlock = curList;
+ }
+ // tuck the node into the end of the active list
+ uint32_t listLen = curList->Length();
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curList, listLen);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Not a list item.
+ else {
+ if (curNode && IsBlockNode(*curNode)) {
+ ChangeIndentation(*curNode->AsElement(), Change::plus);
+ curQuote = nullptr;
+ } else {
+ if (!curQuote) {
+ // First, check that our element can contain a div.
+ if (!mTextEditor->CanContainTag(*curParent, *nsGkAtoms::div)) {
+ return NS_OK; // cancelled
+ }
+
+ rv = SplitAsNeeded(*nsGkAtoms::div, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curQuote = mHTMLEditor->CreateNode(nsGkAtoms::div, curParent,
+ offset);
+ NS_ENSURE_STATE(curQuote);
+ ChangeIndentation(*curQuote, Change::plus);
+ // remember our new block for postprocessing
+ mNewBlock = curQuote;
+ // curQuote is now the correct thing to put curNode in
+ }
+
+ // tuck the node into the end of the active blockquote
+ uint32_t quoteLen = curQuote->Length();
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillHTMLIndent(Selection* aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ WillInsert(*aSelection, aCancel);
+
+ // initialize out param
+ // we want to ignore result of WillInsert()
+ *aCancel = false;
+ *aHandled = true;
+
+ nsresult rv = NormalizeSelection(aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+
+ // 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
+
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+ GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::indent);
+
+ // use these ranges to contruct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if nothing visible in list, make an empty block
+ if (ListIsEmptyLine(arrayOfNodes)) {
+ // get selection location
+ NS_ENSURE_STATE(aSelection->RangeCount());
+ nsCOMPtr<nsINode> parent = aSelection->GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection->GetRangeAt(0)->StartOffset();
+ NS_ENSURE_STATE(parent);
+
+ // make sure we can put a block here
+ rv = SplitAsNeeded(*nsGkAtoms::blockquote, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> theBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote,
+ parent, offset);
+ NS_ENSURE_STATE(theBlock);
+ // remember our new block for postprocessing
+ mNewBlock = theBlock;
+ // delete anything that was in the list of nodes
+ while (!arrayOfNodes.IsEmpty()) {
+ OwningNonNull<nsINode> curNode = arrayOfNodes[0];
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ arrayOfNodes.RemoveElementAt(0);
+ }
+ // put selection in new block
+ *aHandled = true;
+ rv = aSelection->Collapse(theBlock, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ return rv;
+ }
+
+ // Ok, now go through all the nodes and put them in a blockquote,
+ // or whatever is appropriate. Wohoo!
+ nsCOMPtr<nsINode> curParent;
+ nsCOMPtr<nsIContent> sibling;
+ nsCOMPtr<Element> curList, curQuote, indentedLI;
+ int32_t listCount = arrayOfNodes.Length();
+ for (int32_t i = 0; i < listCount; i++) {
+ // here's where we actually figure out what to do
+ NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
+ nsCOMPtr<nsIContent> curNode = arrayOfNodes[i]->AsContent();
+
+ // Ignore all non-editable nodes. Leave them be.
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsEditable(curNode)) {
+ continue;
+ }
+
+ curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // some logic for putting list items into nested lists...
+ if (HTMLEditUtils::IsList(curParent)) {
+ sibling = nullptr;
+
+ // Check for whether we should join a list that follows curNode.
+ // We do this if the next element is a list, and the list is of the
+ // same type (li/ol) as curNode was a part it.
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetNextHTMLSibling(curNode);
+ if (sibling && HTMLEditUtils::IsList(sibling) &&
+ curParent->NodeInfo()->NameAtom() ==
+ sibling->NodeInfo()->NameAtom() &&
+ curParent->NodeInfo()->NamespaceID() ==
+ sibling->NodeInfo()->NamespaceID()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, sibling, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ // Check for whether we should join a list that preceeds curNode.
+ // We do this if the previous element is a list, and the list is of
+ // the same type (li/ol) as curNode was a part of.
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(curNode);
+ if (sibling && HTMLEditUtils::IsList(sibling) &&
+ curParent->NodeInfo()->NameAtom() ==
+ sibling->NodeInfo()->NameAtom() &&
+ curParent->NodeInfo()->NamespaceID() ==
+ sibling->NodeInfo()->NamespaceID()) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, sibling, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+
+ sibling = nullptr;
+
+ // check to see if curList is still appropriate. Which it is if
+ // curNode is still right after it in the same list.
+ if (curList) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(curNode);
+ }
+
+ if (!curList || (sibling && sibling != curList)) {
+ // create a new nested list of correct type
+ rv =
+ SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
+ curParent, offset);
+ NS_ENSURE_STATE(curList);
+ // curList is now the correct thing to put curNode in
+ // remember our new block for postprocessing
+ mNewBlock = curList;
+ }
+ // tuck the node into the end of the active list
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // forget curQuote, if any
+ curQuote = nullptr;
+ }
+ // Not a list item, use blockquote?
+ else {
+ // 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.
+ nsCOMPtr<Element> listItem = IsInListItem(curNode);
+ if (listItem) {
+ if (indentedLI == listItem) {
+ // already indented this list item
+ continue;
+ }
+ curParent = listItem->GetParentNode();
+ offset = curParent ? curParent->IndexOf(listItem) : -1;
+ // check to see if curList is still appropriate. Which it is if
+ // curNode is still right after it in the same list.
+ if (curList) {
+ sibling = nullptr;
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(curNode);
+ }
+
+ if (!curList || (sibling && sibling != curList)) {
+ // create a new nested list of correct type
+ rv = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent,
+ offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
+ curParent, offset);
+ NS_ENSURE_STATE(curList);
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(listItem, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // remember we indented this li
+ indentedLI = listItem;
+ } else {
+ // 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 && InDifferentTableElements(curQuote, curNode)) {
+ curQuote = nullptr;
+ }
+
+ if (!curQuote) {
+ // First, check that our element can contain a blockquote.
+ if (!mTextEditor->CanContainTag(*curParent, *nsGkAtoms::blockquote)) {
+ return NS_OK; // cancelled
+ }
+
+ rv = SplitAsNeeded(*nsGkAtoms::blockquote, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curQuote = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent,
+ offset);
+ NS_ENSURE_STATE(curQuote);
+ // remember our new block for postprocessing
+ mNewBlock = curQuote;
+ // curQuote is now the correct thing to put curNode in
+ }
+
+ // tuck the node into the end of the active blockquote
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(curNode, curQuote, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // forget curList, if any
+ curList = nullptr;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::WillOutdent(Selection& aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aCancel && aHandled);
+ *aCancel = false;
+ *aHandled = true;
+ nsCOMPtr<nsIContent> rememberedLeftBQ, rememberedRightBQ;
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+ bool useCSS = htmlEditor->IsCSSEnabled();
+
+ nsresult rv = NormalizeSelection(&aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Some scoping for selection resetting - we may need to tweak it
+ {
+ AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
+
+ // 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
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetNodesFromSelection(aSelection, EditAction::outdent, arrayOfNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Okay, now go through all the nodes and remove a level of blockquoting,
+ // or whatever is appropriate. Wohoo!
+
+ nsCOMPtr<Element> curBlockQuote;
+ nsCOMPtr<nsIContent> firstBQChild, lastBQChild;
+ bool curBlockQuoteIsIndentedWithCSS = false;
+ for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
+ if (!arrayOfNodes[i]->IsContent()) {
+ continue;
+ }
+ OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
+
+ // Here's where we actually figure out what to do
+ nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // Is it a blockquote?
+ if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
+ // If it is a blockquote, remove it. So we need to finish up dealng
+ // with any curBlockQuote first.
+ if (curBlockQuote) {
+ rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
+ curBlockQuoteIsIndentedWithCSS,
+ getter_AddRefs(rememberedLeftBQ),
+ getter_AddRefs(rememberedRightBQ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ curBlockQuote = nullptr;
+ firstBQChild = nullptr;
+ lastBQChild = nullptr;
+ curBlockQuoteIsIndentedWithCSS = false;
+ }
+ rv = htmlEditor->RemoveBlockContainer(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ // Is it a block with a 'margin' property?
+ if (useCSS && IsBlockNode(curNode)) {
+ nsIAtom& marginProperty =
+ MarginPropertyAtomForIndent(*htmlEditor->mCSSEditUtils, curNode);
+ nsAutoString value;
+ htmlEditor->mCSSEditUtils->GetSpecifiedProperty(curNode,
+ marginProperty,
+ value);
+ float f;
+ nsCOMPtr<nsIAtom> unit;
+ NS_ENSURE_STATE(htmlEditor);
+ htmlEditor->mCSSEditUtils->ParseLength(value, &f,
+ getter_AddRefs(unit));
+ if (f > 0) {
+ ChangeIndentation(*curNode->AsElement(), Change::minus);
+ continue;
+ }
+ }
+ // Is it a list item?
+ if (HTMLEditUtils::IsListItem(curNode)) {
+ // If it is a list item, that means we are not outdenting whole list.
+ // So we need to finish up dealing with any curBlockQuote, and then pop
+ // this list item.
+ if (curBlockQuote) {
+ rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
+ curBlockQuoteIsIndentedWithCSS,
+ getter_AddRefs(rememberedLeftBQ),
+ getter_AddRefs(rememberedRightBQ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ curBlockQuote = nullptr;
+ firstBQChild = nullptr;
+ lastBQChild = nullptr;
+ curBlockQuoteIsIndentedWithCSS = false;
+ }
+ bool unused;
+ rv = PopListItem(GetAsDOMNode(curNode), &unused);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ // Do we have a blockquote that we are already committed to removing?
+ if (curBlockQuote) {
+ // If so, is this node a descendant?
+ if (EditorUtils::IsDescendantOf(curNode, curBlockQuote)) {
+ lastBQChild = curNode;
+ // Then we don't need to do anything different for this node
+ continue;
+ }
+ // Otherwise, we have progressed beyond end of curBlockQuote, so
+ // let's handle it now. We need to remove the portion of
+ // curBlockQuote that contains [firstBQChild - lastBQChild].
+ rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
+ curBlockQuoteIsIndentedWithCSS,
+ getter_AddRefs(rememberedLeftBQ),
+ getter_AddRefs(rememberedRightBQ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ curBlockQuote = nullptr;
+ firstBQChild = nullptr;
+ lastBQChild = nullptr;
+ curBlockQuoteIsIndentedWithCSS = false;
+ // Fall out and handle curNode
+ }
+
+ // Are we inside a blockquote?
+ OwningNonNull<nsINode> n = curNode;
+ curBlockQuoteIsIndentedWithCSS = false;
+ // Keep looking up the hierarchy as long as we don't hit the body or the
+ // active editing host or a table element (other than an entire table)
+ while (!n->IsHTMLElement(nsGkAtoms::body) &&
+ htmlEditor->IsDescendantOfEditorRoot(n) &&
+ (n->IsHTMLElement(nsGkAtoms::table) ||
+ !HTMLEditUtils::IsTableElement(n))) {
+ if (!n->GetParentNode()) {
+ break;
+ }
+ n = *n->GetParentNode();
+ if (n->IsHTMLElement(nsGkAtoms::blockquote)) {
+ // If so, remember it and the first node we are taking out of it.
+ curBlockQuote = n->AsElement();
+ firstBQChild = curNode;
+ lastBQChild = curNode;
+ break;
+ } else if (useCSS) {
+ nsIAtom& marginProperty =
+ MarginPropertyAtomForIndent(*htmlEditor->mCSSEditUtils, curNode);
+ nsAutoString value;
+ htmlEditor->mCSSEditUtils->GetSpecifiedProperty(*n, marginProperty,
+ value);
+ float f;
+ nsCOMPtr<nsIAtom> unit;
+ htmlEditor->mCSSEditUtils->ParseLength(value, &f, getter_AddRefs(unit));
+ if (f > 0 && !(HTMLEditUtils::IsList(curParent) &&
+ HTMLEditUtils::IsList(curNode))) {
+ curBlockQuote = n->AsElement();
+ firstBQChild = curNode;
+ lastBQChild = curNode;
+ curBlockQuoteIsIndentedWithCSS = true;
+ break;
+ }
+ }
+ }
+
+ if (!curBlockQuote) {
+ // Couldn't find enclosing blockquote. Handle list cases.
+ if (HTMLEditUtils::IsList(curParent)) {
+ // Move node out of list
+ if (HTMLEditUtils::IsList(curNode)) {
+ // Just unwrap this sublist
+ rv = htmlEditor->RemoveBlockContainer(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // handled list item case above
+ } else if (HTMLEditUtils::IsList(curNode)) {
+ // node is a list, but parent is non-list: move list items out
+ nsCOMPtr<nsIContent> child = curNode->GetLastChild();
+ while (child) {
+ if (HTMLEditUtils::IsListItem(child)) {
+ bool unused;
+ rv = PopListItem(GetAsDOMNode(child), &unused);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (HTMLEditUtils::IsList(child)) {
+ // 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.
+
+ rv = htmlEditor->MoveNode(child, curParent, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Delete any non-list items for now
+ rv = htmlEditor->DeleteNode(child);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ child = curNode->GetLastChild();
+ }
+ // Delete the now-empty list
+ rv = htmlEditor->RemoveBlockContainer(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (useCSS) {
+ nsCOMPtr<Element> element;
+ if (curNode->GetAsText()) {
+ // We want to outdent the parent of text nodes
+ element = curNode->GetParentElement();
+ } else if (curNode->IsElement()) {
+ element = curNode->AsElement();
+ }
+ if (element) {
+ ChangeIndentation(*element, Change::minus);
+ }
+ }
+ }
+ }
+ if (curBlockQuote) {
+ // We have a blockquote we haven't finished handling
+ rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
+ curBlockQuoteIsIndentedWithCSS,
+ getter_AddRefs(rememberedLeftBQ),
+ getter_AddRefs(rememberedRightBQ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ // Make sure selection didn't stick to last piece of content in old bq (only
+ // a problem for collapsed selections)
+ if (rememberedLeftBQ || rememberedRightBQ) {
+ if (aSelection.Collapsed()) {
+ // Push selection past end of rememberedLeftBQ
+ NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_OK);
+ nsCOMPtr<nsINode> startNode = aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t startOffset = aSelection.GetRangeAt(0)->StartOffset();
+ if (rememberedLeftBQ &&
+ (startNode == rememberedLeftBQ ||
+ EditorUtils::IsDescendantOf(startNode, rememberedLeftBQ))) {
+ // Selection is inside rememberedLeftBQ - push it past it.
+ startNode = rememberedLeftBQ->GetParentNode();
+ startOffset = startNode ? 1 + startNode->IndexOf(rememberedLeftBQ) : 0;
+ aSelection.Collapse(startNode, startOffset);
+ }
+ // And pull selection before beginning of rememberedRightBQ
+ startNode = aSelection.GetRangeAt(0)->GetStartParent();
+ startOffset = aSelection.GetRangeAt(0)->StartOffset();
+ if (rememberedRightBQ &&
+ (startNode == rememberedRightBQ ||
+ EditorUtils::IsDescendantOf(startNode, rememberedRightBQ))) {
+ // Selection is inside rememberedRightBQ - push it before it.
+ startNode = rememberedRightBQ->GetParentNode();
+ startOffset = startNode ? startNode->IndexOf(rememberedRightBQ) : -1;
+ aSelection.Collapse(startNode, startOffset);
+ }
+ }
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+
+/**
+ * RemovePartOfBlock() splits aBlock and move aStartChild to aEndChild out of
+ * aBlock.
+ */
+nsresult
+HTMLEditRules::RemovePartOfBlock(Element& aBlock,
+ nsIContent& aStartChild,
+ nsIContent& aEndChild)
+{
+ SplitBlock(aBlock, aStartChild, aEndChild);
+ // Get rid of part of blockquote we are outdenting
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->RemoveBlockContainer(aBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void
+HTMLEditRules::SplitBlock(Element& aBlock,
+ nsIContent& aStartChild,
+ nsIContent& aEndChild,
+ nsIContent** aOutLeftNode,
+ nsIContent** aOutRightNode,
+ nsIContent** aOutMiddleNode)
+{
+ // aStartChild and aEndChild must be exclusive descendants of aBlock
+ MOZ_ASSERT(EditorUtils::IsDescendantOf(&aStartChild, &aBlock) &&
+ EditorUtils::IsDescendantOf(&aEndChild, &aBlock));
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Get split point location
+ OwningNonNull<nsIContent> startParent = *aStartChild.GetParent();
+ int32_t startOffset = startParent->IndexOf(&aStartChild);
+
+ // Do the splits!
+ nsCOMPtr<nsIContent> newMiddleNode1;
+ htmlEditor->SplitNodeDeep(aBlock, startParent, startOffset,
+ HTMLEditor::EmptyContainers::no,
+ aOutLeftNode, getter_AddRefs(newMiddleNode1));
+
+ // Get split point location
+ OwningNonNull<nsIContent> endParent = *aEndChild.GetParent();
+ // +1 because we want to be after the child
+ int32_t endOffset = 1 + endParent->IndexOf(&aEndChild);
+
+ // Do the splits!
+ nsCOMPtr<nsIContent> newMiddleNode2;
+ htmlEditor->SplitNodeDeep(aBlock, endParent, endOffset,
+ HTMLEditor::EmptyContainers::no,
+ getter_AddRefs(newMiddleNode2), aOutRightNode);
+
+ if (aOutMiddleNode) {
+ if (newMiddleNode2) {
+ newMiddleNode2.forget(aOutMiddleNode);
+ } else {
+ newMiddleNode1.forget(aOutMiddleNode);
+ }
+ }
+}
+
+nsresult
+HTMLEditRules::OutdentPartOfBlock(Element& aBlock,
+ nsIContent& aStartChild,
+ nsIContent& aEndChild,
+ bool aIsBlockIndentedWithCSS,
+ nsIContent** aOutLeftNode,
+ nsIContent** aOutRightNode)
+{
+ MOZ_ASSERT(aOutLeftNode && aOutRightNode);
+
+ nsCOMPtr<nsIContent> middleNode;
+ SplitBlock(aBlock, aStartChild, aEndChild, aOutLeftNode, aOutRightNode,
+ getter_AddRefs(middleNode));
+
+ NS_ENSURE_STATE(middleNode);
+
+ if (!aIsBlockIndentedWithCSS) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->RemoveBlockContainer(*middleNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (middleNode->IsElement()) {
+ // We do nothing if middleNode isn't an element
+ nsresult rv = ChangeIndentation(*middleNode->AsElement(), Change::minus);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * ConvertListType() converts list type and list item type.
+ */
+nsresult
+HTMLEditRules::ConvertListType(Element* aList,
+ Element** aOutList,
+ nsIAtom* aListType,
+ nsIAtom* aItemType)
+{
+ MOZ_ASSERT(aList);
+ MOZ_ASSERT(aOutList);
+ MOZ_ASSERT(aListType);
+ MOZ_ASSERT(aItemType);
+
+ nsCOMPtr<nsINode> child = aList->GetFirstChild();
+ while (child) {
+ if (child->IsElement()) {
+ dom::Element* element = child->AsElement();
+ if (HTMLEditUtils::IsListItem(element) &&
+ !element->IsHTMLElement(aItemType)) {
+ child = mHTMLEditor->ReplaceContainer(element, aItemType);
+ NS_ENSURE_STATE(child);
+ } else if (HTMLEditUtils::IsList(element) &&
+ !element->IsHTMLElement(aListType)) {
+ nsCOMPtr<dom::Element> temp;
+ nsresult rv = ConvertListType(child->AsElement(), getter_AddRefs(temp),
+ aListType, aItemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ child = temp.forget();
+ }
+ }
+ child = child->GetNextSibling();
+ }
+
+ if (aList->IsHTMLElement(aListType)) {
+ nsCOMPtr<dom::Element> list = aList->AsElement();
+ list.forget(aOutList);
+ return NS_OK;
+ }
+
+ *aOutList = mHTMLEditor->ReplaceContainer(aList, aListType).take();
+ NS_ENSURE_STATE(aOutList);
+
+ return NS_OK;
+}
+
+
+/**
+ * CreateStyleForInsertText() takes care of clearing and setting appropriate
+ * style nodes for text insertion.
+ */
+nsresult
+HTMLEditRules::CreateStyleForInsertText(Selection& aSelection,
+ nsIDocument& aDoc)
+{
+ MOZ_ASSERT(mHTMLEditor->mTypeInState);
+
+ bool weDidSomething = false;
+ NS_ENSURE_STATE(aSelection.GetRangeAt(0));
+ nsCOMPtr<nsINode> node = aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+ // next examine our present style and make sure default styles are either
+ // present or explicitly overridden. If neither, add the default style to
+ // the TypeInState
+ int32_t length = mHTMLEditor->mDefaultStyles.Length();
+ for (int32_t j = 0; j < length; j++) {
+ PropItem* propItem = mHTMLEditor->mDefaultStyles[j];
+ MOZ_ASSERT(propItem);
+ bool bFirst, bAny, bAll;
+
+ // GetInlineProperty also examine TypeInState. The only gotcha here is
+ // that a cleared property looks like an unset property. For now I'm
+ // assuming that's not a problem: that default styles will always be
+ // multivalue styles (like font face or size) where clearing the style
+ // means we want to go back to the default. If we ever wanted a "toggle"
+ // style like bold for a default, though, I'll have to add code to detect
+ // the difference between unset and explicitly cleared, else user would
+ // never be able to unbold, for instance.
+ nsAutoString curValue;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetInlinePropertyBase(*propItem->tag, &propItem->attr,
+ nullptr, &bFirst, &bAny, &bAll,
+ &curValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!bAny) {
+ // no style set for this prop/attr
+ mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr,
+ propItem->value);
+ }
+ }
+
+ nsCOMPtr<Element> rootElement = aDoc.GetRootElement();
+ NS_ENSURE_STATE(rootElement);
+
+ // process clearing any styles first
+ nsAutoPtr<PropItem> item(mHTMLEditor->mTypeInState->TakeClearProperty());
+ while (item && node != rootElement) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->ClearStyle(address_of(node), &offset,
+ item->tag, &item->attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ item = mHTMLEditor->mTypeInState->TakeClearProperty();
+ weDidSomething = true;
+ }
+
+ // then process setting any styles
+ int32_t relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize();
+ item = mHTMLEditor->mTypeInState->TakeSetProperty();
+
+ if (item || relFontSize) {
+ // we have at least one style to add; make a new text node to insert style
+ // nodes above.
+ if (RefPtr<Text> text = node->GetAsText()) {
+ // if we are in a text node, split it
+ NS_ENSURE_STATE(mHTMLEditor);
+ offset = mHTMLEditor->SplitNodeDeep(*text, *text, offset);
+ NS_ENSURE_STATE(offset != -1);
+ node = node->GetParentNode();
+ }
+ if (!mHTMLEditor->IsContainer(node)) {
+ return NS_OK;
+ }
+ OwningNonNull<Text> newNode = aDoc.CreateTextNode(EmptyString());
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->InsertNode(*newNode, *node, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ node = newNode;
+ offset = 0;
+ weDidSomething = 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++) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RelativeFontChangeOnTextNode(dir, newNode, 0, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ while (item) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->SetInlinePropertyOnNode(*node->AsContent(),
+ *item->tag, &item->attr,
+ item->value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ item = mHTMLEditor->mTypeInState->TakeSetProperty();
+ }
+ }
+ if (weDidSomething) {
+ return aSelection.Collapse(node, offset);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * Figure out if aNode is (or is inside) an empty block. A block can have
+ * children and still be considered empty, if the children are empty or
+ * non-editable.
+ */
+nsresult
+HTMLEditRules::IsEmptyBlock(Element& aNode,
+ bool* aOutIsEmptyBlock,
+ MozBRCounts aMozBRCounts)
+{
+ MOZ_ASSERT(aOutIsEmptyBlock);
+ *aOutIsEmptyBlock = true;
+
+ NS_ENSURE_TRUE(IsBlockNode(aNode), NS_ERROR_NULL_POINTER);
+
+ return mHTMLEditor->IsEmptyNode(aNode.AsDOMNode(), aOutIsEmptyBlock,
+ aMozBRCounts == MozBRCounts::yes ? false
+ : true);
+}
+
+
+nsresult
+HTMLEditRules::WillAlign(Selection& aSelection,
+ const nsAString& aAlignType,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aCancel && aHandled);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ WillInsert(aSelection, aCancel);
+
+ // Initialize out param. We want to ignore result of WillInsert().
+ *aCancel = false;
+ *aHandled = false;
+
+ nsresult rv = NormalizeSelection(&aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
+
+ // 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
+ *aHandled = true;
+ nsTArray<OwningNonNull<nsINode>> nodeArray;
+ rv = GetNodesFromSelection(aSelection, EditAction::align, nodeArray);
+ NS_ENSURE_SUCCESS(rv, 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 emptyDiv = nodeArray.IsEmpty();
+ if (nodeArray.Length() == 1) {
+ OwningNonNull<nsINode> node = nodeArray[0];
+
+ if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(node))) {
+ // 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 AlignBlock
+ rv = AlignBlock(*node->AsElement(), aAlignType, ContentsOnly::yes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ if (TextEditUtils::IsBreak(node)) {
+ // The special case emptyDiv code (below) that consumes BRs 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 emptyDiv FALSE so that we fall
+ // through to the normal case alignment code.
+ //
+ // XXX: It seems a little error prone for the emptyDiv special case code
+ // to assume that the start node of the selection is the parent of the
+ // single node in the nodeArray, as the paragraph above points out. Do we
+ // rely on the selection start node because of the fact that nodeArray
+ // can be empty? We should probably revisit this issue. - kin
+
+ NS_ENSURE_STATE(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> parent =
+ *aSelection.GetRangeAt(0)->GetStartParent();
+
+ emptyDiv = !HTMLEditUtils::IsTableElement(parent) ||
+ HTMLEditUtils::IsTableCellOrCaption(parent);
+ }
+ }
+ if (emptyDiv) {
+ nsCOMPtr<nsINode> parent =
+ aSelection.GetRangeAt(0) ? aSelection.GetRangeAt(0)->GetStartParent()
+ : nullptr;
+ NS_ENSURE_STATE(parent);
+ int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+ rv = SplitAsNeeded(*nsGkAtoms::div, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Consume a trailing br, if any. This is to keep an alignment from
+ // creating extra lines, if possible.
+ nsCOMPtr<nsIContent> brContent =
+ htmlEditor->GetNextHTMLNode(parent, offset);
+ if (brContent && TextEditUtils::IsBreak(brContent)) {
+ // 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.
+ nsCOMPtr<nsIContent> sibling = htmlEditor->GetNextHTMLSibling(parent,
+ offset);
+ if (sibling && !IsBlockNode(*sibling)) {
+ rv = htmlEditor->DeleteNode(brContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ nsCOMPtr<Element> div = htmlEditor->CreateNode(nsGkAtoms::div, parent,
+ offset);
+ NS_ENSURE_STATE(div);
+ // Remember our new block for postprocessing
+ mNewBlock = div;
+ // Set up the alignment on the div, using HTML or CSS
+ rv = AlignBlock(*div, aAlignType, ContentsOnly::yes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aHandled = true;
+ // Put in a moz-br so that it won't get deleted
+ rv = CreateMozBR(div->AsDOMNode(), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aSelection.Collapse(div, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // Next we detect all the transitions in the array, where a transition
+ // means that adjacent nodes in the array don't have the same parent.
+
+ nsTArray<bool> transitionList;
+ MakeTransitionList(nodeArray, 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!
+
+ nsCOMPtr<Element> curDiv;
+ bool useCSS = htmlEditor->IsCSSEnabled();
+ for (size_t i = 0; i < nodeArray.Length(); i++) {
+ auto& curNode = nodeArray[i];
+ // Here's where we actually figure out what to do
+
+ // Ignore all non-editable nodes. Leave them be.
+ if (!htmlEditor->IsEditable(curNode)) {
+ 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 AlignBlock
+ if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(curNode))) {
+ rv = AlignBlock(*curNode->AsElement(), aAlignType, ContentsOnly::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Clear out curDiv so that we don't put nodes after this one into it
+ curDiv = nullptr;
+ continue;
+ }
+
+ nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // Skip insignificant formatting text nodes to prevent unnecessary
+ // structure splitting!
+ bool isEmptyTextNode = false;
+ if (curNode->GetAsText() &&
+ ((HTMLEditUtils::IsTableElement(curParent) &&
+ !HTMLEditUtils::IsTableCellOrCaption(*curParent)) ||
+ HTMLEditUtils::IsList(curParent) ||
+ (NS_SUCCEEDED(htmlEditor->IsEmptyNode(curNode, &isEmptyTextNode)) &&
+ isEmptyTextNode))) {
+ 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(curNode) ||
+ HTMLEditUtils::IsList(curNode)) {
+ rv = RemoveAlignment(GetAsDOMNode(curNode), aAlignType, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCSS) {
+ htmlEditor->mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
+ curNode->AsElement(), nullptr, &NS_LITERAL_STRING("align"),
+ &aAlignType, false);
+ curDiv = nullptr;
+ continue;
+ }
+ if (HTMLEditUtils::IsList(curParent)) {
+ // If we don't use CSS, add a contraint to list element: they have to
+ // be inside another list, i.e., >= second level of nesting
+ rv = AlignInnerBlocks(*curNode, &aAlignType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curDiv = nullptr;
+ continue;
+ }
+ // Clear out curDiv 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 (!curDiv || transitionList[i]) {
+ // First, check that our element can contain a div.
+ if (!mTextEditor->CanContainTag(*curParent, *nsGkAtoms::div)) {
+ // Cancelled
+ return NS_OK;
+ }
+
+ rv = SplitAsNeeded(*nsGkAtoms::div, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParent, offset);
+ NS_ENSURE_STATE(curDiv);
+ // Remember our new block for postprocessing
+ mNewBlock = curDiv;
+ // Set up the alignment on the div
+ rv = AlignBlock(*curDiv, aAlignType, ContentsOnly::yes);
+ }
+
+ NS_ENSURE_STATE(curNode->IsContent());
+
+ // Tuck the node into the end of the active div
+ rv = htmlEditor->MoveNode(curNode->AsContent(), curDiv, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * AlignInnerBlocks() aligns inside table cells or list items.
+ */
+nsresult
+HTMLEditRules::AlignInnerBlocks(nsINode& aNode,
+ const nsAString* alignType)
+{
+ NS_ENSURE_TRUE(alignType, NS_ERROR_NULL_POINTER);
+
+ // Gather list of table cells or list items
+ nsTArray<OwningNonNull<nsINode>> nodeArray;
+ TableCellAndListItemFunctor functor;
+ DOMIterator iter(aNode);
+ iter.AppendList(functor, nodeArray);
+
+ // Now that we have the list, align their contents as requested
+ for (auto& node : nodeArray) {
+ nsresult rv = AlignBlockContents(GetAsDOMNode(node), alignType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * AlignBlockContents() aligns contents of a block element.
+ */
+nsresult
+HTMLEditRules::AlignBlockContents(nsIDOMNode* aNode,
+ const nsAString* alignType)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_TRUE(node && alignType, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIContent> firstChild, lastChild;
+ nsCOMPtr<Element> divNode;
+
+ bool useCSS = mHTMLEditor->IsCSSEnabled();
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ firstChild = mHTMLEditor->GetFirstEditableChild(*node);
+ NS_ENSURE_STATE(mHTMLEditor);
+ lastChild = mHTMLEditor->GetLastEditableChild(*node);
+ NS_NAMED_LITERAL_STRING(attr, "align");
+ if (!firstChild) {
+ // this cell has no content, nothing to align
+ } else if (firstChild == lastChild &&
+ firstChild->IsHTMLElement(nsGkAtoms::div)) {
+ // the cell already has a div containing all of its content: just
+ // act on this div.
+ nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(firstChild);
+ if (useCSS) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr,
+ *alignType, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ } else {
+ // else we need to put in a div, set the alignment, and toss in all the children
+ NS_ENSURE_STATE(mHTMLEditor);
+ divNode = mHTMLEditor->CreateNode(nsGkAtoms::div, node, 0);
+ NS_ENSURE_STATE(divNode);
+ // set up the alignment on the div
+ nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(divNode);
+ if (useCSS) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ // tuck the children into the end of the active div
+ while (lastChild && (lastChild != divNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->MoveNode(lastChild, divNode, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ lastChild = mHTMLEditor->GetLastEditableChild(*node);
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * CheckForEmptyBlock() is called by WillDeleteSelection() to detect and handle
+ * case of deleting from inside an empty block.
+ */
+nsresult
+HTMLEditRules::CheckForEmptyBlock(nsINode* aStartNode,
+ Element* aBodyNode,
+ Selection* aSelection,
+ nsIEditor::EDirection aAction,
+ bool* aHandled)
+{
+ // If the editing host is an inline element, bail out early.
+ if (aBodyNode && IsInlineNode(*aBodyNode)) {
+ return NS_OK;
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // If we are inside an empty block, delete it. Note: do NOT delete table
+ // elements this way.
+ nsCOMPtr<Element> block = htmlEditor->GetBlock(*aStartNode);
+ bool bIsEmptyNode;
+ nsCOMPtr<Element> emptyBlock;
+ if (block && block != aBodyNode) {
+ // Efficiency hack, avoiding IsEmptyNode() call when in body
+ nsresult rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (block && bIsEmptyNode && !HTMLEditUtils::IsTableElement(block) &&
+ block != aBodyNode) {
+ emptyBlock = block;
+ block = htmlEditor->GetBlockNodeParent(emptyBlock);
+ if (block) {
+ rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ if (emptyBlock && emptyBlock->IsEditable()) {
+ nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode();
+ NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
+ int32_t offset = blockParent->IndexOf(emptyBlock);
+
+ if (HTMLEditUtils::IsListItem(emptyBlock)) {
+ // Are we the first list item in the list?
+ bool bIsFirst;
+ NS_ENSURE_STATE(htmlEditor);
+ nsresult rv =
+ htmlEditor->IsFirstEditableChild(GetAsDOMNode(emptyBlock), &bIsFirst);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bIsFirst) {
+ nsCOMPtr<nsINode> listParent = blockParent->GetParentNode();
+ NS_ENSURE_TRUE(listParent, NS_ERROR_FAILURE);
+ int32_t listOffset = listParent->IndexOf(blockParent);
+ // If we are a sublist, skip the br creation
+ if (!HTMLEditUtils::IsList(listParent)) {
+ // Create a br before list
+ NS_ENSURE_STATE(htmlEditor);
+ nsCOMPtr<Element> br =
+ htmlEditor->CreateBR(listParent, listOffset);
+ NS_ENSURE_STATE(br);
+ // Adjust selection to be right before it
+ rv = aSelection->Collapse(listParent, listOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Else just let selection percolate up. We'll adjust it in
+ // AfterEdit()
+ }
+ } else {
+ if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
+ aAction == nsIEditor::eToEndOfLine) {
+ // Move to the start of the next node, if any
+ nsCOMPtr<nsIContent> nextNode = htmlEditor->GetNextNode(blockParent,
+ offset + 1, true);
+ if (nextNode) {
+ EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
+ nsresult rv = aSelection->Collapse(pt.node, pt.offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Adjust selection to be right after it.
+ nsresult rv = aSelection->Collapse(blockParent, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else if (aAction == nsIEditor::ePrevious ||
+ aAction == nsIEditor::ePreviousWord ||
+ aAction == nsIEditor::eToBeginningOfLine) {
+ // Move to the end of the previous node
+ nsCOMPtr<nsIContent> priorNode = htmlEditor->GetPriorNode(blockParent,
+ offset,
+ true);
+ if (priorNode) {
+ EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
+ nsresult rv = aSelection->Collapse(pt.node, pt.offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsresult rv = aSelection->Collapse(blockParent, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else if (aAction != nsIEditor::eNone) {
+ NS_RUNTIMEABORT("CheckForEmptyBlock doesn't support this action yet");
+ }
+ }
+ NS_ENSURE_STATE(htmlEditor);
+ *aHandled = true;
+ nsresult rv = htmlEditor->DeleteNode(emptyBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+Element*
+HTMLEditRules::CheckForInvisibleBR(Element& aBlock,
+ BRLocation aWhere,
+ int32_t aOffset)
+{
+ nsCOMPtr<nsINode> testNode;
+ int32_t testOffset = 0;
+
+ if (aWhere == BRLocation::blockEnd) {
+ // No block crossing
+ nsCOMPtr<nsIContent> rightmostNode =
+ mHTMLEditor->GetRightmostChild(&aBlock, true);
+
+ if (!rightmostNode) {
+ return nullptr;
+ }
+
+ testNode = rightmostNode->GetParentNode();
+ // Use offset + 1, so last node is included in our evaluation
+ testOffset = testNode->IndexOf(rightmostNode) + 1;
+ } else if (aOffset) {
+ testNode = &aBlock;
+ // We'll check everything to the left of the input position
+ testOffset = aOffset;
+ } else {
+ return nullptr;
+ }
+
+ WSRunObject wsTester(mHTMLEditor, testNode, testOffset);
+ if (WSType::br == wsTester.mStartReason) {
+ return wsTester.mStartReasonNode->AsElement();
+ }
+
+ return nullptr;
+}
+
+/**
+ * aLists and aTables allow the caller to specify what kind of content to
+ * "look inside". If aTables is Tables::yes, look inside any table content,
+ * and insert the inner content into the supplied issupportsarray at offset
+ * aIndex. Similarly with aLists and list content. aIndex is updated to
+ * point past inserted elements.
+ */
+void
+HTMLEditRules::GetInnerContent(
+ nsINode& aNode,
+ nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
+ int32_t* aIndex,
+ Lists aLists,
+ Tables aTables)
+{
+ MOZ_ASSERT(aIndex);
+
+ for (nsCOMPtr<nsIContent> node = mHTMLEditor->GetFirstEditableChild(aNode);
+ node; node = node->GetNextSibling()) {
+ if ((aLists == Lists::yes && (HTMLEditUtils::IsList(node) ||
+ HTMLEditUtils::IsListItem(node))) ||
+ (aTables == Tables::yes && HTMLEditUtils::IsTableElement(node))) {
+ GetInnerContent(*node, aOutArrayOfNodes, aIndex, aLists, aTables);
+ } else {
+ aOutArrayOfNodes.InsertElementAt(*aIndex, *node);
+ (*aIndex)++;
+ }
+ }
+}
+
+/**
+ * Promotes selection to include blocks that have all their children selected.
+ */
+nsresult
+HTMLEditRules::ExpandSelectionForDeletion(Selection& aSelection)
+{
+ // Don't need to touch collapsed selections
+ if (aSelection.Collapsed()) {
+ return NS_OK;
+ }
+
+ // We don't need to mess with cell selections, and we assume multirange
+ // selections are those.
+ if (aSelection.RangeCount() != 1) {
+ return NS_OK;
+ }
+
+ // Find current sel start and end
+ NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_ERROR_NULL_POINTER);
+ OwningNonNull<nsRange> range = *aSelection.GetRangeAt(0);
+
+ nsCOMPtr<nsINode> selStartNode = range->GetStartParent();
+ int32_t selStartOffset = range->StartOffset();
+ nsCOMPtr<nsINode> selEndNode = range->GetEndParent();
+ int32_t selEndOffset = range->EndOffset();
+
+ // Find current selection common block parent
+ nsCOMPtr<Element> selCommon =
+ HTMLEditor::GetBlock(*range->GetCommonAncestor());
+ NS_ENSURE_STATE(selCommon);
+
+ // Set up for loops and cache our root element
+ nsCOMPtr<nsINode> firstBRParent;
+ nsCOMPtr<nsINode> unused;
+ int32_t visOffset = 0, firstBROffset = 0;
+ WSType wsType;
+ nsCOMPtr<Element> root = mHTMLEditor->GetActiveEditingHost();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ // Find previous visible thingy before start of selection
+ if (selStartNode != selCommon && selStartNode != root) {
+ while (true) {
+ WSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
+ wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(unused),
+ &visOffset, &wsType);
+ if (wsType != WSType::thisBlock) {
+ break;
+ }
+ // We want to keep looking up. But stop if we are crossing table
+ // element boundaries, or if we hit the root.
+ if (HTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
+ selCommon == wsObj.mStartReasonNode ||
+ root == wsObj.mStartReasonNode) {
+ break;
+ }
+ selStartNode = wsObj.mStartReasonNode->GetParentNode();
+ selStartOffset = selStartNode ?
+ selStartNode->IndexOf(wsObj.mStartReasonNode) : -1;
+ }
+ }
+
+ // Find next visible thingy after end of selection
+ if (selEndNode != selCommon && selEndNode != root) {
+ for (;;) {
+ WSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
+ wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(unused),
+ &visOffset, &wsType);
+ if (wsType == WSType::br) {
+ if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode)) {
+ break;
+ }
+ if (!firstBRParent) {
+ firstBRParent = selEndNode;
+ firstBROffset = selEndOffset;
+ }
+ selEndNode = wsObj.mEndReasonNode->GetParentNode();
+ selEndOffset = selEndNode
+ ? selEndNode->IndexOf(wsObj.mEndReasonNode) + 1 : 0;
+ } else if (wsType == WSType::thisBlock) {
+ // We want to keep looking up. But stop if we are crossing table
+ // element boundaries, or if we hit the root.
+ if (HTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
+ selCommon == wsObj.mEndReasonNode ||
+ root == wsObj.mEndReasonNode) {
+ break;
+ }
+ selEndNode = wsObj.mEndReasonNode->GetParentNode();
+ selEndOffset = 1 + selEndNode->IndexOf(wsObj.mEndReasonNode);
+ } else {
+ break;
+ }
+ }
+ }
+ // Now set the selection to the new range
+ aSelection.Collapse(selStartNode, selStartOffset);
+
+ // Expand selection endpoint only if we didn't pass a br, or if we really
+ // needed to pass that br (i.e., its block is now totally selected)
+ bool doEndExpansion = true;
+ if (firstBRParent) {
+ // Find block node containing br
+ nsCOMPtr<Element> brBlock = HTMLEditor::GetBlock(*firstBRParent);
+ bool nodeBefore = false, nodeAfter = false;
+
+ // Create a range that represents expanded selection
+ RefPtr<nsRange> range = new nsRange(selStartNode);
+ nsresult rv = range->SetStart(selStartNode, selStartOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = range->SetEnd(selEndNode, selEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if block is entirely inside range
+ if (brBlock) {
+ nsRange::CompareNodeToRange(brBlock, range, &nodeBefore, &nodeAfter);
+ }
+
+ // If block isn't contained, forgo grabbing the br in expanded selection
+ if (nodeBefore || nodeAfter) {
+ doEndExpansion = false;
+ }
+ }
+ if (doEndExpansion) {
+ nsresult rv = aSelection.Extend(selEndNode, selEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Only expand to just before br
+ nsresult rv = aSelection.Extend(firstBRParent, firstBROffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * NormalizeSelection() tweaks non-collapsed selections to be more "natural".
+ * Idea here is to adjust selection endpoint 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.
+ */
+nsresult
+HTMLEditRules::NormalizeSelection(Selection* inSelection)
+{
+ NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
+
+ // don't need to touch collapsed selections
+ if (inSelection->Collapsed()) {
+ return NS_OK;
+ }
+
+ int32_t rangeCount;
+ nsresult rv = inSelection->GetRangeCount(&rangeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we don't need to mess with cell selections, and we assume multirange selections are those.
+ if (rangeCount != 1) {
+ return NS_OK;
+ }
+
+ RefPtr<nsRange> range = inSelection->GetRangeAt(0);
+ NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIDOMNode> startNode, endNode;
+ int32_t startOffset, endOffset;
+ nsCOMPtr<nsIDOMNode> newStartNode, newEndNode;
+ int32_t newStartOffset, newEndOffset;
+
+ rv = range->GetStartContainer(getter_AddRefs(startNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = range->GetStartOffset(&startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = range->GetEndContainer(getter_AddRefs(endNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = range->GetEndOffset(&endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // adjusted values default to original values
+ newStartNode = startNode;
+ newStartOffset = startOffset;
+ newEndNode = endNode;
+ newEndOffset = endOffset;
+
+ // some locals we need for whitespace code
+ nsCOMPtr<nsINode> unused;
+ int32_t offset;
+ WSType wsType;
+
+ // let the whitespace code do the heavy lifting
+ WSRunObject wsEndObj(mHTMLEditor, endNode, endOffset);
+ // is there any intervening visible whitespace? if so we can't push selection past that,
+ // it would visibly change maening of users selection
+ nsCOMPtr<nsINode> endNode_(do_QueryInterface(endNode));
+ wsEndObj.PriorVisibleNode(endNode_, endOffset, address_of(unused),
+ &offset, &wsType);
+ if (wsType != WSType::text && wsType != WSType::normalWS) {
+ // eThisBlock and eOtherBlock conveniently distinquish cases
+ // of going "down" into a block and "up" out of a block.
+ if (wsEndObj.mStartReason == WSType::otherBlock) {
+ // endpoint is just after the close of a block.
+ nsCOMPtr<nsIDOMNode> child =
+ GetAsDOMNode(mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode,
+ true));
+ if (child) {
+ newEndNode = EditorBase::GetNodeLocation(child, &newEndOffset);
+ ++newEndOffset; // offset *after* child
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsEndObj.mStartReason == WSType::thisBlock) {
+ // endpoint is just after start of this block
+ nsCOMPtr<nsIDOMNode> child;
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child));
+ if (child) {
+ newEndNode = EditorBase::GetNodeLocation(child, &newEndOffset);
+ ++newEndOffset; // offset *after* child
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsEndObj.mStartReason == WSType::br) {
+ // endpoint is just after break. lets adjust it to before it.
+ newEndNode =
+ EditorBase::GetNodeLocation(GetAsDOMNode(wsEndObj.mStartReasonNode),
+ &newEndOffset);
+ }
+ }
+
+
+ // similar dealio for start of range
+ WSRunObject wsStartObj(mHTMLEditor, startNode, startOffset);
+ // is there any intervening visible whitespace? if so we can't push selection past that,
+ // it would visibly change maening of users selection
+ nsCOMPtr<nsINode> startNode_(do_QueryInterface(startNode));
+ wsStartObj.NextVisibleNode(startNode_, startOffset, address_of(unused),
+ &offset, &wsType);
+ if (wsType != WSType::text && wsType != WSType::normalWS) {
+ // eThisBlock and eOtherBlock conveniently distinquish cases
+ // of going "down" into a block and "up" out of a block.
+ if (wsStartObj.mEndReason == WSType::otherBlock) {
+ // startpoint is just before the start of a block.
+ nsCOMPtr<nsIDOMNode> child =
+ GetAsDOMNode(mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode,
+ true));
+ if (child) {
+ newStartNode = EditorBase::GetNodeLocation(child, &newStartOffset);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsStartObj.mEndReason == WSType::thisBlock) {
+ // startpoint is just before end of this block
+ nsCOMPtr<nsIDOMNode> child;
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child));
+ if (child) {
+ newStartNode = EditorBase::GetNodeLocation(child, &newStartOffset);
+ }
+ // else block is empty - we can leave selection alone here, i think.
+ } else if (wsStartObj.mEndReason == WSType::br) {
+ // startpoint is just before a break. lets adjust it to after it.
+ newStartNode =
+ EditorBase::GetNodeLocation(GetAsDOMNode(wsStartObj.mEndReasonNode),
+ &newStartOffset);
+ ++newStartOffset; // offset *after* break
+ }
+ }
+
+ // there is a demented possiblity 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: ie, new end before old start, or new start after old end. If so
+ // then just leave things alone.
+
+ int16_t comp;
+ comp = nsContentUtils::ComparePoints(startNode, startOffset,
+ newEndNode, newEndOffset);
+ if (comp == 1) {
+ return NS_OK; // New end before old start.
+ }
+ comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset,
+ endNode, endOffset);
+ if (comp == 1) {
+ return NS_OK; // New start after old end.
+ }
+
+ // otherwise set selection to new values.
+ inSelection->Collapse(newStartNode, newStartOffset);
+ inSelection->Extend(newEndNode, newEndOffset);
+ return NS_OK;
+}
+
+/**
+ * GetPromotedPoint() figures out where a start or end point for a block
+ * operation really is.
+ */
+void
+HTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere,
+ nsIDOMNode* aNode,
+ int32_t aOffset,
+ EditAction actionID,
+ nsCOMPtr<nsIDOMNode>* outNode,
+ int32_t* outOffset)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ MOZ_ASSERT(node && outNode && outOffset);
+
+ // default values
+ *outNode = node->AsDOMNode();
+ *outOffset = aOffset;
+
+ // we do one thing for text actions, something else entirely for other
+ // actions
+ if (actionID == EditAction::insertText ||
+ actionID == EditAction::insertIMEText ||
+ actionID == EditAction::insertBreak ||
+ actionID == EditAction::deleteText) {
+ bool isSpace, isNBSP;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(node), temp;
+ // for text actions, we want to look backwards (or forwards, as
+ // appropriate) for additional whitespace 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!
+ while (content) {
+ int32_t offset;
+ if (aWhere == kStart) {
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset,
+ &isSpace, &isNBSP,
+ getter_AddRefs(temp), &offset);
+ } else {
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ mHTMLEditor->IsNextCharInNodeWhitespace(content, *outOffset,
+ &isSpace, &isNBSP,
+ getter_AddRefs(temp), &offset);
+ }
+ if (isSpace || isNBSP) {
+ content = temp;
+ *outOffset = offset;
+ } else {
+ break;
+ }
+ }
+
+ *outNode = content->AsDOMNode();
+ return;
+ }
+
+ int32_t offset = aOffset;
+
+ // else not a text section. In this case we want to see if we should grab
+ // any adjacent inline nodes and/or parents and other ancestors
+ if (aWhere == kStart) {
+ // some special casing for text nodes
+ if (node->IsNodeOfType(nsINode::eTEXT)) {
+ if (!node->GetParentNode()) {
+ // Okay, can't promote any further
+ return;
+ }
+ offset = node->GetParentNode()->IndexOf(node);
+ node = node->GetParentNode();
+ }
+
+ // look back through any further inline nodes that aren't across a <br>
+ // from us, and that are enclosed in the same block.
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nsCOMPtr<nsINode> priorNode =
+ mHTMLEditor->GetPriorHTMLNode(node, offset, true);
+
+ while (priorNode && priorNode->GetParentNode() &&
+ mHTMLEditor && !mHTMLEditor->IsVisBreak(priorNode) &&
+ !IsBlockNode(*priorNode)) {
+ offset = priorNode->GetParentNode()->IndexOf(priorNode);
+ node = priorNode->GetParentNode();
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true);
+ }
+
+ // finding the real start for this point. look up the tree for as long as
+ // we are the first node in the container, and as long as we haven't hit
+ // the body node.
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nsCOMPtr<nsIContent> nearNode =
+ mHTMLEditor->GetPriorHTMLNode(node, offset, true);
+ while (!nearNode && !node->IsHTMLElement(nsGkAtoms::body) &&
+ node->GetParentNode()) {
+ // some cutoffs are here: we don't need to also include them in the
+ // aWhere == kEnd case. as long as they are in one or the other it will
+ // work. special case for outdent: don't keep looking up if we have
+ // found a blockquote element to act on
+ if (actionID == EditAction::outdent &&
+ node->IsHTMLElement(nsGkAtoms::blockquote)) {
+ break;
+ }
+
+ int32_t parentOffset = node->GetParentNode()->IndexOf(node);
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+
+ // 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.
+ bool blockLevelAction = actionID == EditAction::indent ||
+ actionID == EditAction::outdent ||
+ actionID == EditAction::align ||
+ actionID == EditAction::makeBasicBlock;
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
+ (blockLevelAction || !mHTMLEditor ||
+ !mHTMLEditor->IsDescendantOfEditorRoot(node))) {
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ break;
+ }
+
+ node = parent;
+ offset = parentOffset;
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true);
+ }
+ *outNode = node->AsDOMNode();
+ *outOffset = offset;
+ return;
+ }
+
+ // aWhere == kEnd
+ // some special casing for text nodes
+ if (node->IsNodeOfType(nsINode::eTEXT)) {
+ if (!node->GetParentNode()) {
+ // Okay, can't promote any further
+ return;
+ }
+ // want to be after the text node
+ offset = 1 + node->GetParentNode()->IndexOf(node);
+ node = node->GetParentNode();
+ }
+
+ // look ahead through any further inline nodes that aren't across a <br> from
+ // us, and that are enclosed in the same block.
+ NS_ENSURE_TRUE(mHTMLEditor, /* void */);
+ nsCOMPtr<nsIContent> nextNode =
+ mHTMLEditor->GetNextHTMLNode(node, offset, true);
+
+ while (nextNode && !IsBlockNode(*nextNode) && nextNode->GetParentNode()) {
+ offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode);
+ node = nextNode->GetParentNode();
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ if (mHTMLEditor->IsVisBreak(nextNode)) {
+ break;
+ }
+
+ // Check for newlines in pre-formatted text nodes.
+ bool isPRE;
+ mHTMLEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
+ if (isPRE) {
+ nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(nextNode);
+ if (textNode) {
+ nsAutoString tempString;
+ textNode->GetData(tempString);
+ int32_t newlinePos = tempString.FindChar(nsCRT::LF);
+ if (newlinePos >= 0) {
+ if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
+ // No need for special processing if the newline is at the end.
+ break;
+ }
+ *outNode = nextNode->AsDOMNode();
+ *outOffset = newlinePos + 1;
+ return;
+ }
+ }
+ }
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
+ }
+
+ // finding the real end for this point. look up the tree for as long as we
+ // are the last node in the container, and as long as we haven't hit the body
+ // node.
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nsCOMPtr<nsIContent> nearNode =
+ mHTMLEditor->GetNextHTMLNode(node, offset, true);
+ while (!nearNode && !node->IsHTMLElement(nsGkAtoms::body) &&
+ node->GetParentNode()) {
+ int32_t parentOffset = node->GetParentNode()->IndexOf(node);
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+
+ // 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 ((!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(node)) &&
+ (!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(parent))) {
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ break;
+ }
+
+ node = parent;
+ // we want to be AFTER nearNode
+ offset = parentOffset + 1;
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
+ }
+ *outNode = node->AsDOMNode();
+ *outOffset = offset;
+}
+
+/**
+ * GetPromotedRanges() runs all the selection range endpoint through
+ * GetPromotedPoint().
+ */
+void
+HTMLEditRules::GetPromotedRanges(Selection& aSelection,
+ nsTArray<RefPtr<nsRange>>& outArrayOfRanges,
+ EditAction inOperationType)
+{
+ uint32_t rangeCount = aSelection.RangeCount();
+
+ for (uint32_t i = 0; i < rangeCount; i++) {
+ RefPtr<nsRange> selectionRange = aSelection.GetRangeAt(i);
+ MOZ_ASSERT(selectionRange);
+
+ // Clone range so we don't muck with actual selection ranges
+ RefPtr<nsRange> opRange = selectionRange->CloneRange();
+
+ // 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.
+ PromoteRange(*opRange, inOperationType);
+
+ // Stuff new opRange into array
+ outArrayOfRanges.AppendElement(opRange);
+ }
+}
+
+/**
+ * PromoteRange() expands a range to include any parents for which all editable
+ * children are already in range.
+ */
+void
+HTMLEditRules::PromoteRange(nsRange& aRange,
+ EditAction aOperationType)
+{
+ NS_ENSURE_TRUE(mHTMLEditor, );
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
+ nsCOMPtr<nsINode> endNode = aRange.GetEndParent();
+ int32_t startOffset = aRange.StartOffset();
+ int32_t endOffset = aRange.EndOffset();
+
+ // MOOSE major hack:
+ // GetPromotedPoint doesn'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 GetPromotedPoint. :-(
+ if (startNode == endNode && startOffset == endOffset) {
+ nsCOMPtr<Element> block = htmlEditor->GetBlock(*startNode);
+ if (block) {
+ bool bIsEmptyNode = false;
+ nsCOMPtr<nsIContent> root = htmlEditor->GetActiveEditingHost();
+ // Make sure we don't go higher than our root element in the content tree
+ NS_ENSURE_TRUE(root, );
+ if (!nsContentUtils::ContentIsDescendantOf(root, block)) {
+ htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
+ }
+ if (bIsEmptyNode) {
+ startNode = block;
+ endNode = block;
+ startOffset = 0;
+ endOffset = block->Length();
+ }
+ }
+ }
+
+ // 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.
+
+ nsCOMPtr<nsIDOMNode> opStartNode;
+ nsCOMPtr<nsIDOMNode> opEndNode;
+ int32_t opStartOffset, opEndOffset;
+
+ GetPromotedPoint(kStart, GetAsDOMNode(startNode), startOffset,
+ aOperationType, address_of(opStartNode), &opStartOffset);
+ GetPromotedPoint(kEnd, GetAsDOMNode(endNode), endOffset, aOperationType,
+ address_of(opEndNode), &opEndOffset);
+
+ // Make sure that the new range ends up to be in the editable section.
+ if (!htmlEditor->IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) ||
+ !htmlEditor->IsDescendantOfEditorRoot(
+ EditorBase::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) {
+ return;
+ }
+
+ DebugOnly<nsresult> rv = aRange.SetStart(opStartNode, opStartOffset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aRange.SetEnd(opEndNode, opEndOffset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+class UniqueFunctor final : public BoolDomIterFunctor
+{
+public:
+ explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
+ : mArray(aArray)
+ {
+ }
+
+ // Used to build list of all nodes iterator covers.
+ virtual bool operator()(nsINode* aNode) const
+ {
+ return !mArray.Contains(aNode);
+ }
+
+private:
+ nsTArray<OwningNonNull<nsINode>>& mArray;
+};
+
+/**
+ * GetNodesForOperation() runs through the ranges in the array and construct a
+ * new array of nodes to be acted on.
+ */
+nsresult
+HTMLEditRules::GetNodesForOperation(
+ nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
+ nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
+ EditAction aOperationType,
+ TouchContent aTouchContent)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ int32_t rangeCount = aArrayOfRanges.Length();
+ if (aTouchContent == TouchContent::yes) {
+ // Split text nodes. This is necessary, since GetPromotedPoint() may return a
+ // range ending in a text node in case where part of a pre-formatted
+ // elements needs to be moved.
+ for (int32_t i = 0; i < rangeCount; i++) {
+ RefPtr<nsRange> r = aArrayOfRanges[i];
+ nsCOMPtr<nsIContent> endParent = do_QueryInterface(r->GetEndParent());
+ if (!htmlEditor->IsTextNode(endParent)) {
+ continue;
+ }
+ nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(endParent);
+ if (textNode) {
+ int32_t offset = r->EndOffset();
+ nsAutoString tempString;
+ textNode->GetData(tempString);
+
+ if (0 < offset && offset < static_cast<int32_t>(tempString.Length())) {
+ // Split the text node.
+ nsCOMPtr<nsIDOMNode> tempNode;
+ nsresult rv = htmlEditor->SplitNode(endParent->AsDOMNode(), offset,
+ getter_AddRefs(tempNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Correct the range.
+ // The new end parent becomes the parent node of the text.
+ nsCOMPtr<nsIContent> newParent = endParent->GetParent();
+ r->SetEnd(newParent, newParent->IndexOf(endParent));
+ }
+ }
+ }
+ }
+
+ // Bust up any inlines that cross our range endpoints, but only if we are
+ // allowed to touch content.
+
+ if (aTouchContent == TouchContent::yes) {
+ nsTArray<OwningNonNull<RangeItem>> rangeItemArray;
+ rangeItemArray.AppendElements(rangeCount);
+
+ // First register ranges for special editor gravity
+ for (int32_t i = 0; i < rangeCount; i++) {
+ rangeItemArray[i] = new RangeItem();
+ rangeItemArray[i]->StoreRange(aArrayOfRanges[0]);
+ htmlEditor->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]);
+ aArrayOfRanges.RemoveElementAt(0);
+ }
+ // Now bust up inlines.
+ nsresult rv = NS_OK;
+ for (auto& item : Reversed(rangeItemArray)) {
+ rv = BustUpInlinesAtRangeEndpoints(*item);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+ // Then unregister the ranges
+ for (auto& item : rangeItemArray) {
+ htmlEditor->mRangeUpdater.DropRangeItem(item);
+ aArrayOfRanges.AppendElement(item->GetRange());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Gather up a list of all the nodes
+ for (auto& range : aArrayOfRanges) {
+ DOMSubtreeIterator iter;
+ nsresult rv = iter.Init(*range);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aOutArrayOfNodes.IsEmpty()) {
+ iter.AppendList(TrivialFunctor(), aOutArrayOfNodes);
+ } else {
+ // We don't want duplicates in aOutArrayOfNodes, so we use an
+ // iterator/functor that only return nodes that are not already in
+ // aOutArrayOfNodes.
+ nsTArray<OwningNonNull<nsINode>> nodes;
+ iter.AppendList(UniqueFunctor(aOutArrayOfNodes), nodes);
+ aOutArrayOfNodes.AppendElements(nodes);
+ }
+ }
+
+ // Certain operations should not act on li's and td's, but rather inside
+ // them. Alter the list as needed.
+ if (aOperationType == EditAction::makeBasicBlock) {
+ for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
+ if (HTMLEditUtils::IsListItem(node)) {
+ int32_t j = i;
+ aOutArrayOfNodes.RemoveElementAt(i);
+ GetInnerContent(*node, aOutArrayOfNodes, &j);
+ }
+ }
+ }
+ // Indent/outdent already do something special for list items, but we still
+ // need to make sure we don't act on table elements
+ else if (aOperationType == EditAction::outdent ||
+ aOperationType == EditAction::indent ||
+ aOperationType == EditAction::setAbsolutePosition) {
+ for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
+ if (HTMLEditUtils::IsTableElementButNotTable(node)) {
+ int32_t j = i;
+ aOutArrayOfNodes.RemoveElementAt(i);
+ GetInnerContent(*node, aOutArrayOfNodes, &j);
+ }
+ }
+ }
+ // Outdent should look inside of divs.
+ if (aOperationType == EditAction::outdent &&
+ !htmlEditor->IsCSSEnabled()) {
+ for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
+ if (node->IsHTMLElement(nsGkAtoms::div)) {
+ int32_t j = i;
+ aOutArrayOfNodes.RemoveElementAt(i);
+ GetInnerContent(*node, aOutArrayOfNodes, &j, Lists::no, Tables::no);
+ }
+ }
+ }
+
+
+ // 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
+ if (aOperationType == EditAction::makeBasicBlock ||
+ aOperationType == EditAction::makeList ||
+ aOperationType == EditAction::align ||
+ aOperationType == EditAction::setAbsolutePosition ||
+ aOperationType == EditAction::indent ||
+ aOperationType == EditAction::outdent) {
+ for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
+ if (aTouchContent == TouchContent::yes && IsInlineNode(node) &&
+ htmlEditor->IsContainer(node) && !htmlEditor->IsTextNode(node)) {
+ nsTArray<OwningNonNull<nsINode>> arrayOfInlines;
+ nsresult rv = BustUpInlinesAtBRs(*node->AsContent(), arrayOfInlines);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Put these nodes in aOutArrayOfNodes, replacing the current node
+ aOutArrayOfNodes.RemoveElementAt(i);
+ aOutArrayOfNodes.InsertElementsAt(i, arrayOfInlines);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void
+HTMLEditRules::GetChildNodesForOperation(
+ nsINode& aNode,
+ nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes)
+{
+ for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
+ child; child = child->GetNextSibling()) {
+ outArrayOfNodes.AppendElement(*child);
+ }
+}
+
+nsresult
+HTMLEditRules::GetListActionNodes(
+ nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
+ EntireList aEntireList,
+ TouchContent aTouchContent)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+
+ // Added this in so that ui code can ask to change an entire list, even if
+ // selection is only in part of it. used by list item dialog.
+ if (aEntireList == EntireList::yes) {
+ uint32_t rangeCount = selection->RangeCount();
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
+ for (nsCOMPtr<nsINode> parent = range->GetCommonAncestor();
+ parent; parent = parent->GetParentNode()) {
+ if (HTMLEditUtils::IsList(parent)) {
+ aOutArrayOfNodes.AppendElement(*parent);
+ break;
+ }
+ }
+ }
+ // If we didn't find any nodes this way, then try the normal way. Perhaps
+ // the selection spans multiple lists but with no common list parent.
+ if (!aOutArrayOfNodes.IsEmpty()) {
+ return NS_OK;
+ }
+ }
+
+ {
+ // We don't like other people messing with our selection!
+ AutoTransactionsConserveSelection dontSpazMySelection(htmlEditor);
+
+ // contruct a list of nodes to act on.
+ nsresult rv = GetNodesFromSelection(*selection, EditAction::makeList,
+ aOutArrayOfNodes, aTouchContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Pre-process our list of nodes
+ for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> testNode = aOutArrayOfNodes[i];
+
+ // Remove all non-editable nodes. Leave them be.
+ if (!htmlEditor->IsEditable(testNode)) {
+ aOutArrayOfNodes.RemoveElementAt(i);
+ continue;
+ }
+
+ // Scan for table elements and divs. If we find table elements other than
+ // table, replace it with a list of any editable non-table content.
+ if (HTMLEditUtils::IsTableElementButNotTable(testNode)) {
+ int32_t j = i;
+ aOutArrayOfNodes.RemoveElementAt(i);
+ GetInnerContent(*testNode, aOutArrayOfNodes, &j, Lists::no);
+ }
+ }
+
+ // 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.
+ LookInsideDivBQandList(aOutArrayOfNodes);
+
+ return NS_OK;
+}
+
+void
+HTMLEditRules::LookInsideDivBQandList(
+ nsTArray<OwningNonNull<nsINode>>& aNodeArray)
+{
+ NS_ENSURE_TRUE(mHTMLEditor, );
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // 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 (aNodeArray.Length() != 1) {
+ return;
+ }
+
+ OwningNonNull<nsINode> curNode = aNodeArray[0];
+
+ while (curNode->IsHTMLElement(nsGkAtoms::div) ||
+ HTMLEditUtils::IsList(curNode) ||
+ curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
+ // Dive as long as there's only one child, and it's a list, div, blockquote
+ uint32_t numChildren = htmlEditor->CountEditableChildren(curNode);
+ if (numChildren != 1) {
+ break;
+ }
+
+ // Keep diving! XXX One would expect to dive into the one editable node.
+ nsCOMPtr<nsIContent> child = curNode->GetFirstChild();
+ if (!child->IsHTMLElement(nsGkAtoms::div) &&
+ !HTMLEditUtils::IsList(child) &&
+ !child->IsHTMLElement(nsGkAtoms::blockquote)) {
+ break;
+ }
+
+ // check editability XXX floppy moose
+ curNode = child;
+ }
+
+ // We've found innermost list/blockquote/div: replace the one node in the
+ // array with these nodes
+ aNodeArray.RemoveElementAt(0);
+ if (curNode->IsAnyOfHTMLElements(nsGkAtoms::div,
+ nsGkAtoms::blockquote)) {
+ int32_t j = 0;
+ GetInnerContent(*curNode, aNodeArray, &j, Lists::no, Tables::no);
+ return;
+ }
+
+ aNodeArray.AppendElement(*curNode);
+}
+
+void
+HTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement,
+ bool* aDT,
+ bool* aDD)
+{
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(aElement->IsHTMLElement(nsGkAtoms::dl));
+ MOZ_ASSERT(aDT);
+ MOZ_ASSERT(aDD);
+
+ *aDT = *aDD = false;
+ for (nsIContent* child = aElement->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::dt)) {
+ *aDT = true;
+ } else if (child->IsHTMLElement(nsGkAtoms::dd)) {
+ *aDD = true;
+ }
+ }
+}
+
+nsresult
+HTMLEditRules::GetParagraphFormatNodes(
+ nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
+ TouchContent aTouchContent)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+ NS_ENSURE_STATE(selection);
+
+ // Contruct a list of nodes to act on.
+ nsresult rv = GetNodesFromSelection(*selection, EditAction::makeBasicBlock,
+ outArrayOfNodes, aTouchContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Pre-process our list of nodes
+ for (int32_t i = outArrayOfNodes.Length() - 1; i >= 0; i--) {
+ OwningNonNull<nsINode> testNode = outArrayOfNodes[i];
+
+ // Remove all non-editable nodes. Leave them be.
+ if (!htmlEditor->IsEditable(testNode)) {
+ outArrayOfNodes.RemoveElementAt(i);
+ continue;
+ }
+
+ // Scan for table elements. If we find table elements other than table,
+ // replace it with a list of any editable non-table content. Ditto for
+ // list elements.
+ if (HTMLEditUtils::IsTableElement(testNode) ||
+ HTMLEditUtils::IsList(testNode) ||
+ HTMLEditUtils::IsListItem(testNode)) {
+ int32_t j = i;
+ outArrayOfNodes.RemoveElementAt(i);
+ GetInnerContent(testNode, outArrayOfNodes, &j);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::BustUpInlinesAtRangeEndpoints(RangeItem& item)
+{
+ bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset));
+
+ nsCOMPtr<nsIContent> endInline = GetHighestInlineParent(*item.endNode);
+
+ // if we have inline parents above range endpoints, split them
+ if (endInline && !isCollapsed) {
+ nsCOMPtr<nsINode> resultEndNode = endInline->GetParentNode();
+ NS_ENSURE_STATE(mHTMLEditor);
+ // item.endNode must be content if endInline isn't null
+ int32_t resultEndOffset =
+ mHTMLEditor->SplitNodeDeep(*endInline, *item.endNode->AsContent(),
+ item.endOffset,
+ EditorBase::EmptyContainers::no);
+ NS_ENSURE_TRUE(resultEndOffset != -1, NS_ERROR_FAILURE);
+ // reset range
+ item.endNode = resultEndNode;
+ item.endOffset = resultEndOffset;
+ }
+
+ nsCOMPtr<nsIContent> startInline = GetHighestInlineParent(*item.startNode);
+
+ if (startInline) {
+ nsCOMPtr<nsINode> resultStartNode = startInline->GetParentNode();
+ NS_ENSURE_STATE(mHTMLEditor);
+ int32_t resultStartOffset =
+ mHTMLEditor->SplitNodeDeep(*startInline, *item.startNode->AsContent(),
+ item.startOffset,
+ EditorBase::EmptyContainers::no);
+ NS_ENSURE_TRUE(resultStartOffset != -1, NS_ERROR_FAILURE);
+ // reset range
+ item.startNode = resultStartNode;
+ item.startOffset = resultStartOffset;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::BustUpInlinesAtBRs(
+ nsIContent& aNode,
+ nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // First build up a list of all the break nodes inside the inline container.
+ nsTArray<OwningNonNull<nsINode>> arrayOfBreaks;
+ BRNodeFunctor functor;
+ DOMIterator iter(aNode);
+ iter.AppendList(functor, arrayOfBreaks);
+
+ // If there aren't any breaks, just put inNode itself in the array
+ if (arrayOfBreaks.IsEmpty()) {
+ aOutArrayOfNodes.AppendElement(aNode);
+ return NS_OK;
+ }
+
+ // Else we need to bust up inNode along all the breaks
+ nsCOMPtr<nsINode> inlineParentNode = aNode.GetParentNode();
+ nsCOMPtr<nsIContent> splitDeepNode = &aNode;
+ nsCOMPtr<nsIContent> leftNode, rightNode;
+
+ for (uint32_t i = 0; i < arrayOfBreaks.Length(); i++) {
+ OwningNonNull<Element> breakNode = *arrayOfBreaks[i]->AsElement();
+ NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(breakNode->GetParent(), NS_ERROR_NULL_POINTER);
+ OwningNonNull<nsIContent> splitParentNode = *breakNode->GetParent();
+ int32_t splitOffset = splitParentNode->IndexOf(breakNode);
+
+ int32_t resultOffset =
+ htmlEditor->SplitNodeDeep(*splitDeepNode, splitParentNode, splitOffset,
+ HTMLEditor::EmptyContainers::yes,
+ getter_AddRefs(leftNode),
+ getter_AddRefs(rightNode));
+ NS_ENSURE_STATE(resultOffset != -1);
+
+ // Put left node in node list
+ if (leftNode) {
+ // Might not be a left node. A break might have been at the very
+ // beginning of inline container, in which case SplitNodeDeep would not
+ // actually split anything
+ aOutArrayOfNodes.AppendElement(*leftNode);
+ }
+ // Move break outside of container and also put in node list
+ nsresult rv =
+ htmlEditor->MoveNode(breakNode, inlineParentNode, resultOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aOutArrayOfNodes.AppendElement(*breakNode);
+
+ // Now rightNode becomes the new node to split
+ splitDeepNode = rightNode;
+ }
+ // Now tack on remaining rightNode, if any, to the list
+ if (rightNode) {
+ aOutArrayOfNodes.AppendElement(*rightNode);
+ }
+ return NS_OK;
+}
+
+nsIContent*
+HTMLEditRules::GetHighestInlineParent(nsINode& aNode)
+{
+ if (!aNode.IsContent() || IsBlockNode(aNode)) {
+ return nullptr;
+ }
+ OwningNonNull<nsIContent> node = *aNode.AsContent();
+
+ while (node->GetParent() && IsInlineNode(*node->GetParent())) {
+ node = *node->GetParent();
+ }
+ return node;
+}
+
+/**
+ * GetNodesFromPoint() constructs a list of nodes from a point that will be
+ * operated on.
+ */
+nsresult
+HTMLEditRules::GetNodesFromPoint(
+ EditorDOMPoint aPoint,
+ EditAction aOperation,
+ nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
+ TouchContent aTouchContent)
+{
+ NS_ENSURE_STATE(aPoint.node);
+ RefPtr<nsRange> range = new nsRange(aPoint.node);
+ nsresult rv = range->SetStart(aPoint.node, aPoint.offset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Expand the range to include adjacent inlines
+ PromoteRange(*range, aOperation);
+
+ // Make array of ranges
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+
+ // Stuff new opRange into array
+ arrayOfRanges.AppendElement(range);
+
+ // Use these ranges to contruct a list of nodes to act on
+ rv = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation,
+ aTouchContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * GetNodesFromSelection() constructs a list of nodes from the selection that
+ * will be operated on.
+ */
+nsresult
+HTMLEditRules::GetNodesFromSelection(
+ Selection& aSelection,
+ EditAction aOperation,
+ nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
+ TouchContent aTouchContent)
+{
+ // Promote selection ranges
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+ GetPromotedRanges(aSelection, arrayOfRanges, aOperation);
+
+ // Use these ranges to contruct a list of nodes to act on.
+ nsresult rv = GetNodesForOperation(arrayOfRanges, outArrayOfNodes,
+ aOperation, aTouchContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * MakeTransitionList() detects all the transitions in the array, where a
+ * transition means that adjacent nodes in the array don't have the same parent.
+ */
+void
+HTMLEditRules::MakeTransitionList(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
+ nsTArray<bool>& aTransitionArray)
+{
+ nsCOMPtr<nsINode> prevParent;
+
+ aTransitionArray.EnsureLengthAtLeast(aNodeArray.Length());
+ for (uint32_t i = 0; i < aNodeArray.Length(); i++) {
+ if (aNodeArray[i]->GetParentNode() != prevParent) {
+ // Different parents: transition point
+ aTransitionArray[i] = true;
+ } else {
+ // Same parents: these nodes grew up together
+ aTransitionArray[i] = false;
+ }
+ prevParent = aNodeArray[i]->GetParentNode();
+ }
+}
+
+/**
+ * If aNode is the descendant of a listitem, return that li. But table element
+ * boundaries are stoppers on the search. Also stops on the active editor host
+ * (contenteditable). Also test if aNode is an li itself.
+ */
+Element*
+HTMLEditRules::IsInListItem(nsINode* aNode)
+{
+ NS_ENSURE_TRUE(aNode, nullptr);
+ if (HTMLEditUtils::IsListItem(aNode)) {
+ return aNode->AsElement();
+ }
+
+ Element* parent = aNode->GetParentElement();
+ while (parent &&
+ mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
+ !HTMLEditUtils::IsTableElement(parent)) {
+ if (HTMLEditUtils::IsListItem(parent)) {
+ return parent;
+ }
+ parent = parent->GetParentElement();
+ }
+ return nullptr;
+}
+
+/**
+ * ReturnInHeader: do the right thing for returns pressed in headers
+ */
+nsresult
+HTMLEditRules::ReturnInHeader(Selection& aSelection,
+ Element& aHeader,
+ nsINode& aNode,
+ int32_t aOffset)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Remember where the header is
+ nsCOMPtr<nsINode> headerParent = aHeader.GetParentNode();
+ int32_t offset = headerParent ? headerParent->IndexOf(&aHeader) : -1;
+
+ // Get ws code to adjust any ws
+ nsCOMPtr<nsINode> node = &aNode;
+ nsresult rv = WSRunObject::PrepareToSplitAcrossBlocks(htmlEditor,
+ address_of(node),
+ &aOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Split the header
+ NS_ENSURE_STATE(node->IsContent());
+ htmlEditor->SplitNodeDeep(aHeader, *node->AsContent(), aOffset);
+
+ // If the left-hand heading is empty, put a mozbr in it
+ nsCOMPtr<nsIContent> prevItem = htmlEditor->GetPriorHTMLSibling(&aHeader);
+ if (prevItem && HTMLEditUtils::IsHeader(*prevItem)) {
+ bool isEmptyNode;
+ rv = htmlEditor->IsEmptyNode(prevItem, &isEmptyNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEmptyNode) {
+ rv = CreateMozBR(prevItem->AsDOMNode(), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // If the new (righthand) header node is empty, delete it
+ bool isEmpty;
+ rv = IsEmptyBlock(aHeader, &isEmpty, MozBRCounts::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEmpty) {
+ rv = htmlEditor->DeleteNode(&aHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Layout tells the caret to blink in a weird place if we don't place a
+ // break after the header.
+ nsCOMPtr<nsIContent> sibling =
+ htmlEditor->GetNextHTMLSibling(headerParent, offset + 1);
+ if (!sibling || !sibling->IsHTMLElement(nsGkAtoms::br)) {
+ ClearCachedStyles();
+ htmlEditor->mTypeInState->ClearAllProps();
+
+ // Create a paragraph
+ nsCOMPtr<Element> pNode =
+ htmlEditor->CreateNode(nsGkAtoms::p, headerParent, offset + 1);
+ NS_ENSURE_STATE(pNode);
+
+ // Append a <br> to it
+ nsCOMPtr<Element> brNode = htmlEditor->CreateBR(pNode, 0);
+ NS_ENSURE_STATE(brNode);
+
+ // Set selection to before the break
+ rv = aSelection.Collapse(pNode, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ headerParent = sibling->GetParentNode();
+ offset = headerParent ? headerParent->IndexOf(sibling) : -1;
+ // Put selection after break
+ rv = aSelection.Collapse(headerParent, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Put selection at front of righthand heading
+ rv = aSelection.Collapse(&aHeader, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+/**
+ * ReturnInParagraph() does the right thing for returns pressed in paragraphs.
+ */
+nsresult
+HTMLEditRules::ReturnInParagraph(Selection* aSelection,
+ nsIDOMNode* aPara,
+ nsIDOMNode* aNode,
+ int32_t aOffset,
+ bool* aCancel,
+ bool* aHandled)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ if (!aSelection || !aPara || !node || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aCancel = false;
+ *aHandled = false;
+
+ int32_t offset;
+ nsCOMPtr<nsINode> parent = EditorBase::GetNodeLocation(node, &offset);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph();
+
+ bool newBRneeded = false;
+ bool newSelNode = false;
+ nsCOMPtr<nsIContent> sibling;
+ nsCOMPtr<nsIDOMNode> selNode = aNode;
+ int32_t selOffset = aOffset;
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (aNode == aPara && doesCRCreateNewP) {
+ // we are at the edges of the block, newBRneeded not needed!
+ sibling = node->AsContent();
+ } else if (mHTMLEditor->IsTextNode(aNode)) {
+ nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode);
+ uint32_t strLength;
+ nsresult rv = textNode->GetLength(&strLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // at beginning of text node?
+ if (!aOffset) {
+ // is there a BR prior to it?
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetPriorHTMLSibling(node);
+ if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) ||
+ TextEditUtils::HasMozAttr(GetAsDOMNode(sibling))) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ newBRneeded = true;
+ }
+ } else if (aOffset == (int32_t)strLength) {
+ // we're at the end of text node...
+ // is there a BR after to it?
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->GetNextHTMLSibling(node);
+ if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) ||
+ TextEditUtils::HasMozAttr(GetAsDOMNode(sibling))) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ newBRneeded = true;
+ offset++;
+ }
+ } else {
+ if (doesCRCreateNewP) {
+ nsCOMPtr<nsIDOMNode> tmp;
+ rv = mTextEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+ selNode = tmp;
+ }
+
+ newBRneeded = true;
+ offset++;
+ }
+ } else {
+ // not in a text node.
+ // is there a BR prior to it?
+ nsCOMPtr<nsIContent> nearNode;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nearNode = mHTMLEditor->GetPriorHTMLNode(node, aOffset);
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) ||
+ TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
+ // is there a BR after it?
+ NS_ENSURE_STATE(mHTMLEditor);
+ nearNode = mHTMLEditor->GetNextHTMLNode(node, aOffset);
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) ||
+ TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
+ newBRneeded = true;
+ parent = node;
+ offset = aOffset;
+ newSelNode = true;
+ }
+ }
+ if (!newBRneeded) {
+ sibling = nearNode;
+ }
+ }
+ if (newBRneeded) {
+ // if CR does not create a new P, default to BR creation
+ NS_ENSURE_TRUE(doesCRCreateNewP, NS_OK);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ sibling = mHTMLEditor->CreateBR(parent, offset);
+ if (newSelNode) {
+ // We split the parent after the br we've just inserted.
+ selNode = GetAsDOMNode(parent);
+ selOffset = offset + 1;
+ }
+ }
+ *aHandled = true;
+ return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &selOffset);
+}
+
+/**
+ * SplitParagraph() splits a paragraph at selection point, possibly deleting a
+ * br.
+ */
+nsresult
+HTMLEditRules::SplitParagraph(nsIDOMNode *aPara,
+ nsIContent* aBRNode,
+ Selection* aSelection,
+ nsCOMPtr<nsIDOMNode>* aSelNode,
+ int32_t* aOffset)
+{
+ nsCOMPtr<Element> para = do_QueryInterface(aPara);
+ NS_ENSURE_TRUE(para && aBRNode && aSelNode && *aSelNode && aOffset &&
+ aSelection, NS_ERROR_NULL_POINTER);
+
+ // split para
+ // get ws code to adjust any ws
+ nsCOMPtr<nsIContent> leftPara, rightPara;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsINode> selNode(do_QueryInterface(*aSelNode));
+ nsresult rv =
+ WSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor,
+ address_of(selNode), aOffset);
+ // XXX When it fails, why do we need to return selection node? (Why can the
+ // caller trust the result even when it returns error?)
+ *aSelNode = GetAsDOMNode(selNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // split the paragraph
+ NS_ENSURE_STATE(mHTMLEditor);
+ NS_ENSURE_STATE(selNode->IsContent());
+ mHTMLEditor->SplitNodeDeep(*para, *selNode->AsContent(), *aOffset,
+ HTMLEditor::EmptyContainers::yes,
+ getter_AddRefs(leftPara),
+ getter_AddRefs(rightPara));
+ // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p)
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (mHTMLEditor->IsVisBreak(aBRNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->DeleteNode(aBRNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // remove ID attribute on the paragraph we just created
+ nsCOMPtr<nsIDOMElement> rightElt = do_QueryInterface(rightPara);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check both halves of para to see if we need mozBR
+ rv = InsertMozBRIfNeeded(*leftPara);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InsertMozBRIfNeeded(*rightPara);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // selection to beginning of right hand para;
+ // look inside any containers that are up front.
+ nsCOMPtr<nsINode> rightParaNode = do_QueryInterface(rightPara);
+ NS_ENSURE_STATE(mHTMLEditor && rightParaNode);
+ nsCOMPtr<nsIDOMNode> child =
+ GetAsDOMNode(mHTMLEditor->GetLeftmostChild(rightParaNode, true));
+ if (mHTMLEditor->IsTextNode(child) ||
+ mHTMLEditor->IsContainer(child)) {
+ aSelection->Collapse(child,0);
+ } else {
+ int32_t offset;
+ nsCOMPtr<nsIDOMNode> parent = EditorBase::GetNodeLocation(child, &offset);
+ aSelection->Collapse(parent,offset);
+ }
+ return NS_OK;
+}
+
+/**
+ * ReturnInListItem: do the right thing for returns pressed in list items
+ */
+nsresult
+HTMLEditRules::ReturnInListItem(Selection& aSelection,
+ Element& aListItem,
+ nsINode& aNode,
+ int32_t aOffset)
+{
+ MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItem));
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Get the item parent and the active editing host.
+ nsCOMPtr<Element> root = htmlEditor->GetActiveEditingHost();
+
+ nsCOMPtr<Element> list = aListItem.GetParentElement();
+ int32_t itemOffset = list ? list->IndexOf(&aListItem) : -1;
+
+ // 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.
+ bool isEmpty;
+ nsresult rv = IsEmptyBlock(aListItem, &isEmpty, MozBRCounts::no);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEmpty && root != list && mReturnInEmptyLIKillsList) {
+ // Get the list offset now -- before we might eventually split the list
+ nsCOMPtr<nsINode> listParent = list->GetParentNode();
+ int32_t offset = listParent ? listParent->IndexOf(list) : -1;
+
+ // Are we the last list item in the list?
+ bool isLast;
+ rv = htmlEditor->IsLastEditableChild(aListItem.AsDOMNode(), &isLast);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isLast) {
+ // We need to split the list!
+ ErrorResult rv;
+ htmlEditor->SplitNode(*list, itemOffset, rv);
+ NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
+ }
+
+ // Are we in a sublist?
+ if (HTMLEditUtils::IsList(listParent)) {
+ // If so, move item out of this list and into the grandparent list
+ rv = htmlEditor->MoveNode(&aListItem, listParent, offset + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aSelection.Collapse(&aListItem, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Otherwise kill this item
+ rv = htmlEditor->DeleteNode(&aListItem);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Time to insert a paragraph
+ nsCOMPtr<Element> pNode =
+ htmlEditor->CreateNode(nsGkAtoms::p, listParent, offset + 1);
+ NS_ENSURE_STATE(pNode);
+
+ // Append a <br> to it
+ nsCOMPtr<Element> brNode = htmlEditor->CreateBR(pNode, 0);
+ NS_ENSURE_STATE(brNode);
+
+ // Set selection to before the break
+ rv = aSelection.Collapse(pNode, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+
+ // Else we want a new list item at the same list level. Get ws code to
+ // adjust any ws.
+ nsCOMPtr<nsINode> selNode = &aNode;
+ rv = WSRunObject::PrepareToSplitAcrossBlocks(htmlEditor,
+ address_of(selNode), &aOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Now split list item
+ NS_ENSURE_STATE(selNode->IsContent());
+ htmlEditor->SplitNodeDeep(aListItem, *selNode->AsContent(), aOffset);
+
+ // 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 = htmlEditor->GetPriorHTMLSibling(&aListItem);
+ if (prevItem && HTMLEditUtils::IsListItem(prevItem)) {
+ bool isEmptyNode;
+ rv = htmlEditor->IsEmptyNode(prevItem, &isEmptyNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEmptyNode) {
+ rv = CreateMozBR(prevItem->AsDOMNode(), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = htmlEditor->IsEmptyNode(&aListItem, &isEmptyNode, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEmptyNode) {
+ nsCOMPtr<nsIAtom> nodeAtom = aListItem.NodeInfo()->NameAtom();
+ if (nodeAtom == nsGkAtoms::dd || nodeAtom == nsGkAtoms::dt) {
+ nsCOMPtr<nsINode> list = aListItem.GetParentNode();
+ int32_t itemOffset = list ? list->IndexOf(&aListItem) : -1;
+
+ nsIAtom* listAtom = nodeAtom == nsGkAtoms::dt ? nsGkAtoms::dd
+ : nsGkAtoms::dt;
+ nsCOMPtr<Element> newListItem =
+ htmlEditor->CreateNode(listAtom, list, itemOffset + 1);
+ NS_ENSURE_STATE(newListItem);
+ rv = mTextEditor->DeleteNode(&aListItem);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aSelection.Collapse(newListItem, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<Element> brNode;
+ rv = htmlEditor->CopyLastEditableChildStyles(GetAsDOMNode(prevItem),
+ GetAsDOMNode(&aListItem),
+ getter_AddRefs(brNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (brNode) {
+ nsCOMPtr<nsINode> brParent = brNode->GetParentNode();
+ int32_t offset = brParent ? brParent->IndexOf(brNode) : -1;
+ rv = aSelection.Collapse(brParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ } else {
+ WSRunObject wsObj(htmlEditor, &aListItem, 0);
+ nsCOMPtr<nsINode> visNode;
+ int32_t visOffset = 0;
+ WSType wsType;
+ wsObj.NextVisibleNode(&aListItem, 0, address_of(visNode),
+ &visOffset, &wsType);
+ if (wsType == WSType::special || wsType == WSType::br ||
+ visNode->IsHTMLElement(nsGkAtoms::hr)) {
+ nsCOMPtr<nsINode> parent = visNode->GetParentNode();
+ int32_t offset = parent ? parent->IndexOf(visNode) : -1;
+ rv = aSelection.Collapse(parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ } else {
+ rv = aSelection.Collapse(visNode, visOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ }
+ }
+ }
+ rv = aSelection.Collapse(&aListItem, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+/**
+ * MakeBlockquote() puts the list of nodes into one or more blockquotes.
+ */
+nsresult
+HTMLEditRules::MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray)
+{
+ // 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).
+ nsCOMPtr<Element> curBlock;
+ nsCOMPtr<nsINode> prevParent;
+
+ for (auto& curNode : aNodeArray) {
+ // Get the node to act on, and its location
+ NS_ENSURE_STATE(curNode->IsContent());
+
+ // If the node is a table element or list item, dive inside
+ if (HTMLEditUtils::IsTableElementButNotTable(curNode) ||
+ HTMLEditUtils::IsListItem(curNode)) {
+ // Forget any previous block
+ curBlock = nullptr;
+ // Recursion time
+ nsTArray<OwningNonNull<nsINode>> childArray;
+ GetChildNodesForOperation(*curNode, childArray);
+ nsresult rv = MakeBlockquote(childArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the node has different parent than previous node, further nodes in a
+ // new parent
+ if (prevParent) {
+ if (prevParent != curNode->GetParentNode()) {
+ // Forget any previous blockquote node we were using
+ curBlock = nullptr;
+ prevParent = curNode->GetParentNode();
+ }
+ } else {
+ prevParent = curNode->GetParentNode();
+ }
+
+ // If no curBlock, make one
+ if (!curBlock) {
+ nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+ nsresult rv = SplitAsNeeded(*nsGkAtoms::blockquote, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ curBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent,
+ offset);
+ NS_ENSURE_STATE(curBlock);
+ // remember our new block for postprocessing
+ mNewBlock = curBlock;
+ // note: doesn't matter if we set mNewBlock multiple times.
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->MoveNode(curNode->AsContent(), curBlock, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+/**
+ * RemoveBlockStyle() makes the nodes have no special block type.
+ */
+nsresult
+HTMLEditRules::RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // 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> curBlock;
+ nsCOMPtr<nsIContent> firstNode, lastNode;
+ for (auto& curNode : aNodeArray) {
+ // If curNode is a address, p, header, address, or pre, remove it
+ if (HTMLEditUtils::IsFormatNode(curNode)) {
+ // Process any partial progress saved
+ if (curBlock) {
+ nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstNode = lastNode = curBlock = nullptr;
+ }
+ // Remove current block
+ nsresult rv = htmlEditor->RemoveBlockContainer(*curNode->AsContent());
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::table,
+ nsGkAtoms::tr,
+ nsGkAtoms::tbody,
+ nsGkAtoms::td,
+ nsGkAtoms::li,
+ nsGkAtoms::blockquote,
+ nsGkAtoms::div) ||
+ HTMLEditUtils::IsList(curNode)) {
+ // Process any partial progress saved
+ if (curBlock) {
+ nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstNode = lastNode = curBlock = nullptr;
+ }
+ // Recursion time
+ nsTArray<OwningNonNull<nsINode>> childArray;
+ GetChildNodesForOperation(*curNode, childArray);
+ nsresult rv = RemoveBlockStyle(childArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (IsInlineNode(curNode)) {
+ if (curBlock) {
+ // If so, is this node a descendant?
+ if (EditorUtils::IsDescendantOf(curNode, curBlock)) {
+ // Then we don't need to do anything different for this node
+ lastNode = curNode->AsContent();
+ continue;
+ }
+ // Otherwise, we have progressed beyond end of curBlock, so let's
+ // handle it now. We need to remove the portion of curBlock that
+ // contains [firstNode - lastNode].
+ nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstNode = lastNode = curBlock = nullptr;
+ // Fall out and handle curNode
+ }
+ curBlock = htmlEditor->GetBlockNodeParent(curNode);
+ if (curBlock && HTMLEditUtils::IsFormatNode(curBlock)) {
+ firstNode = lastNode = curNode->AsContent();
+ } else {
+ // Not a block kind that we care about.
+ curBlock = nullptr;
+ }
+ } else if (curBlock) {
+ // Some node that is already sans block style. Skip over it and process
+ // any partial progress saved.
+ nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstNode = lastNode = curBlock = nullptr;
+ }
+ }
+ // Process any partial progress saved
+ if (curBlock) {
+ nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstNode = lastNode = curBlock = nullptr;
+ }
+ return NS_OK;
+}
+
+/**
+ * ApplyBlockStyle() does whatever it takes to make the list of nodes into one
+ * or more blocks of type aBlockTag.
+ */
+nsresult
+HTMLEditRules::ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray,
+ nsIAtom& aBlockTag)
+{
+ // 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...
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Remove all non-editable nodes. Leave them be.
+ for (int32_t i = aNodeArray.Length() - 1; i >= 0; i--) {
+ if (!htmlEditor->IsEditable(aNodeArray[i])) {
+ aNodeArray.RemoveElementAt(i);
+ }
+ }
+
+ nsCOMPtr<Element> newBlock;
+
+ nsCOMPtr<Element> curBlock;
+ for (auto& curNode : aNodeArray) {
+ nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // Is it already the right kind of block?
+ if (curNode->IsHTMLElement(&aBlockTag)) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ // Do nothing to this block
+ continue;
+ }
+
+ // If curNode 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(curNode) ||
+ HTMLEditUtils::IsFormatNode(curNode)) {
+ // Forget any previous block used for previous inline nodes
+ curBlock = nullptr;
+ newBlock = htmlEditor->ReplaceContainer(curNode->AsElement(),
+ &aBlockTag, nullptr, nullptr,
+ EditorBase::eCloneAttributes);
+ NS_ENSURE_STATE(newBlock);
+ } else if (HTMLEditUtils::IsTable(curNode) ||
+ HTMLEditUtils::IsList(curNode) ||
+ curNode->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
+ nsTArray<OwningNonNull<nsINode>> childArray;
+ GetChildNodesForOperation(*curNode, childArray);
+ if (!childArray.IsEmpty()) {
+ nsresult rv = ApplyBlockStyle(childArray, aBlockTag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Make sure we can put a block here
+ nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<Element> theBlock =
+ htmlEditor->CreateNode(&aBlockTag, curParent, offset);
+ NS_ENSURE_STATE(theBlock);
+ // Remember our new block for postprocessing
+ mNewBlock = theBlock;
+ }
+ } else if (curNode->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;
+ nsresult rv = htmlEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // The break is the first (or even only) node we encountered. Create a
+ // block for it.
+ nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curBlock = htmlEditor->CreateNode(&aBlockTag, curParent, offset);
+ NS_ENSURE_STATE(curBlock);
+ // Remember our new block for postprocessing
+ mNewBlock = curBlock;
+ // Note: doesn't matter if we set mNewBlock multiple times.
+ rv = htmlEditor->MoveNode(curNode->AsContent(), curBlock, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else if (IsInlineNode(curNode)) {
+ // If curNode 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 curNode is a non editable, drop it if we are going to <pre>.
+ if (&aBlockTag == nsGkAtoms::pre && !htmlEditor->IsEditable(curNode)) {
+ // Do nothing to this block
+ continue;
+ }
+
+ // If no curBlock, make one
+ if (!curBlock) {
+ nsresult rv = SplitAsNeeded(aBlockTag, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curBlock = htmlEditor->CreateNode(&aBlockTag, curParent, offset);
+ NS_ENSURE_STATE(curBlock);
+ // Remember our new block for postprocessing
+ mNewBlock = curBlock;
+ // Note: doesn't matter if we set mNewBlock multiple times.
+ }
+
+ // XXX If curNode 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.
+ nsresult rv = htmlEditor->MoveNode(curNode->AsContent(), curBlock, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Given a tag name, split inOutParent up to the point where we can insert the
+ * tag. Adjust inOutParent and inOutOffset to point to new location for tag.
+ */
+nsresult
+HTMLEditRules::SplitAsNeeded(nsIAtom& aTag,
+ OwningNonNull<nsINode>& aInOutParent,
+ int32_t& aInOutOffset)
+{
+ // XXX Is there a better way to do this?
+ nsCOMPtr<nsINode> parent = aInOutParent.forget();
+ nsresult rv = SplitAsNeeded(aTag, parent, aInOutOffset);
+ aInOutParent = parent.forget();
+ return rv;
+}
+
+nsresult
+HTMLEditRules::SplitAsNeeded(nsIAtom& aTag,
+ nsCOMPtr<nsINode>& inOutParent,
+ int32_t& inOutOffset)
+{
+ NS_ENSURE_TRUE(inOutParent, NS_ERROR_NULL_POINTER);
+
+ // Check that we have a place that can legally contain the tag
+ nsCOMPtr<nsINode> tagParent, splitNode;
+ for (nsCOMPtr<nsINode> parent = inOutParent; parent;
+ parent = parent->GetParentNode()) {
+ // Sniffing up the parent tree until we find a legal place for the block
+
+ // Don't leave the active editing host
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsDescendantOfEditorRoot(parent)) {
+ // XXX Why do we need to check mHTMLEditor again here?
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (parent != mHTMLEditor->GetActiveEditingHost()) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (mHTMLEditor->CanContainTag(*parent, aTag)) {
+ // Success
+ tagParent = parent;
+ break;
+ }
+
+ splitNode = parent;
+ }
+ if (!tagParent) {
+ // Could not find a place to build tag!
+ return NS_ERROR_FAILURE;
+ }
+ if (splitNode && splitNode->IsContent() && inOutParent->IsContent()) {
+ // We found a place for block, but above inOutParent. We need to split.
+ NS_ENSURE_STATE(mHTMLEditor);
+ int32_t offset = mHTMLEditor->SplitNodeDeep(*splitNode->AsContent(),
+ *inOutParent->AsContent(),
+ inOutOffset);
+ NS_ENSURE_STATE(offset != -1);
+ inOutParent = tagParent;
+ inOutOffset = offset;
+ }
+ return NS_OK;
+}
+
+/**
+ * JoinNodesSmart: Join two nodes, doing whatever makes sense for their
+ * children (which often means joining them, too). aNodeLeft & aNodeRight must
+ * be same type of node.
+ *
+ * Returns the point where they're merged, or (nullptr, -1) on failure.
+ */
+EditorDOMPoint
+HTMLEditRules::JoinNodesSmart(nsIContent& aNodeLeft,
+ nsIContent& aNodeRight)
+{
+ // Caller responsible for left and right node being the same type
+ nsCOMPtr<nsINode> parent = aNodeLeft.GetParentNode();
+ NS_ENSURE_TRUE(parent, EditorDOMPoint());
+ int32_t parOffset = parent->IndexOf(&aNodeLeft);
+ nsCOMPtr<nsINode> rightParent = aNodeRight.GetParentNode();
+
+ // If they don't have the same parent, first move the right node to after the
+ // left one
+ if (parent != rightParent) {
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ nsresult rv = mHTMLEditor->MoveNode(&aNodeRight, parent, parOffset);
+ NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
+ }
+
+ EditorDOMPoint ret(&aNodeRight, aNodeLeft.Length());
+
+ // Separate join rules for differing blocks
+ if (HTMLEditUtils::IsList(&aNodeLeft) || aNodeLeft.GetAsText()) {
+ // For lists, merge shallow (wouldn't want to combine list items)
+ nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
+ NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
+ return ret;
+ }
+
+ // Remember the last left child, and first right child
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ nsCOMPtr<nsIContent> lastLeft = mHTMLEditor->GetLastEditableChild(aNodeLeft);
+ NS_ENSURE_TRUE(lastLeft, EditorDOMPoint());
+
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ nsCOMPtr<nsIContent> firstRight = mHTMLEditor->GetFirstEditableChild(aNodeRight);
+ NS_ENSURE_TRUE(firstRight, EditorDOMPoint());
+
+ // For list items, divs, etc., merge smart
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
+ NS_ENSURE_SUCCESS(rv, EditorDOMPoint());
+
+ if (lastLeft && firstRight && mHTMLEditor &&
+ mHTMLEditor->AreNodesSameType(lastLeft, firstRight) &&
+ (lastLeft->GetAsText() || !mHTMLEditor ||
+ (lastLeft->IsElement() && firstRight->IsElement() &&
+ mHTMLEditor->mCSSEditUtils->ElementsSameStyle(lastLeft->AsElement(),
+ firstRight->AsElement())))) {
+ NS_ENSURE_TRUE(mHTMLEditor, EditorDOMPoint());
+ return JoinNodesSmart(*lastLeft, *firstRight);
+ }
+ return ret;
+}
+
+Element*
+HTMLEditRules::GetTopEnclosingMailCite(nsINode& aNode)
+{
+ nsCOMPtr<Element> ret;
+
+ for (nsCOMPtr<nsINode> node = &aNode; node; node = node->GetParentNode()) {
+ if ((IsPlaintextEditor() && node->IsHTMLElement(nsGkAtoms::pre)) ||
+ HTMLEditUtils::IsMailCite(node)) {
+ ret = node->AsElement();
+ }
+ if (node->IsHTMLElement(nsGkAtoms::body)) {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+nsresult
+HTMLEditRules::CacheInlineStyles(nsIDOMNode* aNode)
+{
+ NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ bool useCSS = mHTMLEditor->IsCSSEnabled();
+
+ for (int32_t j = 0; j < SIZE_STYLE_TABLE; ++j) {
+ // If type-in state is set, don't intervene
+ bool typeInSet, unused;
+ if (NS_WARN_IF(!mHTMLEditor)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mHTMLEditor->mTypeInState->GetTypingState(typeInSet, unused,
+ mCachedStyles[j].tag, mCachedStyles[j].attr, nullptr);
+ if (typeInSet) {
+ continue;
+ }
+
+ bool isSet = false;
+ nsAutoString outValue;
+ // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
+ if (!useCSS || (mCachedStyles[j].tag == nsGkAtoms::font &&
+ mCachedStyles[j].attr.EqualsLiteral("size"))) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag,
+ &(mCachedStyles[j].attr), nullptr,
+ isSet, &outValue);
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode,
+ mCachedStyles[j].tag, &(mCachedStyles[j].attr), isSet, outValue,
+ CSSEditUtils::eComputed);
+ }
+ if (isSet) {
+ mCachedStyles[j].mPresent = true;
+ mCachedStyles[j].value.Assign(outValue);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::ReapplyCachedStyles()
+{
+ // 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.
+
+ // remember if we are in css mode
+ NS_ENSURE_STATE(mHTMLEditor);
+ bool useCSS = mHTMLEditor->IsCSSEnabled();
+
+ // get selection point; if it doesn't exist, we have nothing to do
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ if (!selection) {
+ // If the document is removed from its parent document during executing an
+ // editor operation with DOMMutationEvent or something, there may be no
+ // selection.
+ return NS_OK;
+ }
+ if (!selection->RangeCount()) {
+ // Nothing to do
+ return NS_OK;
+ }
+ nsCOMPtr<nsIContent> selNode =
+ do_QueryInterface(selection->GetRangeAt(0)->GetStartParent());
+ if (!selNode) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ for (int32_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
+ if (mCachedStyles[i].mPresent) {
+ bool bFirst, bAny, bAll;
+ bFirst = bAny = bAll = false;
+
+ nsAutoString curValue;
+ if (useCSS) {
+ // check computed style first in css case
+ NS_ENSURE_STATE(mHTMLEditor);
+ bAny = mHTMLEditor->mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(
+ selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue,
+ CSSEditUtils::eComputed);
+ }
+ if (!bAny) {
+ // then check typeinstate and html style
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetInlinePropertyBase(*mCachedStyles[i].tag,
+ &(mCachedStyles[i].attr),
+ &(mCachedStyles[i].value),
+ &bFirst, &bAny, &bAll,
+ &curValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // this style has disappeared through deletion. Add to our typeinstate:
+ if (!bAny || IsStyleCachePreservingAction(mTheAction)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
+ mCachedStyles[i].attr,
+ mCachedStyles[i].value);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLEditRules::ClearCachedStyles()
+{
+ // clear the mPresent bits in mCachedStyles array
+ for (uint32_t j = 0; j < SIZE_STYLE_TABLE; j++) {
+ mCachedStyles[j].mPresent = false;
+ mCachedStyles[j].value.Truncate();
+ }
+}
+
+void
+HTMLEditRules::AdjustSpecialBreaks()
+{
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+
+ // Gather list of empty nodes
+ nsTArray<OwningNonNull<nsINode>> nodeArray;
+ EmptyEditableFunctor functor(mHTMLEditor);
+ DOMIterator iter;
+ if (NS_WARN_IF(NS_FAILED(iter.Init(*mDocChangeRange)))) {
+ return;
+ }
+ iter.AppendList(functor, nodeArray);
+
+ // Put moz-br's into these empty li's and td's
+ for (auto& node : nodeArray) {
+ // 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.
+ nsresult rv = CreateMozBR(node->AsDOMNode(), (int32_t)node->Length());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ }
+}
+
+nsresult
+HTMLEditRules::AdjustWhitespace(Selection* aSelection)
+{
+ // get selection point
+ nsCOMPtr<nsIDOMNode> selNode;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ask whitespace object to tweak nbsp's
+ NS_ENSURE_STATE(mHTMLEditor);
+ return WSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
+}
+
+nsresult
+HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
+{
+ NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
+ if (!aSelection->Collapsed()) {
+ return NS_OK;
+ }
+
+ // get the (collapsed) selection location
+ nsCOMPtr<nsIDOMNode> selNode, temp;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ temp = selNode;
+
+ // use ranges and sRangeHelper to compare sel point to new block
+ nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
+ NS_ENSURE_STATE(node);
+ RefPtr<nsRange> range = new nsRange(node);
+ rv = range->SetStart(selNode, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = range->SetEnd(selNode, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIContent> block = mNewBlock.get();
+ NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
+ bool nodeBefore, nodeAfter;
+ rv = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodeBefore && nodeAfter) {
+ return NS_OK; // selection is inside block
+ } else if (nodeBefore) {
+ // selection is after block. put at end of block.
+ nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
+ NS_ENSURE_STATE(mHTMLEditor);
+ tmp = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*block));
+ uint32_t endPoint;
+ if (mHTMLEditor->IsTextNode(tmp) ||
+ mHTMLEditor->IsContainer(tmp)) {
+ rv = EditorBase::GetLengthOfDOMNode(tmp, endPoint);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ tmp = EditorBase::GetNodeLocation(tmp, (int32_t*)&endPoint);
+ endPoint++; // want to be after this node
+ }
+ return aSelection->Collapse(tmp, (int32_t)endPoint);
+ } else {
+ // selection is before block. put at start of block.
+ nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
+ NS_ENSURE_STATE(mHTMLEditor);
+ tmp = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*block));
+ int32_t offset;
+ if (mHTMLEditor->IsTextNode(tmp) ||
+ mHTMLEditor->IsContainer(tmp)) {
+ tmp = EditorBase::GetNodeLocation(tmp, &offset);
+ }
+ return aSelection->Collapse(tmp, 0);
+ }
+}
+
+void
+HTMLEditRules::CheckInterlinePosition(Selection& aSelection)
+{
+ // If the selection isn't collapsed, do nothing.
+ if (!aSelection.Collapsed()) {
+ return;
+ }
+
+ NS_ENSURE_TRUE_VOID(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Get the (collapsed) selection location
+ NS_ENSURE_TRUE_VOID(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> selNode = *aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t selOffset = aSelection.GetRangeAt(0)->StartOffset();
+
+ // 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.
+ nsCOMPtr<nsIContent> node =
+ htmlEditor->GetPriorHTMLNode(selNode, selOffset, true);
+ if (node && node->IsHTMLElement(nsGkAtoms::br)) {
+ aSelection.SetInterlinePosition(true);
+ return;
+ }
+
+ // Are we after a block? If so try set caret to following content
+ node = htmlEditor->GetPriorHTMLSibling(selNode, selOffset);
+ if (node && IsBlockNode(*node)) {
+ aSelection.SetInterlinePosition(true);
+ return;
+ }
+
+ // Are we before a block? If so try set caret to prior content
+ node = htmlEditor->GetNextHTMLSibling(selNode, selOffset);
+ if (node && IsBlockNode(*node)) {
+ aSelection.SetInterlinePosition(false);
+ }
+}
+
+nsresult
+HTMLEditRules::AdjustSelection(Selection* aSelection,
+ nsIEditor::EDirection aAction)
+{
+ NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
+
+ // if the selection isn't collapsed, do nothing.
+ // moose: one thing to do instead is check for the case of
+ // only a single break selected, and collapse it. Good thing? Beats me.
+ if (!aSelection->Collapsed()) {
+ return NS_OK;
+ }
+
+ // get the (collapsed) selection location
+ nsCOMPtr<nsINode> selNode, temp;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(aSelection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ temp = selNode;
+
+ // are we in an editable node?
+ NS_ENSURE_STATE(mHTMLEditor);
+ while (!mHTMLEditor->IsEditable(selNode)) {
+ // scan up the tree until we find an editable place to be
+ selNode = EditorBase::GetNodeLocation(temp, &selOffset);
+ NS_ENSURE_TRUE(selNode, NS_ERROR_FAILURE);
+ temp = selNode;
+ NS_ENSURE_STATE(mHTMLEditor);
+ }
+
+ // make sure we aren't in an empty block - user will see no cursor. If this
+ // is happening, put a <br> in the block if allowed.
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> theblock = mHTMLEditor->GetBlock(*selNode);
+
+ if (theblock && mHTMLEditor->IsEditable(theblock)) {
+ bool bIsEmptyNode;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // check if br can go into the destination node
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (bIsEmptyNode && mHTMLEditor->CanContainTag(*selNode, *nsGkAtoms::br)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> rootNode = mHTMLEditor->GetRoot();
+ NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
+ if (selNode == rootNode) {
+ // Our root node is completely empty. Don't add a <br> here.
+ // AfterEditInner() will add one for us when it calls
+ // CreateBogusNodeIfNeeded()!
+ return NS_OK;
+ }
+
+ // we know we can skip the rest of this routine given the cirumstance
+ return CreateMozBR(GetAsDOMNode(selNode), selOffset);
+ }
+ }
+
+ // are we in a text node?
+ nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode);
+ if (textNode)
+ return NS_OK; // we LIKE it when we are in a text node. that RULZ
+
+ // do we need to insert a special mozBR? 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
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIContent> nearNode =
+ mHTMLEditor->GetPriorHTMLNode(selNode, selOffset);
+ if (nearNode) {
+ // is nearNode also a descendant of same block?
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> block = mHTMLEditor->GetBlock(*selNode);
+ nsCOMPtr<Element> nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode);
+ if (block && block == nearBlock) {
+ if (nearNode && TextEditUtils::IsBreak(nearNode)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsVisBreak(nearNode)) {
+ // need to insert special moz BR. Why? Because if we don't
+ // the user will see no new line for the break. Also, things
+ // like table cells won't grow in height.
+ nsCOMPtr<nsIDOMNode> brNode;
+ rv = CreateMozBR(GetAsDOMNode(selNode), selOffset,
+ getter_AddRefs(brNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDOMNode> brParent =
+ EditorBase::GetNodeLocation(brNode, &selOffset);
+ // selection stays *before* moz-br, sticking to it
+ aSelection->SetInterlinePosition(true);
+ rv = aSelection->Collapse(brParent, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIContent> nextNode =
+ mHTMLEditor->GetNextHTMLNode(nearNode, true);
+ if (nextNode && TextEditUtils::IsMozBR(nextNode)) {
+ // selection between br and mozbr. make it stick to mozbr
+ // so that it will be on blank line.
+ aSelection->SetInterlinePosition(true);
+ }
+ }
+ }
+ }
+ }
+
+ // we aren't in a textnode: are we adjacent to text or a break or an image?
+ NS_ENSURE_STATE(mHTMLEditor);
+ nearNode = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, true);
+ if (nearNode && (TextEditUtils::IsBreak(nearNode) ||
+ EditorBase::IsTextNode(nearNode) ||
+ HTMLEditUtils::IsImage(nearNode) ||
+ nearNode->IsHTMLElement(nsGkAtoms::hr))) {
+ // this is a good place for the caret to be
+ return NS_OK;
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ nearNode = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, true);
+ if (nearNode && (TextEditUtils::IsBreak(nearNode) ||
+ EditorBase::IsTextNode(nearNode) ||
+ nearNode->IsAnyOfHTMLElements(nsGkAtoms::img,
+ nsGkAtoms::hr))) {
+ return NS_OK; // this is a good place for the caret to be
+ }
+
+ // look for a nearby text node.
+ // prefer the correct direction.
+ nsCOMPtr<nsIDOMNode> nearNodeDOM = GetAsDOMNode(nearNode);
+ rv = FindNearSelectableNode(GetAsDOMNode(selNode), selOffset, aAction,
+ address_of(nearNodeDOM));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nearNode = do_QueryInterface(nearNodeDOM);
+
+ if (!nearNode) {
+ return NS_OK;
+ }
+ EditorDOMPoint pt = GetGoodSelPointForNode(*nearNode, aAction);
+ rv = aSelection->Collapse(pt.node, pt.offset);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+
+nsresult
+HTMLEditRules::FindNearSelectableNode(nsIDOMNode* aSelNode,
+ int32_t aSelOffset,
+ nsIEditor::EDirection& aDirection,
+ nsCOMPtr<nsIDOMNode>* outSelectableNode)
+{
+ NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER);
+ *outSelectableNode = nullptr;
+
+ nsCOMPtr<nsIDOMNode> nearNode, curNode;
+ if (aDirection == nsIEditor::ePrevious) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // Try the other direction then.
+ if (!nearNode) {
+ if (aDirection == nsIEditor::ePrevious) {
+ aDirection = nsIEditor::eNext;
+ } else {
+ aDirection = nsIEditor::ePrevious;
+ }
+
+ if (aDirection == nsIEditor::ePrevious) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset,
+ address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset,
+ address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ // scan in the right direction until we find an eligible text node,
+ // but don't cross any breaks, images, or table elements.
+ NS_ENSURE_STATE(mHTMLEditor);
+ while (nearNode && !(mHTMLEditor->IsTextNode(nearNode) ||
+ TextEditUtils::IsBreak(nearNode) ||
+ HTMLEditUtils::IsImage(nearNode))) {
+ curNode = nearNode;
+ if (aDirection == nsIEditor::ePrevious) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ }
+
+ if (nearNode) {
+ // don't cross any table elements
+ if (InDifferentTableElements(nearNode, aSelNode)) {
+ return NS_OK;
+ }
+
+ // otherwise, ok, we have found a good spot to put the selection
+ *outSelectableNode = do_QueryInterface(nearNode);
+ }
+ return NS_OK;
+}
+
+bool
+HTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1,
+ nsIDOMNode* aNode2)
+{
+ nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode1);
+ nsCOMPtr<nsINode> node2 = do_QueryInterface(aNode2);
+ return InDifferentTableElements(node1, node2);
+}
+
+bool
+HTMLEditRules::InDifferentTableElements(nsINode* aNode1,
+ nsINode* aNode2)
+{
+ MOZ_ASSERT(aNode1 && aNode2);
+
+ while (aNode1 && !HTMLEditUtils::IsTableElement(aNode1)) {
+ aNode1 = aNode1->GetParentNode();
+ }
+
+ while (aNode2 && !HTMLEditUtils::IsTableElement(aNode2)) {
+ aNode2 = aNode2->GetParentNode();
+ }
+
+ return aNode1 != aNode2;
+}
+
+
+nsresult
+HTMLEditRules::RemoveEmptyNodes()
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ // Some general notes on the algorithm used here: the goal is to examine all
+ // the nodes in mDocChangeRange, 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.
+
+ // need an iterator
+ nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
+
+ nsresult rv = iter->Init(mDocChangeRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<OwningNonNull<nsINode>> arrayOfEmptyNodes, arrayOfEmptyCites, skipList;
+
+ // Check for empty nodes
+ while (!iter->IsDone()) {
+ OwningNonNull<nsINode> node = *iter->GetCurrentNode();
+
+ nsCOMPtr<nsINode> parent = node->GetParentNode();
+
+ size_t idx = skipList.IndexOf(node);
+ 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 (parent) {
+ skipList[idx] = parent;
+ }
+ } else {
+ bool bIsCandidate = false;
+ bool bIsEmptyNode = false;
+ bool bIsMailCite = false;
+
+ if (node->IsElement()) {
+ if (node->IsHTMLElement(nsGkAtoms::body)) {
+ // Don't delete the body
+ } else if ((bIsMailCite = HTMLEditUtils::IsMailCite(node)) ||
+ node->IsHTMLElement(nsGkAtoms::a) ||
+ HTMLEditUtils::IsInlineStyle(node) ||
+ HTMLEditUtils::IsList(node) ||
+ node->IsHTMLElement(nsGkAtoms::div)) {
+ // Only consider certain nodes to be empty for purposes of removal
+ bIsCandidate = true;
+ } else if (HTMLEditUtils::IsFormatNode(node) ||
+ HTMLEditUtils::IsListItem(node) ||
+ node->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.
+ bool bIsSelInNode;
+ rv = SelectionEndpointInNode(node, &bIsSelInNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bIsSelInNode) {
+ bIsCandidate = true;
+ }
+ }
+ }
+
+ if (bIsCandidate) {
+ // We delete mailcites even if they have a solo br in them. Other
+ // nodes we require to be empty.
+ rv = htmlEditor->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode,
+ bIsMailCite, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bIsEmptyNode) {
+ if (bIsMailCite) {
+ // mailcites go on a separate list from other empty nodes
+ arrayOfEmptyCites.AppendElement(*node);
+ } else {
+ arrayOfEmptyNodes.AppendElement(*node);
+ }
+ }
+ }
+
+ if (!bIsEmptyNode && parent) {
+ // put parent on skip list
+ skipList.AppendElement(*parent);
+ }
+ }
+
+ iter->Next();
+ }
+
+ // now delete the empty nodes
+ for (auto& delNode : arrayOfEmptyNodes) {
+ if (htmlEditor->IsModifiableNode(delNode)) {
+ rv = htmlEditor->DeleteNode(delNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Now delete the empty mailcites. This is a separate step because we want
+ // to pull out any br's and preserve them.
+ for (auto& delNode : arrayOfEmptyCites) {
+ bool bIsEmptyNode;
+ rv = htmlEditor->IsEmptyNode(delNode, &bIsEmptyNode, false, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bIsEmptyNode) {
+ // We are deleting a cite that has just a br. We want to delete cite,
+ // but preserve br.
+ nsCOMPtr<nsINode> parent = delNode->GetParentNode();
+ int32_t offset = parent ? parent->IndexOf(delNode) : -1;
+ nsCOMPtr<Element> br = htmlEditor->CreateBR(parent, offset);
+ NS_ENSURE_STATE(br);
+ }
+ rv = htmlEditor->DeleteNode(delNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::SelectionEndpointInNode(nsINode* aNode,
+ bool* aResult)
+{
+ NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER);
+
+ nsIDOMNode* node = aNode->AsDOMNode();
+
+ *aResult = false;
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ NS_ENSURE_STATE(selection);
+
+ uint32_t rangeCount = selection->RangeCount();
+ for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
+ RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
+ nsCOMPtr<nsIDOMNode> startParent, endParent;
+ range->GetStartContainer(getter_AddRefs(startParent));
+ if (startParent) {
+ if (node == startParent) {
+ *aResult = true;
+ return NS_OK;
+ }
+ if (EditorUtils::IsDescendantOf(startParent, node)) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ range->GetEndContainer(getter_AddRefs(endParent));
+ if (startParent == endParent) {
+ continue;
+ }
+ if (endParent) {
+ if (node == endParent) {
+ *aResult = true;
+ return NS_OK;
+ }
+ if (EditorUtils::IsDescendantOf(endParent, node)) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * IsEmptyInline: Return true if aNode is an empty inline container
+ */
+bool
+HTMLEditRules::IsEmptyInline(nsINode& aNode)
+{
+ NS_ENSURE_TRUE(mHTMLEditor, false);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ if (IsInlineNode(aNode) && htmlEditor->IsContainer(&aNode)) {
+ bool isEmpty = true;
+ htmlEditor->IsEmptyNode(&aNode, &isEmpty);
+ return isEmpty;
+ }
+ return false;
+}
+
+
+bool
+HTMLEditRules::ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes)
+{
+ // We have a list of nodes which we are candidates for being moved into a new
+ // block. Determine if it's anything more than a blank line. Look for
+ // editable content above and beyond one single BR.
+ NS_ENSURE_TRUE(aArrayOfNodes.Length(), true);
+
+ NS_ENSURE_TRUE(mHTMLEditor, false);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ int32_t brCount = 0;
+
+ for (auto& node : aArrayOfNodes) {
+ if (!htmlEditor->IsEditable(node)) {
+ continue;
+ }
+ if (TextEditUtils::IsBreak(node)) {
+ // First break doesn't count
+ if (brCount) {
+ return false;
+ }
+ brCount++;
+ } else if (IsEmptyInline(node)) {
+ // Empty inline, keep looking
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+nsresult
+HTMLEditRules::PopListItem(nsIDOMNode* aListItem,
+ bool* aOutOfList)
+{
+ nsCOMPtr<Element> listItem = do_QueryInterface(aListItem);
+ // check parms
+ NS_ENSURE_TRUE(listItem && aOutOfList, NS_ERROR_NULL_POINTER);
+
+ // init out params
+ *aOutOfList = false;
+
+ nsCOMPtr<nsINode> curParent = listItem->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(listItem) : -1;
+
+ if (!HTMLEditUtils::IsListItem(listItem)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if it's first or last list item, don't need to split the list
+ // otherwise we do.
+ nsCOMPtr<nsINode> curParPar = curParent->GetParentNode();
+ int32_t parOffset = curParPar ? curParPar->IndexOf(curParent) : -1;
+
+ bool bIsFirstListItem;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->IsFirstEditableChild(aListItem, &bIsFirstListItem);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool bIsLastListItem;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLastListItem);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!bIsFirstListItem && !bIsLastListItem) {
+ // split the list
+ nsCOMPtr<nsIDOMNode> newBlock;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->SplitNode(GetAsDOMNode(curParent), offset,
+ getter_AddRefs(newBlock));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!bIsFirstListItem) {
+ parOffset++;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->MoveNode(listItem, curParPar, parOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // unwrap list item contents if they are no longer in a list
+ if (!HTMLEditUtils::IsList(curParPar) &&
+ HTMLEditUtils::IsListItem(listItem)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RemoveBlockContainer(*listItem);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aOutOfList = true;
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::RemoveListStructure(Element& aList)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+ while (aList.GetFirstChild()) {
+ OwningNonNull<nsIContent> child = *aList.GetFirstChild();
+
+ if (HTMLEditUtils::IsListItem(child)) {
+ bool isOutOfList;
+ // Keep popping it out until it's not in a list anymore
+ do {
+ nsresult rv = PopListItem(child->AsDOMNode(), &isOutOfList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (!isOutOfList);
+ } else if (HTMLEditUtils::IsList(child)) {
+ nsresult rv = RemoveListStructure(*child->AsElement());
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Delete any non-list items for now
+ nsresult rv = htmlEditor->DeleteNode(child);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Delete the now-empty list
+ nsresult rv = htmlEditor->RemoveBlockContainer(aList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::ConfirmSelectionInBody()
+{
+ // get the body
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mHTMLEditor->GetRoot());
+ NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
+
+ // get the selection
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<Selection> selection = mHTMLEditor->GetSelection();
+ NS_ENSURE_STATE(selection);
+
+ // get the selection start location
+ nsCOMPtr<nsIDOMNode> selNode, temp, parent;
+ int32_t selOffset;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(selection,
+ getter_AddRefs(selNode), &selOffset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ temp = selNode;
+
+ // check that selNode is inside body
+ while (temp && !TextEditUtils::IsBody(temp)) {
+ temp->GetParentNode(getter_AddRefs(parent));
+ temp = parent;
+ }
+
+ // if we aren't in the body, force the issue
+ if (!temp) {
+// uncomment this to see when we get bad selections
+// NS_NOTREACHED("selection not in body");
+ selection->Collapse(rootElement, 0);
+ }
+
+ // get the selection end location
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetEndNodeAndOffset(selection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ temp = selNode;
+
+ // check that selNode is inside body
+ while (temp && !TextEditUtils::IsBody(temp)) {
+ rv = temp->GetParentNode(getter_AddRefs(parent));
+ temp = parent;
+ }
+
+ // if we aren't in the body, force the issue
+ if (!temp) {
+// uncomment this to see when we get bad selections
+// NS_NOTREACHED("selection not in body");
+ selection->Collapse(rootElement, 0);
+ }
+
+ // XXX This is the result of the last call of GetParentNode(), it doesn't
+ // make sense...
+ return rv;
+}
+
+nsresult
+HTMLEditRules::UpdateDocChangeRange(nsRange* aRange)
+{
+ // first make sure aRange is in the document. It might not be if
+ // portions of our editting action involved manipulating nodes
+ // prior to placing them in the document (e.g., populating a list item
+ // before placing it in its list)
+ nsCOMPtr<nsIDOMNode> startNode;
+ nsresult rv = aRange->GetStartContainer(getter_AddRefs(startNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (!mHTMLEditor->IsDescendantOfRoot(startNode)) {
+ // just return - we don't need to adjust mDocChangeRange in this case
+ return NS_OK;
+ }
+
+ if (!mDocChangeRange) {
+ // clone aRange.
+ mDocChangeRange = aRange->CloneRange();
+ } else {
+ int16_t result;
+
+ // compare starts of ranges
+ rv = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START,
+ aRange, &result);
+ if (rv == NS_ERROR_NOT_INITIALIZED) {
+ // This will happen is mDocChangeRange is non-null, but the range is
+ // uninitialized. In this case we'll set the start to aRange start.
+ // The same test won't be needed further down since after we've set
+ // the start the range will be collapsed to that point.
+ result = 1;
+ rv = NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Positive result means mDocChangeRange start is after aRange start.
+ if (result > 0) {
+ int32_t startOffset;
+ rv = aRange->GetStartOffset(&startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mDocChangeRange->SetStart(startNode, startOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // compare ends of ranges
+ rv = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END,
+ aRange, &result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Negative result means mDocChangeRange end is before aRange end.
+ if (result < 0) {
+ nsCOMPtr<nsIDOMNode> endNode;
+ int32_t endOffset;
+ rv = aRange->GetEndContainer(getter_AddRefs(endNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRange->GetEndOffset(&endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mDocChangeRange->SetEnd(endNode, endOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::InsertMozBRIfNeeded(nsINode& aNode)
+{
+ if (!IsBlockNode(aNode)) {
+ return NS_OK;
+ }
+
+ bool isEmpty;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->IsEmptyNode(&aNode, &isEmpty);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isEmpty) {
+ return NS_OK;
+ }
+
+ return CreateMozBR(aNode.AsDOMNode(), 0);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillCreateNode(const nsAString& aTag,
+ nsIDOMNode* aParent,
+ int32_t aPosition)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidCreateNode(const nsAString& aTag,
+ nsIDOMNode* aNode,
+ nsIDOMNode* aParent,
+ int32_t aPosition,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ // assumption that Join keeps the righthand node
+ nsresult rv = mUtilRange->SelectNode(aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillInsertNode(nsIDOMNode* aNode,
+ nsIDOMNode* aParent,
+ int32_t aPosition)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidInsertNode(nsIDOMNode* aNode,
+ nsIDOMNode* aParent,
+ int32_t aPosition,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ nsresult rv = mUtilRange->SelectNode(aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillDeleteNode(nsIDOMNode* aChild)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ nsresult rv = mUtilRange->SelectNode(aChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidDeleteNode(nsIDOMNode* aChild,
+ nsresult aResult)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillSplitNode(nsIDOMNode* aExistingRightNode,
+ int32_t aOffset)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidSplitNode(nsIDOMNode* aExistingRightNode,
+ int32_t aOffset,
+ nsIDOMNode* aNewLeftNode,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ nsresult rv = mUtilRange->SetStart(aNewLeftNode, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetEnd(aExistingRightNode, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillJoinNodes(nsIDOMNode* aLeftNode,
+ nsIDOMNode* aRightNode,
+ nsIDOMNode* aParent)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ // remember split point
+ return EditorBase::GetLengthOfDOMNode(aLeftNode, mJoinOffset);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidJoinNodes(nsIDOMNode* aLeftNode,
+ nsIDOMNode* aRightNode,
+ nsIDOMNode* aParent,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ // assumption that Join keeps the righthand node
+ nsresult rv = mUtilRange->SetStart(aRightNode, mJoinOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetEnd(aRightNode, mJoinOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillInsertText(nsIDOMCharacterData* aTextNode,
+ int32_t aOffset,
+ const nsAString& aString)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidInsertText(nsIDOMCharacterData* aTextNode,
+ int32_t aOffset,
+ const nsAString& aString,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ int32_t length = aString.Length();
+ nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
+ nsresult rv = mUtilRange->SetStart(theNode, aOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetEnd(theNode, aOffset+length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillDeleteText(nsIDOMCharacterData* aTextNode,
+ int32_t aOffset,
+ int32_t aLength)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidDeleteText(nsIDOMCharacterData* aTextNode,
+ int32_t aOffset,
+ int32_t aLength,
+ nsresult aResult)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
+ nsresult rv = mUtilRange->SetStart(theNode, aOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetEnd(theNode, aOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::WillDeleteSelection(nsISelection* aSelection)
+{
+ if (!mListenerEnabled) {
+ return NS_OK;
+ }
+ if (NS_WARN_IF(!aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ RefPtr<Selection> selection = aSelection->AsSelection();
+ // get the (collapsed) selection location
+ nsCOMPtr<nsIDOMNode> selNode;
+ int32_t selOffset;
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetStartNodeAndOffset(selection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetStart(selNode, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetEndNodeAndOffset(selection,
+ getter_AddRefs(selNode), &selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mUtilRange->SetEnd(selNode, selOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return UpdateDocChangeRange(mUtilRange);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DidDeleteSelection(nsISelection *aSelection)
+{
+ return NS_OK;
+}
+
+// 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.
+nsresult
+HTMLEditRules::RemoveAlignment(nsIDOMNode* aNode,
+ const nsAString& aAlignType,
+ bool aChildrenOnly)
+{
+ NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ if (mHTMLEditor->IsTextNode(aNode) || HTMLEditUtils::IsTable(aNode)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMNode> child = aNode,tmp;
+ if (aChildrenOnly) {
+ aNode->GetFirstChild(getter_AddRefs(child));
+ }
+ NS_ENSURE_STATE(mHTMLEditor);
+ bool useCSS = mHTMLEditor->IsCSSEnabled();
+
+ while (child) {
+ if (aChildrenOnly) {
+ // get the next sibling right now because we could have to remove child
+ child->GetNextSibling(getter_AddRefs(tmp));
+ } else {
+ tmp = nullptr;
+ }
+ bool isBlock;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->NodeIsBlockStatic(child, &isBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (EditorBase::NodeIsType(child, nsGkAtoms::center)) {
+ // the current node is a CENTER element
+ // first remove children's alignment
+ rv = RemoveAlignment(child, aAlignType, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we may have to insert BRs in first and last position of element's children
+ // if the nodes before/after are not blocks and not BRs
+ rv = MakeSureElemStartsOrEndsOnCR(child);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now remove the CENTER container
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<Element> childAsElement = do_QueryInterface(child);
+ NS_ENSURE_STATE(childAsElement);
+ rv = mHTMLEditor->RemoveContainer(childAsElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (isBlock || HTMLEditUtils::IsHR(child)) {
+ // the current node is a block element
+ nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(child);
+ if (HTMLEditUtils::SupportsAlignAttr(child)) {
+ // remove the ALIGN attribute if this element can have it
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (useCSS) {
+ if (HTMLEditUtils::IsTable(child) || HTMLEditUtils::IsHR(child)) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->SetAttributeOrEquivalent(curElem,
+ NS_LITERAL_STRING("align"),
+ aAlignType, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ nsAutoString dummyCssValue;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->mCSSEditUtils->RemoveCSSInlineStyle(
+ child,
+ nsGkAtoms::textAlign,
+ dummyCssValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+ if (!HTMLEditUtils::IsTable(child)) {
+ // unless this is a table, look at children
+ rv = RemoveAlignment(child, aAlignType, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ child = tmp;
+ }
+ return NS_OK;
+}
+
+// Let's insert a BR as first (resp. last) child of aNode if its
+// first (resp. last) child is not a block nor a BR, and if the
+// previous (resp. next) sibling is not a block nor a BR
+nsresult
+HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode,
+ bool aStarts)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsIDOMNode> child;
+ if (aStarts) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ child = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*node));
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ child = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*node));
+ }
+ NS_ENSURE_TRUE(child, NS_OK);
+ bool isChildBlock;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool foundCR = false;
+ if (isChildBlock || TextEditUtils::IsBreak(child)) {
+ foundCR = true;
+ } else {
+ nsCOMPtr<nsIDOMNode> sibling;
+ if (aStarts) {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ if (sibling) {
+ bool isBlock;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isBlock || TextEditUtils::IsBreak(sibling)) {
+ foundCR = true;
+ }
+ } else {
+ foundCR = true;
+ }
+ }
+ if (!foundCR) {
+ int32_t offset = 0;
+ if (!aStarts) {
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_STATE(node);
+ offset = node->GetChildCount();
+ }
+ nsCOMPtr<nsIDOMNode> brNode;
+ NS_ENSURE_STATE(mHTMLEditor);
+ rv = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode)
+{
+ nsresult rv = MakeSureElemStartsOrEndsOnCR(aNode, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MakeSureElemStartsOrEndsOnCR(aNode, true);
+}
+
+nsresult
+HTMLEditRules::AlignBlock(Element& aElement,
+ const nsAString& aAlignType,
+ ContentsOnly aContentsOnly)
+{
+ if (!IsBlockNode(aElement) && !aElement.IsHTMLElement(nsGkAtoms::hr)) {
+ // We deal only with blocks; early way out
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ nsresult rv = RemoveAlignment(aElement.AsDOMNode(), aAlignType,
+ aContentsOnly == ContentsOnly::yes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_NAMED_LITERAL_STRING(attr, "align");
+ if (htmlEditor->IsCSSEnabled()) {
+ // Let's use CSS alignment; we use margin-left and margin-right for tables
+ // and text-align for other block-level elements
+ rv = htmlEditor->SetAttributeOrEquivalent(
+ static_cast<nsIDOMElement*>(aElement.AsDOMNode()),
+ attr, aAlignType, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // HTML case; this code is supposed to be called ONLY if the element
+ // supports the align attribute but we'll never know...
+ if (HTMLEditUtils::SupportsAlignAttr(aElement.AsDOMNode())) {
+ rv = htmlEditor->SetAttribute(
+ static_cast<nsIDOMElement*>(aElement.AsDOMNode()),
+ attr, aAlignType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::ChangeIndentation(Element& aElement,
+ Change aChange)
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ nsIAtom& marginProperty =
+ MarginPropertyAtomForIndent(*htmlEditor->mCSSEditUtils, aElement);
+ nsAutoString value;
+ htmlEditor->mCSSEditUtils->GetSpecifiedProperty(aElement, marginProperty,
+ value);
+ float f;
+ nsCOMPtr<nsIAtom> unit;
+ htmlEditor->mCSSEditUtils->ParseLength(value, &f, getter_AddRefs(unit));
+ if (!f) {
+ nsAutoString defaultLengthUnit;
+ htmlEditor->mCSSEditUtils->GetDefaultLengthUnit(defaultLengthUnit);
+ unit = NS_Atomize(defaultLengthUnit);
+ }
+ int8_t multiplier = aChange == Change::plus ? +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) {
+ nsAutoString newValue;
+ newValue.AppendFloat(f);
+ newValue.Append(nsDependentAtomString(unit));
+ htmlEditor->mCSSEditUtils->SetCSSProperty(aElement, marginProperty,
+ newValue);
+ return NS_OK;
+ }
+
+ htmlEditor->mCSSEditUtils->RemoveCSSProperty(aElement, marginProperty,
+ value);
+
+ // Remove unnecessary divs
+ if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
+ &aElement == htmlEditor->GetActiveEditingHost() ||
+ !htmlEditor->IsDescendantOfEditorRoot(&aElement) ||
+ HTMLEditor::HasAttributes(&aElement)) {
+ return NS_OK;
+ }
+
+ nsresult rv = htmlEditor->RemoveContainer(&aElement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
+ bool* aCancel,
+ bool* aHandled)
+{
+ MOZ_ASSERT(aCancel && aHandled);
+ NS_ENSURE_STATE(mHTMLEditor);
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+
+ WillInsert(aSelection, aCancel);
+
+ // We want to ignore result of WillInsert()
+ *aCancel = false;
+ *aHandled = true;
+
+ nsCOMPtr<Element> focusElement = htmlEditor->GetSelectionContainer();
+ if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
+ mNewBlock = focusElement;
+ return NS_OK;
+ }
+
+ nsresult rv = NormalizeSelection(&aSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
+
+ // 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.
+
+ nsTArray<RefPtr<nsRange>> arrayOfRanges;
+ GetPromotedRanges(aSelection, arrayOfRanges,
+ EditAction::setAbsolutePosition);
+
+ // Use these ranges to contruct a list of nodes to act on.
+ nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
+ rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
+ EditAction::setAbsolutePosition);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If nothing visible in list, make an empty block
+ if (ListIsEmptyLine(arrayOfNodes)) {
+ // Get selection location
+ NS_ENSURE_STATE(aSelection.GetRangeAt(0) &&
+ aSelection.GetRangeAt(0)->GetStartParent());
+ OwningNonNull<nsINode> parent =
+ *aSelection.GetRangeAt(0)->GetStartParent();
+ int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
+
+ // Make sure we can put a block here
+ rv = SplitAsNeeded(*nsGkAtoms::div, parent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<Element> positionedDiv =
+ htmlEditor->CreateNode(nsGkAtoms::div, parent, offset);
+ NS_ENSURE_STATE(positionedDiv);
+ // Remember our new block for postprocessing
+ mNewBlock = positionedDiv;
+ // Delete anything that was in the list of nodes
+ while (!arrayOfNodes.IsEmpty()) {
+ OwningNonNull<nsINode> curNode = arrayOfNodes[0];
+ rv = htmlEditor->DeleteNode(curNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ arrayOfNodes.RemoveElementAt(0);
+ }
+ // Put selection in new block
+ *aHandled = true;
+ rv = aSelection.Collapse(positionedDiv, 0);
+ // Don't restore the selection
+ selectionRestorer.Abort();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // Okay, now go through all the nodes and put them in a blockquote, or
+ // whatever is appropriate. Woohoo!
+ nsCOMPtr<Element> curList, curPositionedDiv, indentedLI;
+ for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
+ // Here's where we actually figure out what to do
+ NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
+ OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
+
+ // Ignore all non-editable nodes. Leave them be.
+ if (!htmlEditor->IsEditable(curNode)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIContent> sibling;
+
+ nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
+ int32_t offset = curParent ? curParent->IndexOf(curNode) : -1;
+
+ // Some logic for putting list items into nested lists...
+ if (HTMLEditUtils::IsList(curParent)) {
+ // Check to see if curList is still appropriate. Which it is if curNode
+ // is still right after it in the same list.
+ if (curList) {
+ sibling = htmlEditor->GetPriorHTMLSibling(curNode);
+ }
+
+ if (!curList || (sibling && sibling != curList)) {
+ // Create a new nested list of correct type
+ rv =
+ SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!curPositionedDiv) {
+ nsCOMPtr<nsINode> curParentParent = curParent->GetParentNode();
+ int32_t parentOffset = curParentParent
+ ? curParentParent->IndexOf(curParent) : -1;
+ curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParentParent,
+ parentOffset);
+ mNewBlock = curPositionedDiv;
+ }
+ curList = htmlEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
+ curPositionedDiv, -1);
+ NS_ENSURE_STATE(curList);
+ // curList is now the correct thing to put curNode in. Remember our
+ // new block for postprocessing.
+ }
+ // Tuck the node into the end of the active list
+ rv = htmlEditor->MoveNode(curNode, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // 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.
+ nsCOMPtr<Element> listItem = IsInListItem(curNode);
+ if (listItem) {
+ if (indentedLI == listItem) {
+ // Already indented this list item
+ continue;
+ }
+ curParent = listItem->GetParentNode();
+ offset = curParent ? curParent->IndexOf(listItem) : -1;
+ // Check to see if curList is still appropriate. Which it is if
+ // curNode is still right after it in the same list.
+ if (curList) {
+ sibling = htmlEditor->GetPriorHTMLSibling(curNode);
+ }
+
+ if (!curList || (sibling && sibling != curList)) {
+ // Create a new nested list of correct type
+ rv = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent,
+ offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!curPositionedDiv) {
+ nsCOMPtr<nsINode> curParentParent = curParent->GetParentNode();
+ int32_t parentOffset = curParentParent ?
+ curParentParent->IndexOf(curParent) : -1;
+ curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div,
+ curParentParent,
+ parentOffset);
+ mNewBlock = curPositionedDiv;
+ }
+ curList = htmlEditor->CreateNode(curParent->NodeInfo()->NameAtom(),
+ curPositionedDiv, -1);
+ NS_ENSURE_STATE(curList);
+ }
+ rv = htmlEditor->MoveNode(listItem, curList, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Remember we indented this li
+ indentedLI = listItem;
+ } else {
+ // Need to make a div to put things in if we haven't already
+
+ if (!curPositionedDiv) {
+ if (curNode->IsHTMLElement(nsGkAtoms::div)) {
+ curPositionedDiv = curNode->AsElement();
+ mNewBlock = curPositionedDiv;
+ curList = nullptr;
+ continue;
+ }
+ rv = SplitAsNeeded(*nsGkAtoms::div, curParent, offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ curPositionedDiv = htmlEditor->CreateNode(nsGkAtoms::div, curParent,
+ offset);
+ NS_ENSURE_STATE(curPositionedDiv);
+ // Remember our new block for postprocessing
+ mNewBlock = curPositionedDiv;
+ // curPositionedDiv is now the correct thing to put curNode in
+ }
+
+ // Tuck the node into the end of the active blockquote
+ rv = htmlEditor->MoveNode(curNode, curPositionedDiv, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Forget curList, if any
+ curList = nullptr;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+HTMLEditRules::DidAbsolutePosition()
+{
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
+ nsCOMPtr<nsIDOMElement> elt =
+ static_cast<nsIDOMElement*>(GetAsDOMNode(mNewBlock));
+ return absPosHTMLEditor->AbsolutelyPositionElement(elt, true);
+}
+
+nsresult
+HTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection,
+ bool* aCancel,
+ bool* aHandled) {
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ WillInsert(*aSelection, aCancel);
+
+ // initialize out param
+ // we want to ignore aCancel from WillInsert()
+ *aCancel = false;
+ *aHandled = true;
+
+ nsCOMPtr<nsIDOMElement> elt;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
+ return absPosHTMLEditor->AbsolutelyPositionElement(elt, false);
+}
+
+nsresult
+HTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection,
+ int32_t aChange,
+ bool* aCancel,
+ bool* aHandled)
+{
+ if (!aSelection || !aCancel || !aHandled) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ WillInsert(*aSelection, aCancel);
+
+ // initialize out param
+ // we want to ignore aCancel from WillInsert()
+ *aCancel = false;
+ *aHandled = true;
+
+ nsCOMPtr<nsIDOMElement> elt;
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsresult rv =
+ mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
+
+ NS_ENSURE_STATE(mHTMLEditor);
+ nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
+ int32_t zIndex;
+ return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex);
+}
+
+NS_IMETHODIMP
+HTMLEditRules::DocumentModified()
+{
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &HTMLEditRules::DocumentModifiedWorker));
+ return NS_OK;
+}
+
+void
+HTMLEditRules::DocumentModifiedWorker()
+{
+ if (!mHTMLEditor) {
+ return;
+ }
+
+ // DeleteNode below may cause a flush, which could destroy the editor
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
+ RefPtr<Selection> selection = htmlEditor->GetSelection();
+ if (!selection) {
+ return;
+ }
+
+ // Delete our bogus node, if we have one, since the document might not be
+ // empty any more.
+ if (mBogusNode) {
+ mTextEditor->DeleteNode(mBogusNode);
+ mBogusNode = nullptr;
+ }
+
+ // Try to recreate the bogus node if needed.
+ CreateBogusNodeIfNeeded(selection);
+}
+
+} // namespace mozilla