/* -*- 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 "nsHTMLEditRules.h" #include #include "mozilla/Assertions.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 "nsAString.h" #include "nsAlgorithm.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsEditor.h" #include "nsEditorUtils.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsHTMLCSSUtils.h" #include "nsHTMLEditUtils.h" #include "nsHTMLEditor.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 "nsIHTMLAbsPosEditor.h" #include "nsIHTMLDocument.h" #include "nsINode.h" #include "nsLiteralString.h" #include "nsPlaintextEditor.h" #include "nsRange.h" #include "nsReadableUtils.h" #include "nsString.h" #include "nsStringFwd.h" #include "nsTArray.h" #include "nsTextEditUtils.h" #include "nsThreadUtils.h" #include "nsUnicharUtils.h" #include "nsWSRunObject.h" #include class nsISupports; class nsRulesInfo; using namespace mozilla; using namespace mozilla::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(nsIDOMNode* node) { bool isBlock (false); nsHTMLEditor::NodeIsBlockStatic(node, &isBlock); return isBlock; } static bool IsInlineNode(nsIDOMNode* 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 nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor { public: // Used to build list of all li's, td's & th's iterator covers virtual bool operator()(nsINode* aNode) const { if (nsHTMLEditUtils::IsTableCell(aNode)) return true; if (nsHTMLEditUtils::IsListItem(aNode)) return true; return false; } }; class nsBRNodeFunctor : public nsBoolDomIterFunctor { public: virtual bool operator()(nsINode* aNode) const { if (aNode->IsHTMLElement(nsGkAtoms::br)) { return true; } return false; } }; class nsEmptyEditableFunctor : public nsBoolDomIterFunctor { public: explicit nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} virtual bool operator()(nsINode* aNode) const { if (mHTMLEditor->IsEditable(aNode) && (nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCellOrCaption(GetAsDOMNode(aNode)))) { bool bIsEmptyNode; nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false); NS_ENSURE_SUCCESS(res, false); if (bIsEmptyNode) return true; } return false; } protected: nsHTMLEditor* mHTMLEditor; }; class nsEditableTextFunctor : public nsBoolDomIterFunctor { public: explicit nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} virtual bool operator()(nsINode* aNode) const { if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode)) { return true; } return false; } protected: nsHTMLEditor* mHTMLEditor; }; /******************************************************** * Constructor/Destructor ********************************************************/ nsHTMLEditRules::nsHTMLEditRules() { InitFields(); } void nsHTMLEditRules::InitFields() { mHTMLEditor = nullptr; mDocChangeRange = nullptr; mListenerEnabled = true; mReturnInEmptyLIKillsList = true; mDidDeleteSelection = false; mDidRangedDelete = false; mRestoreContentEditableCount = false; mUtilRange = nullptr; mJoinOffset = 0; mNewBlock = nullptr; mRangeItem = new nsRangeStore(); // 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()); } nsHTMLEditRules::~nsHTMLEditRules() { // remove ourselves as a listener to edit actions // In some cases, we have already been removed by // ~nsHTMLEditor, 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); } /******************************************************** * XPCOM Cruft ********************************************************/ NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules) NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules) NS_IMPL_QUERY_INTERFACE_INHERITED(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener) /******************************************************** * Public methods ********************************************************/ NS_IMETHODIMP nsHTMLEditRules::Init(nsPlaintextEditor *aEditor) { InitFields(); mHTMLEditor = static_cast(aEditor); nsresult res; // call through to base class Init res = nsTextEditRules::Init(aEditor); NS_ENSURE_SUCCESS(res, res); // 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 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 nsAutoLockRulesSniffing lockIt((nsTextEditRules*)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 res = mHTMLEditor->AddEditActionListener(this); return res; } NS_IMETHODIMP nsHTMLEditRules::DetachEditor() { if (mHTMLEditor) { mHTMLEditor->RemoveEditActionListener(this); } mHTMLEditor = nullptr; return nsTextEditRules::DetachEditor(); } NS_IMETHODIMP nsHTMLEditRules::BeforeEdit(EditAction action, nsIEditor::EDirection aDirection) { if (mLockRulesSniffing) return NS_OK; nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); mDidExplicitlySetInterline = false; if (!mActionNesting++) { // clear our flag about if just deleted a range mDidRangedDelete = false; // remember where our selection was before edit action took place: // get selection NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); // get the selection location if (!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 selStartNode = GetAsDOMNode(mRangeItem->startNode); nsCOMPtr selEndNode = GetAsDOMNode(mRangeItem->endNode); // register this range with range updater to track this as we perturb the doc NS_ENSURE_STATE(mHTMLEditor); (mHTMLEditor->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 operations if (action == EditAction::insertText || action == EditAction::insertIMEText || action == EditAction::deleteSelection || IsStyleCachePreservingAction(action)) { nsCOMPtr selNode = selStartNode; if (aDirection == nsIEditor::eNext) selNode = selEndNode; nsresult res = CacheInlineStyles(selNode); NS_ENSURE_SUCCESS(res, res); } // Stabilize the document against contenteditable count changes NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); nsCOMPtr 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 nsHTMLEditRules::AfterEdit(EditAction action, nsIEditor::EDirection aDirection) { if (mLockRulesSniffing) return NS_OK; nsAutoLockRulesSniffing lockIt(this); NS_PRECONDITION(mActionNesting>0, "bad action nesting!"); nsresult res = NS_OK; if (!--mActionNesting) { // do all the tricky stuff res = AfterEditInner(action, aDirection); // free up selectionState range item NS_ENSURE_STATE(mHTMLEditor); (mHTMLEditor->mRangeUpdater).DropRangeItem(mRangeItem); // Reset the contenteditable count to its previous value if (mRestoreContentEditableCount) { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); nsCOMPtr htmlDoc = do_QueryInterface(doc); NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE); if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { htmlDoc->ChangeContentEditableCount(nullptr, -1); } mRestoreContentEditableCount = false; } } return res; } nsresult nsHTMLEditRules::AfterEditInner(EditAction action, nsIEditor::EDirection aDirection) { ConfirmSelectionInBody(); if (action == EditAction::ignore) return NS_OK; NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); NS_ENSURE_STATE(selection); nsCOMPtr 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; } nsresult res; 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); nsAutoTxnsConserveSelection 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) { res = InsertBRIfNeeded(selection); NS_ENSURE_SUCCESS(res, res); } // add in any needed
s, and remove any unneeded ones. AdjustSpecialBreaks(); // merge any adjacent text nodes if ( (action != EditAction::insertText && action != EditAction::insertIMEText) ) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange); NS_ENSURE_SUCCESS(res, res); } // clean up any empty nodes in the selection res = RemoveEmptyNodes(); NS_ENSURE_SUCCESS(res, res); // 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))) { res = AdjustWhitespace(selection); NS_ENSURE_SUCCESS(res, res); // also do this for original selection endpoints. NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(mRangeItem->startNode); NS_ENSURE_STATE(mRangeItem->endNode); nsWSRunObject(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); nsWSRunObject(mHTMLEditor, mRangeItem->endNode, mRangeItem->endOffset).AdjustWhitespace(); } } // if we created a new block, make sure selection lands in it if (mNewBlock) { res = PinSelectionToNewBlock(selection); mNewBlock = 0; } // 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))) { res = AdjustSelection(selection, aDirection); NS_ENSURE_SUCCESS(res, res); } // 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); res = ReapplyCachedStyles(); NS_ENSURE_SUCCESS(res, res); ClearCachedStyles(); } } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->HandleInlineSpellCheck(action, selection, GetAsDOMNode(mRangeItem->startNode), mRangeItem->startOffset, rangeStartParent, rangeStartOffset, rangeEndParent, rangeEndOffset); NS_ENSURE_SUCCESS(res, res); // detect empty doc res = CreateBogusNodeIfNeeded(selection); // adjust selection HINT if needed NS_ENSURE_SUCCESS(res, res); if (!mDidExplicitlySetInterline) { res = CheckInterlinePosition(selection); } return res; } NS_IMETHODIMP nsHTMLEditRules::WillDoAction(Selection* aSelection, nsRulesInfo* aInfo, bool* aCancel, bool* aHandled) { MOZ_ASSERT(aInfo && aCancel && aHandled); *aCancel = false; *aHandled = false; // my kingdom for dynamic cast nsTextRulesInfo *info = static_cast(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 nsTextEditRules::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 range = aSelection->GetRangeAt(0); nsCOMPtr selStartNode = range->GetStartParent(); NS_ENSURE_STATE(mHTMLEditor); if (!mHTMLEditor->IsModifiableNode(selStartNode)) { *aCancel = true; return NS_OK; } nsCOMPtr 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: return WillInsert(aSelection, aCancel); case EditAction::decreaseZIndex: return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled); case EditAction::increaseZIndex: return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled); default: return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); } } NS_IMETHODIMP nsHTMLEditRules::DidDoAction(Selection* aSelection, nsRulesInfo* aInfo, nsresult aResult) { nsTextRulesInfo *info = static_cast(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 thru to nsTextEditRules return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); } } nsresult nsHTMLEditRules::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> arrayOfNodes; nsresult res = GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no); NS_ENSURE_SUCCESS(res, res); // 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 nsHTMLEditRules::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> arrayOfNodes; nsresult res = GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no); NS_ENSURE_SUCCESS(res, res); // 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 nsHTMLEditRules::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign) { // 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 NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER); *aMixed = false; *aAlign = nsIHTMLEditor::eLeft; // get selection NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); NS_ENSURE_STATE(selection); // get selection location NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr rootElem = mHTMLEditor->GetRoot(); NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE); int32_t offset, rootOffset; nsCOMPtr parent = nsEditor::GetNodeLocation(rootElem, &rootOffset); NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); // is the selection collapsed? nsCOMPtr nodeToExamine; if (selection->Collapsed()) { // if it is, we want to look at 'parent' and its ancestors // for divs with alignment on them nodeToExamine = parent; } else if (!mHTMLEditor) { return NS_ERROR_UNEXPECTED; } else if (mHTMLEditor->IsTextNode(parent)) { // 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 NS_ENSURE_STATE(mHTMLEditor); nodeToExamine = mHTMLEditor->GetNextNode(parent, offset, true); } else { nsTArray> arrayOfRanges; GetPromotedRanges(*selection, arrayOfRanges, EditAction::align); // use these ranges to construct a list of nodes to act on. nsTArray> arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::align, TouchContent::no); NS_ENSURE_SUCCESS(res, res); nodeToExamine = arrayOfNodes.SafeElementAt(0); } NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); NS_NAMED_LITERAL_STRING(typeAttrName, "align"); nsIAtom *dummyProperty = nullptr; NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr blockParent = mHTMLEditor->GetBlock(*nodeToExamine); NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsCSSEnabled()) { NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParent, dummyProperty, &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 NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet( blockParent, dummyProperty, &typeAttrName, value, nsHTMLCSSUtils::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; while (nodeToExamine) { if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine)) { // 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 (nsHTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(nodeToExamine))) { // check for alignment nsCOMPtr elem = do_QueryInterface(nodeToExamine); if (elem) { nsAutoString typeAttrVal; res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal); ToLowerCase(typeAttrVal); if (NS_SUCCEEDED(res) && typeAttrVal.Length()) { 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 res; } } } isFirstNodeToExamine = false; nodeToExamine = nodeToExamine->GetParentNode(); } return NS_OK; } static nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node || !aNode, nsGkAtoms::marginLeft); nsAutoString direction; aHTMLCSSUtils->GetComputedProperty(*node, *nsGkAtoms::direction, direction); return direction.EqualsLiteral("rtl") ? nsGkAtoms::marginRight : nsGkAtoms::marginLeft; } nsresult nsHTMLEditRules::GetIndentState(bool *aCanIndent, bool *aCanOutdent) { NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE); *aCanIndent = true; *aCanOutdent = false; // get selection NS_ENSURE_STATE(mHTMLEditor && mHTMLEditor->GetSelection()); OwningNonNull selection = *mHTMLEditor->GetSelection(); // contruct a list of nodes to act on. nsTArray> arrayOfNodes; nsresult res = GetNodesFromSelection(*selection, EditAction::indent, arrayOfNodes, TouchContent::no); NS_ENSURE_SUCCESS(res, res); // 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 (nsHTMLEditUtils::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->mHTMLCSSUtils, GetAsDOMNode(curNode)); nsAutoString value; // retrieve its specified value NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(*curNode, *marginProperty, value); float f; nsCOMPtr unit; // get its number part and its unit NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->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 parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot()); NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); // test start parent hierarchy NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); NS_ENSURE_SUCCESS(res, res); while (parent && (parent!=root)) { if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) { *aCanOutdent = true; break; } tmp=parent; tmp->GetParentNode(getter_AddRefs(parent)); } // test end parent hierarchy NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); NS_ENSURE_SUCCESS(res, res); while (parent && (parent!=root)) { if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) { *aCanOutdent = true; break; } tmp=parent; tmp->GetParentNode(getter_AddRefs(parent)); } } return res; } nsresult nsHTMLEditRules::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> arrayOfNodes; nsresult res = GetParagraphFormatNodes(arrayOfNodes, TouchContent::no); NS_ENSURE_SUCCESS(res, res); // 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(GetAsDOMNode(curNode)) && !nsHTMLEditUtils::IsFormatNode(curNode)) { // arrayOfNodes.RemoveObject(curNode); res = AppendInnerFormatNodes(arrayOfNodes, curNode); NS_ENSURE_SUCCESS(res, res); } } // we might have an empty node list. if so, find selection parent // and put that on the list if (!arrayOfNodes.Length()) { nsCOMPtr selNode; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); NS_ENSURE_STATE(selection); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER); arrayOfNodes.AppendElement(*selNode); } // remember root node NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr 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 (nsHTMLEditUtils::IsFormatNode(curNode)) { GetFormatString(GetAsDOMNode(curNode), format); } else if (IsBlockNode(GetAsDOMNode(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 node, tmp = GetAsDOMNode(curNode); tmp->GetParentNode(getter_AddRefs(node)); while (node) { if (node == rootElem) { format.Truncate(0); break; } else if (nsHTMLEditUtils::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 res; } nsresult nsHTMLEditRules::AppendInnerFormatNodes(nsTArray>& 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->AsDOMNode()); bool isFormat = nsHTMLEditUtils::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 nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); if (nsHTMLEditUtils::IsFormatNode(aNode)) { nsCOMPtr atom = nsEditor::GetTag(aNode); atom->ToString(outFormat); } else outFormat.Truncate(); return NS_OK; } /******************************************************** * Protected rules methods ********************************************************/ nsresult nsHTMLEditRules::WillInsert(Selection* aSelection, bool* aCancel) { nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // 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 NS_OK; } // if we are after a mozBR in the same block, then move selection // to be before it nsCOMPtr selNode, priorNode; int32_t selOffset; // get the (collapsed) selection location NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // get prior node NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(priorNode)); if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode)) { nsCOMPtr block1, block2; if (IsBlockNode(selNode)) { block1 = selNode; } else { NS_ENSURE_STATE(mHTMLEditor); block1 = mHTMLEditor->GetBlockNodeParent(selNode); } NS_ENSURE_STATE(mHTMLEditor); block2 = mHTMLEditor->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 = nsEditor::GetNodeLocation(priorNode, &selOffset); res = aSelection->Collapse(selNode,selOffset); NS_ENSURE_SUCCESS(res, res); } } if (mDidDeleteSelection && (mTheAction == EditAction::insertText || mTheAction == EditAction::insertIMEText || mTheAction == EditAction::deleteSelection)) { res = ReapplyCachedStyles(); NS_ENSURE_SUCCESS(res, res); } // For most actions we want to clear the cached styles, but there are // exceptions if (!IsStyleCachePreservingAction(mTheAction)) { ClearCachedStyles(); } return NS_OK; } nsresult nsHTMLEditRules::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; nsresult res; // 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); res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip); NS_ENSURE_SUCCESS(res, res); } res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; // we need to get the doc NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr doc = mHTMLEditor->GetDocument(); nsCOMPtr domDoc = mHTMLEditor->GetDOMDocument(); NS_ENSURE_TRUE(doc && domDoc, NS_ERROR_NOT_INITIALIZED); // for every property that is set, insert a new inline style node res = CreateStyleForInsertText(aSelection, domDoc); NS_ENSURE_SUCCESS(res, res); // get the (collapsed) selection location NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(aSelection->GetRangeAt(0)); nsCOMPtr 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 nsWSRunObject 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()) { res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc); } else { nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc); } NS_ENSURE_SUCCESS(res, res); } else // aAction == kInsertText { // find where we are nsCOMPtr 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); res = mHTMLEditor->IsPreformatted(GetAsDOMNode(selNode), &isPRE); NS_ENSURE_SUCCESS(res, res); // 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. nsAutoLockListener lockit(&mListenerEnabled); // don't spaz my selection in subtransactions NS_ENSURE_STATE(mHTMLEditor); nsAutoTxnsConserveSelection 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 < (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 == 0) 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 br = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, nsIEditor::eNone); NS_ENSURE_STATE(br); pos++; } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); NS_ENSURE_SUCCESS(res, res); } } } else { NS_NAMED_LITERAL_STRING(tabStr, "\t"); NS_NAMED_LITERAL_STRING(spacesStr, " "); char specialChars[] = {TAB, nsCRT::LF, 0}; while (unicodeBuf && (pos != -1) && (pos < (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 == 0) subStrLen = 1; } else { subStrLen = tString.Length() - oldPos; pos = tString.Length(); } nsDependentSubstring subStr(tString, oldPos, subStrLen); NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset); // is it a tab? if (subStr.Equals(tabStr)) { res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc); NS_ENSURE_SUCCESS(res, res); pos++; } // is it a return? else if (subStr.Equals(newlineStr)) { nsCOMPtr br = wsObj.InsertBreak(address_of(curNode), &curOffset, nsIEditor::eNone); NS_ENSURE_TRUE(br, NS_ERROR_FAILURE); pos++; } else { res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc); NS_ENSURE_SUCCESS(res, res); } NS_ENSURE_SUCCESS(res, res); } } 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); } res = mDocChangeRange->SetStart(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); if (curNode) res = mDocChangeRange->SetEnd(curNode, curOffset); else res = mDocChangeRange->SetEnd(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); } return res; } nsresult nsHTMLEditRules::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) { mEditor->DeleteNode(mBogusNode); mBogusNode = nullptr; } return NS_OK; } nsresult nsHTMLEditRules::WillInsertBreak(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out params *aCancel = false; *aHandled = false; // if the selection isn't collapsed, delete it. nsresult res = NS_OK; if (!aSelection->Collapsed()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); NS_ENSURE_SUCCESS(res, res); } res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // 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()) { res = SplitMailCites(aSelection, aHandled); NS_ENSURE_SUCCESS(res, res); if (*aHandled) { return NS_OK; } } // smart splitting rules nsCOMPtr node; int32_t offset; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); // do nothing if the node is read-only NS_ENSURE_STATE(mHTMLEditor); if (!mHTMLEditor->IsModifiableNode(node)) { *aCancel = true; return NS_OK; } // identify the block nsCOMPtr blockParent; if (IsBlockNode(node)) { blockParent = node; } else { NS_ENSURE_STATE(mHTMLEditor); blockParent = mHTMLEditor->GetBlockNodeParent(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. NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr hostContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr hostNode = do_QueryInterface(hostContent); if (!nsEditorUtils::IsDescendantOf(blockParent, hostNode)) { res = StandardBreakImpl(node, offset, aSelection); NS_ENSURE_SUCCESS(res, res); *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) { uint32_t blockLen; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen); NS_ENSURE_SUCCESS(res, res); nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); } nsCOMPtr listItem = IsInListItem(blockParent); if (listItem && listItem != hostNode) { ReturnInListItem(aSelection, listItem, node, offset); *aHandled = true; return NS_OK; } else if (nsHTMLEditUtils::IsHeader(blockParent)) { // headers: close (or split) header ReturnInHeader(aSelection, blockParent, node, offset); *aHandled = true; return NS_OK; } else if (nsHTMLEditUtils::IsParagraph(blockParent)) { // paragraphs: special rules to look for
s res = ReturnInParagraph(aSelection, blockParent, node, offset, aCancel, aHandled); NS_ENSURE_SUCCESS(res, res); // 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 nsHTMLEditRules::StandardBreakImpl(nsIDOMNode* aNode, int32_t aOffset, Selection* aSelection) { nsCOMPtr brNode; bool bAfterBlock = false; bool bBeforeBlock = false; nsresult res = NS_OK; nsCOMPtr node(aNode); if (IsPlaintextEditor()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode)); } else { NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject wsObj(mHTMLEditor, node, aOffset); int32_t visOffset = 0; WSType wsType; nsCOMPtr node_(do_QueryInterface(node)), 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; } NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr linkNode; if (mHTMLEditor->IsInLink(node, address_of(linkNode))) { // split the link nsCOMPtr linkParent; res = linkNode->GetParentNode(getter_AddRefs(linkParent)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr linkNodeContent = do_QueryInterface(linkNode); NS_ENSURE_STATE(linkNodeContent || !linkNode); aOffset = mHTMLEditor->SplitNodeDeep(*linkNodeContent, *node_->AsContent(), aOffset, nsHTMLEditor::EmptyContainers::no); NS_ENSURE_STATE(aOffset != -1); node = linkParent; } node_ = do_QueryInterface(node); nsCOMPtr br = wsObj.InsertBreak(address_of(node_), &aOffset, nsIEditor::eNone); node = GetAsDOMNode(node_); brNode = GetAsDOMNode(br); NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE); } NS_ENSURE_SUCCESS(res, res); node = nsEditor::GetNodeLocation(brNode, &aOffset); NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); 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); res = aSelection->Collapse(node, aOffset); } else { NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1); nsCOMPtr secondBR; int32_t visOffset = 0; WSType wsType; nsCOMPtr node_(do_QueryInterface(node)); wsObj.NextVisibleNode(node_, aOffset+1, address_of(secondBR), &visOffset, &wsType); if (wsType == WSType::br) { // the next thing after the break we inserted is another break. Move // the 2nd break to be the first breaks 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. int32_t brOffset; nsCOMPtr brParent = nsEditor::GetNodeLocation(GetAsDOMNode(secondBR), &brOffset); if (brParent != node || brOffset != aOffset + 1) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(secondBR->AsContent(), node_, aOffset + 1); NS_ENSURE_SUCCESS(res, res); } } // 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 siblingNode; brNode->GetNextSibling(getter_AddRefs(siblingNode)); if (siblingNode && IsBlockNode(siblingNode)) { aSelection->SetInterlinePosition(false); } else { aSelection->SetInterlinePosition(true); } res = aSelection->Collapse(node, aOffset+1); } return res; } nsresult nsHTMLEditRules::DidInsertBreak(Selection* aSelection, nsresult aResult) { return NS_OK; } nsresult nsHTMLEditRules::SplitMailCites(Selection* aSelection, bool* aHandled) { NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER); nsCOMPtr leftCite, rightCite; nsCOMPtr selNode; nsCOMPtr citeNode; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); citeNode = GetTopEnclosingMailCite(*selNode); NS_ENSURE_SUCCESS(res, res); 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); nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); nsCOMPtr 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, nsHTMLEditor::EmptyContainers::no, getter_AddRefs(leftCite), getter_AddRefs(rightCite)); NS_ENSURE_STATE(newOffset != -1); selNode = citeNode->GetParentNode(); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr brNode = mHTMLEditor->CreateBR(selNode, newOffset); NS_ENSURE_STATE(brNode); // want selection before the break, and on same line aSelection->SetInterlinePosition(true); res = aSelection->Collapse(selNode, newOffset); NS_ENSURE_SUCCESS(res, res); // 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(GetAsDOMNode(citeNode))) { NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset); nsCOMPtr 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); nsWSRunObject 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) { 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); res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false); if (NS_SUCCEEDED(res) && bEmptyCite) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(leftCite); } NS_ENSURE_SUCCESS(res, res); } if (rightCite) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false); if (NS_SUCCEEDED(res) && bEmptyCite) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(rightCite); } NS_ENSURE_SUCCESS(res, res); } *aHandled = true; } return NS_OK; } nsresult nsHTMLEditRules::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 cell; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); if (NS_SUCCEEDED(res) && cell) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteTableCellContents(); *aHandled = true; return res; } 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 selNode; int32_t selOffset; NS_ENSURE_STATE(aSelection->GetRangeAt(0)); nsCOMPtr 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 host = mHTMLEditor->GetActiveEditingHost(); NS_ENSURE_TRUE(host, NS_ERROR_FAILURE); res = CheckForEmptyBlock(startNode, host, aSelection, aHandled); NS_ENSURE_SUCCESS(res, res); if (*aHandled) { return NS_OK; } // Test for distance between caret and text that will be deleted res = CheckBidiLevelForDeletion(aSelection, GetAsDOMNode(startNode), startOffset, aAction, aCancel); NS_ENSURE_SUCCESS(res, res); if (*aCancel) { return NS_OK; } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction); NS_ENSURE_SUCCESS(res, res); // 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); nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset); nsCOMPtr 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; return res; } if (wsType == WSType::normalWS) { // We found some visible ws to delete. Let ws code handle it. if (aAction == nsIEditor::eNext) { res = wsObj.DeleteWSForward(); } else { res = wsObj.DeleteWSBackward(); } *aHandled = true; NS_ENSURE_SUCCESS(res, res); res = InsertBRIfNeeded(aSelection); return res; } if (wsType == WSType::text) { // Found normal text to delete. OwningNonNull nodeAsText = *visNode->GetAsText(); int32_t so = visOffset; int32_t eo = visOffset + 1; if (aAction == nsIEditor::ePrevious) { if (so == 0) { 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 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); res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so)); *aHandled = true; NS_ENSURE_SUCCESS(res, res); res = InsertBRIfNeeded(aSelection); NS_ENSURE_SUCCESS(res, res); // 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); res = mHTMLEditor->DeleteNode(visNode); NS_ENSURE_SUCCESS(res, res); return WillDeleteSelection(aSelection, aAction, aStripWrappers, aCancel, aHandled); } // Special handling for backspace when positioned after
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
. // // In other words, we only want to delete, if our selection position // (indicated by startNode and startOffset) is the position directly // after the
, on the same line as the
. // // 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; res = aSelection->GetInterlinePosition(&interLineIsRight); NS_ENSURE_SUCCESS(res, res); if (startNode == selNode && startOffset - 1 == selOffset && !interLineIsRight) { moveOnly = false; } if (moveOnly) { // Go to the position after the
, but to the end of the
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
is // followed by a
we want to delete the
. WSType otherWSType; nsCOMPtr otherNode; int32_t otherOffset; wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), &otherOffset, &otherWSType); if (otherWSType == WSType::br) { // Delete the
NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr otherContent(do_QueryInterface(otherNode)); res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherContent); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(otherNode); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } // Else continue with normal delete code } // Found break or image, or hr. NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(visNode->IsContent()); res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode->AsContent()); NS_ENSURE_SUCCESS(res, res); // Remember sibling to visnode, if any NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr sibling = mHTMLEditor->GetPriorHTMLSibling(visNode); // Delete the node, and join like nodes if appropriate NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(visNode); NS_ENSURE_SUCCESS(res, res); // We did something, so let's say so. *aHandled = true; // Is there a prior node and are they siblings? nsCOMPtr 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()) { ::DOMPoint pt = JoinNodesSmart(*sibling, *startNode->AsContent()); NS_ENSURE_STATE(pt.node); // Fix up selection res = aSelection->Collapse(pt.node, pt.offset); NS_ENSURE_SUCCESS(res, res); } res = InsertBRIfNeeded(aSelection); NS_ENSURE_SUCCESS(res, res); 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 (nsHTMLEditUtils::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 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 leafNode; nsCOMPtr 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); res = mHTMLEditor->DeleteNode(otherNode); NS_ENSURE_SUCCESS(res, res); *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); ::DOMPoint 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 selPointNode = startNode; int32_t selPointOffset = startOffset; { NS_ENSURE_STATE(mHTMLEditor); nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); res = JoinBlocks(GetAsDOMNode(leftNode), GetAsDOMNode(rightNode), aCancel); *aHandled = true; NS_ENSURE_SUCCESS(res, res); } 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 (nsHTMLEditUtils::IsTableElement(visNode)) { *aCancel = true; return NS_OK; } // First find the relevant nodes nsCOMPtr 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 selPointNode = startNode; int32_t selPointOffset = startOffset; { NS_ENSURE_STATE(mHTMLEditor); nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); res = JoinBlocks(GetAsDOMNode(leftNode), GetAsDOMNode(rightNode), aCancel); *aHandled = true; NS_ENSURE_SUCCESS(res, res); } aSelection->Collapse(selPointNode, selPointOffset); return NS_OK; } } // Else we have a non-collapsed selection. First adjust the selection. res = ExpandSelectionForDeletion(aSelection); NS_ENSURE_SUCCESS(res, res); // 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 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); nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, address_of(endNode), &endOffset); NS_ENSURE_SUCCESS(res, res); } { // Track location of where we are deleting NS_ENSURE_STATE(mHTMLEditor); nsAutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater, address_of(startNode), &startOffset); nsAutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater, address_of(endNode), &endOffset); // We are handling all ranged deletions directly now. *aHandled = true; if (endNode == startNode) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); } else { // Figure out mailcite ancestors nsCOMPtr startCiteNode = GetTopEnclosingMailCite(*startNode); nsCOMPtr 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 leftParent = mHTMLEditor->GetBlock(*startNode); nsCOMPtr rightParent = mHTMLEditor->GetBlock(*endNode); // Are endpoint block parents the same? Use default deletion if (leftParent && leftParent == rightParent) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); } else { // Deleting across blocks. Are the blocks of same type? NS_ENSURE_STATE(leftParent && rightParent); // Are the blocks siblings? nsCOMPtr leftBlockParent = leftParent->GetParentNode(); nsCOMPtr 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))) { if (leftParent->IsHTMLElement(nsGkAtoms::p)) { // First delete the selection NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); // Then join paragraphs, insert break NS_ENSURE_STATE(mHTMLEditor); ::DOMPoint pt = mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent); NS_ENSURE_STATE(pt.node); // Fix up selection res = aSelection->Collapse(pt.node, pt.offset); NS_ENSURE_SUCCESS(res, res); return NS_OK; } if (nsHTMLEditUtils::IsListItem(leftParent) || nsHTMLEditUtils::IsHeader(*leftParent)) { // First delete the selection NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); // Join blocks NS_ENSURE_STATE(mHTMLEditor); ::DOMPoint pt = mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent); NS_ENSURE_STATE(pt.node); // Fix up selection res = aSelection->Collapse(pt.node, pt.offset); NS_ENSURE_SUCCESS(res, res); 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 range = *aSelection->GetRangeAt(rangeIdx); // Build a list of nodes in the range nsTArray> arrayOfNodes; nsTrivialFunctor functor; nsDOMSubtreeIterator iter; nsresult res = iter.Init(*range); NS_ENSURE_SUCCESS(res, res); 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 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 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() > uint32_t(startOffset)) { // Delete to last character OwningNonNull dataNode = *static_cast(startNode.get()); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteText(dataNode, startOffset, startNode->Length() - startOffset); NS_ENSURE_SUCCESS(res, res); } if (endNode->GetAsText() && endOffset) { // Delete to first character NS_ENSURE_STATE(mHTMLEditor); OwningNonNull dataNode = *static_cast(endNode.get()); res = mHTMLEditor->DeleteText(dataNode, 0, endOffset); NS_ENSURE_SUCCESS(res, res); } if (join) { res = JoinBlocks(GetAsDOMNode(leftParent), GetAsDOMNode(rightParent), aCancel); NS_ENSURE_SUCCESS(res, res); } } } } // 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)) { res = aSelection->Collapse(endNode, endOffset); } else { res = aSelection->Collapse(startNode, startOffset); } NS_ENSURE_SUCCESS(res, res); return NS_OK; } /***************************************************************************************************** * 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. * Selection* aSelection the collapsed selection */ nsresult nsHTMLEditRules::InsertBRIfNeeded(Selection* aSelection) { NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); // get selection nsCOMPtr node; int32_t offset; nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); // inline elements don't need any br if (!IsBlockNode(GetAsDOMNode(node))) { return NS_OK; } // examine selection NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject 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 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. * nsINode& aNode the node * nsIEditor::EDirection aAction which edge to find: eNext indicates * beginning, ePrevious ending */ ::DOMPoint nsHTMLEditRules::GetGoodSelPointForNode(nsINode& aNode, nsIEditor::EDirection aAction) { NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint()); if (aNode.GetAsText() || mHTMLEditor->IsContainer(&aNode)) { return ::DOMPoint(&aNode, aAction == nsIEditor::ePrevious ? aNode.Length() : 0); } ::DOMPoint ret; ret.node = aNode.GetParentNode(); ret.offset = ret.node ? ret.node->IndexOf(&aNode) : -1; NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint()); if ((!aNode.IsHTMLElement(nsGkAtoms::br) || mHTMLEditor->IsVisBreak(&aNode)) && aAction == nsIEditor::ePrevious) { ret.offset++; } return ret; } /***************************************************************************************************** * JoinBlocks: 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. * nsCOMPtr *aLeftBlock pointer to the left block * nsCOMPtr *aRightBlock pointer to the right block; will have contents moved to left block * bool *aCanceled return TRUE if we had to cancel operation */ nsresult nsHTMLEditRules::JoinBlocks(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, bool *aCanceled) { NS_ENSURE_ARG_POINTER(aLeftNode && aRightNode); nsCOMPtr aLeftBlock, aRightBlock; if (IsBlockNode(aLeftNode)) { aLeftBlock = aLeftNode; } else if (aLeftNode) { NS_ENSURE_STATE(mHTMLEditor); aLeftBlock = mHTMLEditor->GetBlockNodeParent(aLeftNode); } if (IsBlockNode(aRightNode)) { aRightBlock = aRightNode; } else if (aRightNode) { NS_ENSURE_STATE(mHTMLEditor); aRightBlock = mHTMLEditor->GetBlockNodeParent(aRightNode); } // sanity checks NS_ENSURE_TRUE(aLeftBlock && aRightBlock, NS_ERROR_NULL_POINTER); NS_ENSURE_STATE(aLeftBlock != aRightBlock); if (nsHTMLEditUtils::IsTableElement(aLeftBlock) || nsHTMLEditUtils::IsTableElement(aRightBlock)) { // do not try to merge table elements *aCanceled = true; return NS_OK; } // make sure we don't try to move thing's into HR's, which look like blocks but aren't containers if (nsHTMLEditUtils::IsHR(aLeftBlock)) { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr realLeft = mHTMLEditor->GetBlockNodeParent(aLeftBlock); aLeftBlock = realLeft; } if (nsHTMLEditUtils::IsHR(aRightBlock)) { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr realRight = mHTMLEditor->GetBlockNodeParent(aRightBlock); aRightBlock = realRight; } NS_ENSURE_STATE(aLeftBlock && aRightBlock); // bail if both blocks the same if (aLeftBlock == aRightBlock) { *aCanceled = true; return NS_OK; } // Joining a list item to its parent is a NOP. if (nsHTMLEditUtils::IsList(aLeftBlock) && nsHTMLEditUtils::IsListItem(aRightBlock)) { nsCOMPtr rightParent; aRightBlock->GetParentNode(getter_AddRefs(rightParent)); if (rightParent == aLeftBlock) { 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 bMergeLists = false; nsIAtom* existingList = nsGkAtoms::_empty; int32_t theOffset; nsCOMPtr leftList, rightList; if (nsHTMLEditUtils::IsListItem(aLeftBlock) && nsHTMLEditUtils::IsListItem(aRightBlock)) { aLeftBlock->GetParentNode(getter_AddRefs(leftList)); aRightBlock->GetParentNode(getter_AddRefs(rightList)); if (leftList && rightList && (leftList!=rightList)) { // there are some special complications if the lists are descendants of // the other lists' items. Note that it is ok for them to be descendants // of the other lists themselves, which is the usual case for sublists // in our impllementation. if (!nsEditorUtils::IsDescendantOf(leftList, aRightBlock, &theOffset) && !nsEditorUtils::IsDescendantOf(rightList, aLeftBlock, &theOffset)) { aLeftBlock = leftList; aRightBlock = rightList; bMergeLists = true; NS_ENSURE_STATE(mHTMLEditor); existingList = mHTMLEditor->GetTag(leftList); } } } NS_ENSURE_STATE(mHTMLEditor); nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); nsresult res = NS_OK; int32_t rightOffset = 0; int32_t leftOffset = -1; // theOffset below is where you find yourself in aRightBlock when you traverse upwards // from aLeftBlock if (nsEditorUtils::IsDescendantOf(aLeftBlock, aRightBlock, &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++; NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr leftBlock(do_QueryInterface(aLeftBlock)); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, nsWSRunObject::kBlockEnd, leftBlock); NS_ENSURE_SUCCESS(res, res); { nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(aRightBlock), &rightOffset); nsCOMPtr rightBlock(do_QueryInterface(aRightBlock)); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, nsWSRunObject::kAfterBlock, rightBlock, rightOffset); NS_ENSURE_SUCCESS(res, res); } // Do br adjustment. nsCOMPtr brNode; res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); if (bMergeLists) { // idea here is to take all children in rightList that are past // theOffset, and pull them into leftlist. nsCOMPtr parent(do_QueryInterface(rightList)); NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); nsIContent *child = parent->GetChildAt(theOffset); nsCOMPtr leftList_ = do_QueryInterface(leftList); NS_ENSURE_STATE(leftList_); while (child) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(child, leftList_, -1); NS_ENSURE_SUCCESS(res, res); child = parent->GetChildAt(rightOffset); } } else { res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); } NS_ENSURE_STATE(mHTMLEditor); if (brNode) mHTMLEditor->DeleteNode(brNode); // theOffset below is where you find yourself in aLeftBlock when you traverse upwards // from aRightBlock } else if (nsEditorUtils::IsDescendantOf(aRightBlock, aLeftBlock, &leftOffset)) { // tricky case. right block is inside left block. // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining. NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr rightBlock(do_QueryInterface(aRightBlock)); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, nsWSRunObject::kBlockStart, rightBlock); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); { nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(aLeftBlock), &leftOffset); nsCOMPtr leftBlock(do_QueryInterface(aLeftBlock)); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, nsWSRunObject::kBeforeBlock, leftBlock, leftOffset); NS_ENSURE_SUCCESS(res, res); } // Do br adjustment. nsCOMPtr brNode; res = CheckForInvisibleBR(aLeftBlock, kBeforeBlock, address_of(brNode), leftOffset); NS_ENSURE_SUCCESS(res, res); if (bMergeLists) { res = 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 previousContentParent; if (aLeftNode == aLeftBlock) { // We are working with valid HTML, aLeftNode is a block node, and is // therefore allowed to contain aRightBlock. This is the simple case, // we will simply move the content in aRightBlock out of its block. previousContentParent = aLeftBlock; 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 = nsEditor::GetNodeLocation(aLeftNode, &previousContentOffset); // 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. NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr editorRoot = mHTMLEditor->GetEditorRoot(); if (!editorRoot || aLeftNode != editorRoot->AsDOMNode()) { nsCOMPtr splittedPreviousContent; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParent), &previousContentOffset, nullptr, nullptr, nullptr, address_of(splittedPreviousContent)); NS_ENSURE_SUCCESS(res, res); if (splittedPreviousContent) { previousContentParent = nsEditor::GetNodeLocation(splittedPreviousContent, &previousContentOffset); } } res = MoveBlock(previousContentParent, aRightBlock, previousContentOffset, rightOffset); } NS_ENSURE_STATE(mHTMLEditor); if (brNode) mHTMLEditor->DeleteNode(brNode); } else { // normal case. blocks are siblings, or at least close enough to siblings. An example // of the latter is a

paragraph

  • one
  • two
  • three
. 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 NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr leftBlock(do_QueryInterface(aLeftBlock)); nsCOMPtr rightBlock(do_QueryInterface(aRightBlock)); res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, leftBlock, rightBlock); NS_ENSURE_SUCCESS(res, res); // Do br adjustment. nsCOMPtr brNode; res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); if (bMergeLists || mHTMLEditor->NodesSameType(aLeftBlock, aRightBlock)) { // nodes are same type. merge them. ::DOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock); if (pt.node && bMergeLists) { nsCOMPtr newBlock; res = ConvertListType(aRightBlock, address_of(newBlock), existingList, nsGkAtoms::li); } } else { // nodes are disimilar types. res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); } if (NS_SUCCEEDED(res) && brNode) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(brNode); } } return res; } /***************************************************************************************************** * MoveBlock: this method is used to move the content from rightBlock into leftBlock * Note that the "block" might merely be inline nodes between
s, or between blocks, etc. * DTD containment rules are followed throughout. * nsIDOMNode *aLeftBlock parent to receive moved content * nsIDOMNode *aRightBlock parent to provide moved content * int32_t aLeftOffset offset in aLeftBlock to move content to * int32_t aRightOffset offset in aRightBlock to move content from */ nsresult nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int32_t aLeftOffset, int32_t aRightOffset) { nsTArray> arrayOfNodes; // GetNodesFromPoint is the workhorse that figures out what we wnat to move. nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset), EditAction::makeList, arrayOfNodes, TouchContent::yes); NS_ENSURE_SUCCESS(res, res); for (auto& curNode : arrayOfNodes) { // get the node to act on if (IsBlockNode(GetAsDOMNode(curNode))) { // For block nodes, move their contents only, then delete block. res = MoveContents(GetAsDOMNode(curNode), aLeftBlock, &aLeftOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); } else { // otherwise move the content as is, checking against the dtd. res = MoveNodeSmart(GetAsDOMNode(curNode), aLeftBlock, &aLeftOffset); } } return res; } /***************************************************************************************************** * MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset). * DTD containment rules are followed throughout. aOffset is updated to point _after_ * inserted content. * nsIDOMNode *aSource the selection. * nsIDOMNode *aDest parent to receive moved content * int32_t *aOffset offset in aDest to move content to */ nsresult nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) { nsCOMPtr source = do_QueryInterface(aSource); nsCOMPtr dest = do_QueryInterface(aDest); NS_ENSURE_TRUE(source && dest && aOffset, NS_ERROR_NULL_POINTER); nsresult res; // check if this node can go into the destination node NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->CanContain(*dest, *source)) { // if it can, move it there NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(source, dest, *aOffset); NS_ENSURE_SUCCESS(res, res); if (*aOffset != -1) ++(*aOffset); } else { // if it can't, move its children, and then delete it. res = MoveContents(aSource, aDest, aOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(aSource); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } /***************************************************************************************************** * MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset). * DTD containment rules are followed throughout. aOffset is updated to point _after_ * inserted content. aSource is deleted. * nsIDOMNode *aSource the selection. * nsIDOMNode *aDest parent to receive moved content * int32_t *aOffset offset in aDest to move content to */ nsresult nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) { NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE; NS_ENSURE_STATE(mHTMLEditor); NS_ASSERTION(!mHTMLEditor->IsTextNode(aSource), "#text does not have contents"); nsCOMPtr child; nsAutoString tag; nsresult res; aSource->GetFirstChild(getter_AddRefs(child)); while (child) { res = MoveNodeSmart(child, aDest, aOffset); NS_ENSURE_SUCCESS(res, res); aSource->GetFirstChild(getter_AddRefs(child)); } return NS_OK; } nsresult nsHTMLEditRules::DeleteNonTableElements(nsINode* aNode) { MOZ_ASSERT(aNode); if (!nsHTMLEditUtils::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 nsHTMLEditRules::DidDeleteSelection(Selection* aSelection, nsIEditor::EDirection aDir, nsresult aResult) { if (!aSelection) { return NS_ERROR_NULL_POINTER; } // find where we are nsCOMPtr startNode; int32_t startOffset; nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); // find any enclosing mailcite nsCOMPtr 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 parent = nsEditor::GetNodeLocation(citeNode, &offset); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(citeNode); NS_ENSURE_SUCCESS(res, res); if (parent && seenBR) { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr brNode = mHTMLEditor->CreateBR(parent, offset); NS_ENSURE_STATE(brNode); aSelection->Collapse(parent, offset); } } } // call through to base class return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult); } nsresult nsHTMLEditRules::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 listType = do_GetAtom(*aListType); nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; *aHandled = false; // deduce what tag to use for list items nsCOMPtr itemType; if (aItemType) { itemType = do_GetAtom(*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; res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsTArray> arrayOfNodes; res = GetListActionNodes(arrayOfNodes, aEntireList ? EntireList::yes : EntireList::no); NS_ENSURE_SUCCESS(res, res); // check if all our nodes are
s, or empty inlines bool bOnlyBreaks = true; for (auto& curNode : arrayOfNodes) { // if curNode is not a Break or empty inline, we're done if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(GetAsDOMNode(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.Length() || bOnlyBreaks) { // if only breaks, delete them if (bOnlyBreaks) { for (auto& node : arrayOfNodes) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(node); NS_ENSURE_SUCCESS(res, res); } } // get selection location NS_ENSURE_STATE(aSelection->RangeCount()); nsCOMPtr 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; } res = SplitAsNeeded(listType, parent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theList = mHTMLEditor->CreateNode(listType, parent, offset); NS_ENSURE_STATE(theList); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theListItem = mHTMLEditor->CreateNode(itemType, theList, 0); NS_ENSURE_STATE(theListItem); // remember our new block for postprocessing mNewBlock = GetAsDOMNode(theListItem); // put selection in new list item res = aSelection->Collapse(theListItem, 0); // to prevent selection resetter from overriding us selectionResetter.Abort(); *aHandled = true; return res; } // 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 curParent; nsCOMPtr curList, prevListItem; for (uint32_t i = 0; i < listCount; i++) { // here's where we actually figure out what to do nsCOMPtr newBlock; NS_ENSURE_STATE(arrayOfNodes[i]->IsContent()); nsCOMPtr curNode = arrayOfNodes[i]->AsContent(); int32_t offset; curParent = nsEditor::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 (nsTextEditUtils::IsBreak(curNode)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); prevListItem = 0; continue; } else if (IsEmptyInline(GetAsDOMNode(curNode))) { // if curNode is an empty inline container, delete it NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); continue; } if (nsHTMLEditUtils::IsList(curNode)) { // do we have a curList already? if (curList && !nsEditorUtils::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); res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); res = ConvertListType(GetAsDOMNode(curNode), address_of(newBlock), listType, itemType); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(newBlock); NS_ENSURE_SUCCESS(res, res); } else { // replace list with new list type res = ConvertListType(GetAsDOMNode(curNode), address_of(newBlock), listType, itemType); NS_ENSURE_SUCCESS(res, res); curList = do_QueryInterface(newBlock); } prevListItem = 0; continue; } if (nsHTMLEditUtils::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 || nsEditorUtils::IsDescendantOf(curNode, curList)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SplitNode(curParent->AsDOMNode(), offset, getter_AddRefs(newBlock)); NS_ENSURE_SUCCESS(res, res); int32_t offset; nsCOMPtr parent = nsEditor::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); res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); // convert list item type if needed NS_ENSURE_STATE(mHTMLEditor); if (!curNode->IsHTMLElement(itemType)) { NS_ENSURE_STATE(mHTMLEditor); newBlock = dont_AddRef(GetAsDOMNode( mHTMLEditor->ReplaceContainer(curNode->AsElement(), itemType).take())); 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); res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); } NS_ENSURE_STATE(mHTMLEditor); if (!curNode->IsHTMLElement(itemType)) { NS_ENSURE_STATE(mHTMLEditor); newBlock = dont_AddRef(GetAsDOMNode( mHTMLEditor->ReplaceContainer(curNode->AsElement(), itemType).take())); NS_ENSURE_STATE(newBlock); } } nsCOMPtr curElement = do_QueryInterface(curNode); NS_NAMED_LITERAL_STRING(typestr, "type"); if (aBulletType && !aBulletType->IsEmpty()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveAttribute(curElement, typestr); } NS_ENSURE_SUCCESS(res, res); 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); res = mHTMLEditor->RemoveContainer(curNode); NS_ENSURE_SUCCESS(res, res); listCount = arrayOfNodes.Length(); continue; } // need to make a list to put things in if we haven't already, if (!curList) { res = SplitAsNeeded(listType, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curList = mHTMLEditor->CreateNode(listType, curParent, offset); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = GetAsDOMNode(curList); // curList is now the correct thing to put curNode in prevListItem = 0; } // if curNode isn't a list item, we must wrap it in one nsCOMPtr listItem; if (!nsHTMLEditUtils::IsListItem(curNode)) { if (IsInlineNode(GetAsDOMNode(curNode)) && prevListItem) { // this is a continuation of some inline nodes that belong together in // the same list item. use prevListItem NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, prevListItem, -1); NS_ENSURE_SUCCESS(res, res); } 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(GetAsDOMNode(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); res = mHTMLEditor->MoveNode(listItem, curList, -1); NS_ENSURE_SUCCESS(res, res); } } return res; } nsresult nsHTMLEditRules::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 res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsTArray> arrayOfRanges; GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::makeList); // use these ranges to contruct a list of nodes to act on. nsTArray> arrayOfNodes; res = GetListActionNodes(arrayOfNodes, EntireList::no); NS_ENSURE_SUCCESS(res, res); // Remove all non-editable nodes. Leave them be. int32_t listCount = arrayOfNodes.Length(); int32_t i; for (i=listCount-1; i>=0; i--) { OwningNonNull testNode = arrayOfNodes[i]; NS_ENSURE_STATE(mHTMLEditor); if (!mHTMLEditor->IsEditable(testNode)) { arrayOfNodes.RemoveElementAt(i); } } // reset list count listCount = arrayOfNodes.Length(); // 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 (nsHTMLEditUtils::IsListItem(curNode)) // unlist this listitem { bool bOutOfList; do { res = PopListItem(GetAsDOMNode(curNode), &bOutOfList); NS_ENSURE_SUCCESS(res, res); } while (!bOutOfList); // keep popping it out until it's not in a list anymore } else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, move list items out { res = RemoveListStructure(GetAsDOMNode(curNode)); NS_ENSURE_SUCCESS(res, res); } } return res; } nsresult nsHTMLEditRules::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 nsHTMLEditRules::WillMakeBasicBlock(Selection* aSelection, const nsAString *aBlockType, bool *aCancel, bool *aHandled) { OwningNonNull blockType = do_GetAtom(*aBlockType); if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = false; *aHandled = false; nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); NS_ENSURE_STATE(mHTMLEditor); nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); *aHandled = true; nsString tString(*aBlockType); // contruct a list of nodes to act on. nsTArray> arrayOfNodes; res = GetNodesFromSelection(*aSelection, EditAction::makeBasicBlock, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // Remove all non-editable nodes. Leave them be. int32_t listCount = arrayOfNodes.Length(); int32_t i; for (i=listCount-1; i>=0; i--) { NS_ENSURE_STATE(mHTMLEditor); if (!mHTMLEditor->IsEditable(arrayOfNodes[i])) { arrayOfNodes.RemoveElementAt(i); } } // reset list count listCount = arrayOfNodes.Length(); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { nsCOMPtr theBlock; // get selection location NS_ENSURE_STATE(aSelection->RangeCount()); nsCOMPtr parent = aSelection->GetRangeAt(0)->GetStartParent(); int32_t offset = aSelection->GetRangeAt(0)->StartOffset(); NS_ENSURE_STATE(parent); if (tString.EqualsLiteral("normal") || tString.IsEmpty() ) // we are removing blocks (going to "body text") { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr curBlock = mHTMLEditor->GetBlock(*parent); NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER); nsCOMPtr curBlockPar = GetAsDOMNode(curBlock->GetParentNode()); if (nsHTMLEditUtils::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 brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(parent->AsDOMNode(), offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); if (brNode && nsTextEditUtils::IsBreak(brNode)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); } // do the splits! NS_ENSURE_STATE(mHTMLEditor); offset = mHTMLEditor->SplitNodeDeep(*curBlock, *parent->AsContent(), offset, nsHTMLEditor::EmptyContainers::no); NS_ENSURE_STATE(offset != -1); // put a br at the split point NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); // put selection at the split point res = aSelection->Collapse(curBlockPar, offset); selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = true; } // else nothing to do! } else // we are making a block { // consume a br, if needed NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr brNode = mHTMLEditor->GetNextHTMLNode(parent, offset, true); NS_ENSURE_SUCCESS(res, res); if (brNode && nsTextEditUtils::IsBreak(brNode)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); // we don't need to act on this node any more arrayOfNodes.RemoveElement(brNode); } // make sure we can put a block here res = SplitAsNeeded(blockType, parent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); theBlock = dont_AddRef(GetAsDOMNode( mHTMLEditor->CreateNode(blockType, parent, offset).take())); 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 curNode = arrayOfNodes[0]; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveElementAt(0); } // put selection in new block res = aSelection->Collapse(theBlock,0); selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = true; } return res; } else { // Ok, now go through all the nodes and make the right kind of blocks, // or whatever is approriate. Wohoo! // Note: blockquote is handled a little differently if (tString.EqualsLiteral("blockquote")) { res = MakeBlockquote(arrayOfNodes); } else if (tString.EqualsLiteral("normal") || tString.IsEmpty()) { res = RemoveBlockStyle(arrayOfNodes); } else { res = ApplyBlockStyle(arrayOfNodes, *blockType); } return res; } return res; } nsresult nsHTMLEditRules::DidMakeBasicBlock(Selection* aSelection, nsRulesInfo *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; } nsCOMPtr parent; int32_t offset; nsresult res = nsEditor::GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = InsertMozBRIfNeeded(parent); return res; } nsresult nsHTMLEditRules::WillIndent(Selection* aSelection, bool* aCancel, bool* aHandled) { nsresult res; NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsCSSEnabled()) { res = WillCSSIndent(aSelection, aCancel, aHandled); } else { res = WillHTMLIndent(aSelection, aCancel, aHandled); } return res; } nsresult nsHTMLEditRules::WillCSSIndent(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; *aHandled = true; res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsTArray> arrayOfRanges; nsTArray> arrayOfNodes; // short circuit: detect case of collapsed selection inside an
  • . // just sublist that
  • . This prevents bug 97797. nsCOMPtr liNode; if (aSelection->Collapsed()) { nsCOMPtr node; int32_t offset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); NS_ENSURE_SUCCESS(res, res); nsCOMPtr block = mHTMLEditor->GetBlock(*node); if (block && nsHTMLEditUtils::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 res = GetNodesFromSelection(*aSelection, EditAction::indent, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); } // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { // get selection location NS_ENSURE_STATE(aSelection->RangeCount()); nsCOMPtr parent = aSelection->GetRangeAt(0)->GetStartParent(); int32_t offset = aSelection->GetRangeAt(0)->StartOffset(); NS_ENSURE_STATE(parent); // make sure we can put a block here res = SplitAsNeeded(*nsGkAtoms::div, parent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theBlock = mHTMLEditor->CreateNode(nsGkAtoms::div, parent, offset); NS_ENSURE_STATE(theBlock); // remember our new block for postprocessing mNewBlock = theBlock->AsDOMNode(); RelativeChangeIndentationOfElementNode(theBlock->AsDOMNode(), +1); // delete anything that was in the list of nodes while (!arrayOfNodes.IsEmpty()) { OwningNonNull curNode = arrayOfNodes[0]; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveElementAt(0); } // put selection in new block res = aSelection->Collapse(theBlock,0); selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = true; return res; } // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! int32_t i; nsCOMPtr curParent; nsCOMPtr curList, curQuote; nsCOMPtr sibling; int32_t listCount = arrayOfNodes.Length(); for (i=0; iIsContent()); nsCOMPtr 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 (nsHTMLEditUtils::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 && nsHTMLEditUtils::IsList(sibling)) { if (curParent->NodeInfo()->NameAtom() == sibling->NodeInfo()->NameAtom() && curParent->NodeInfo()->NamespaceID() == sibling->NodeInfo()->NamespaceID()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, sibling, 0); NS_ENSURE_SUCCESS(res, res); 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 && nsHTMLEditUtils::IsList(sibling)) { if (curParent->NodeInfo()->NameAtom() == sibling->NodeInfo()->NameAtom() && curParent->NodeInfo()->NamespaceID() == sibling->NodeInfo()->NamespaceID()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, sibling, -1); NS_ENSURE_SUCCESS(res, res); 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 res = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_SUCCESS(res, res); 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->AsDOMNode(); } // tuck the node into the end of the active list uint32_t listLen = curList->Length(); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, curList, listLen); NS_ENSURE_SUCCESS(res, res); } else // not a list item { if (IsBlockNode(curNode->AsDOMNode())) { RelativeChangeIndentationOfElementNode(curNode->AsDOMNode(), +1); curQuote = nullptr; } else { if (!curQuote) { // First, check that our element can contain a div. if (!mEditor->CanContainTag(*curParent, *nsGkAtoms::div)) { return NS_OK; // cancelled } res = SplitAsNeeded(*nsGkAtoms::div, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curQuote = mHTMLEditor->CreateNode(nsGkAtoms::div, curParent, offset); NS_ENSURE_STATE(curQuote); RelativeChangeIndentationOfElementNode(curQuote->AsDOMNode(), +1); // remember our new block for postprocessing mNewBlock = curQuote->AsDOMNode(); // 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); res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen); NS_ENSURE_SUCCESS(res, res); } } } return res; } nsresult nsHTMLEditRules::WillHTMLIndent(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; *aHandled = true; res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(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> arrayOfRanges; GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::indent); // use these ranges to contruct a list of nodes to act on. nsTArray> arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent); NS_ENSURE_SUCCESS(res, res); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { // get selection location NS_ENSURE_STATE(aSelection->RangeCount()); nsCOMPtr parent = aSelection->GetRangeAt(0)->GetStartParent(); int32_t offset = aSelection->GetRangeAt(0)->StartOffset(); NS_ENSURE_STATE(parent); // make sure we can put a block here res = SplitAsNeeded(*nsGkAtoms::blockquote, parent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, parent, offset); NS_ENSURE_STATE(theBlock); // remember our new block for postprocessing mNewBlock = theBlock->AsDOMNode(); // delete anything that was in the list of nodes while (!arrayOfNodes.IsEmpty()) { OwningNonNull curNode = arrayOfNodes[0]; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveElementAt(0); } // put selection in new block res = aSelection->Collapse(theBlock,0); selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = true; return res; } // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! int32_t i; nsCOMPtr curParent; nsCOMPtr sibling; nsCOMPtr curList, curQuote, indentedLI; int32_t listCount = arrayOfNodes.Length(); for (i=0; iIsContent()); nsCOMPtr 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 (nsHTMLEditUtils::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 && nsHTMLEditUtils::IsList(sibling) && curParent->NodeInfo()->NameAtom() == sibling->NodeInfo()->NameAtom() && curParent->NodeInfo()->NamespaceID() == sibling->NodeInfo()->NamespaceID()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, sibling, 0); NS_ENSURE_SUCCESS(res, res); 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 && nsHTMLEditUtils::IsList(sibling) && curParent->NodeInfo()->NameAtom() == sibling->NodeInfo()->NameAtom() && curParent->NodeInfo()->NamespaceID() == sibling->NodeInfo()->NamespaceID()) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, sibling, -1); NS_ENSURE_SUCCESS(res, res); 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 res = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_SUCCESS(res, res); 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->AsDOMNode(); } // tuck the node into the end of the active list NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); // forget curQuote, if any curQuote = nullptr; } 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 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 res = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_STATE(curList); } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(listItem, curList, -1); NS_ENSURE_SUCCESS(res, res); // 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 (!mEditor->CanContainTag(*curParent, *nsGkAtoms::blockquote)) { return NS_OK; // cancelled } res = SplitAsNeeded(*nsGkAtoms::blockquote, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curQuote = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent, offset); NS_ENSURE_STATE(curQuote); // remember our new block for postprocessing mNewBlock = curQuote->AsDOMNode(); // curQuote is now the correct thing to put curNode in } // tuck the node into the end of the active blockquote NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, curQuote, -1); NS_ENSURE_SUCCESS(res, res); // forget curList, if any curList = nullptr; } } } return res; } nsresult nsHTMLEditRules::WillOutdent(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } // initialize out param *aCancel = false; *aHandled = true; nsresult res = NS_OK; nsCOMPtr rememberedLeftBQ, rememberedRightBQ; NS_ENSURE_STATE(mHTMLEditor); bool useCSS = mHTMLEditor->IsCSSEnabled(); res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); // some scoping for selection resetting - we may need to tweak it { NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(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> arrayOfNodes; res = GetNodesFromSelection(*aSelection, EditAction::outdent, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // Ok, now go through all the nodes and remove a level of blockquoting, // or whatever is appropriate. Wohoo! nsCOMPtr curBlockQuote, firstBQChild, lastBQChild; bool curBlockQuoteIsIndentedWithCSS = false; for (auto& curNode : arrayOfNodes) { // here's where we actually figure out what to do nsCOMPtr 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) { res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, curBlockQuoteIsIndentedWithCSS, address_of(rememberedLeftBQ), address_of(rememberedRightBQ)); NS_ENSURE_SUCCESS(res, res); curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; curBlockQuoteIsIndentedWithCSS = false; } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(GetAsDOMNode(curNode)); NS_ENSURE_SUCCESS(res, res); continue; } // is it a block with a 'margin' property? if (useCSS && IsBlockNode(GetAsDOMNode(curNode))) { NS_ENSURE_STATE(mHTMLEditor); nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, GetAsDOMNode(curNode)); nsAutoString value; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(*curNode, *marginProperty, value); float f; nsCOMPtr unit; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (f > 0) { RelativeChangeIndentationOfElementNode(GetAsDOMNode(curNode), -1); continue; } } // is it a list item? if (nsHTMLEditUtils::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) { res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, curBlockQuoteIsIndentedWithCSS, address_of(rememberedLeftBQ), address_of(rememberedRightBQ)); NS_ENSURE_SUCCESS(res, res); curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; curBlockQuoteIsIndentedWithCSS = false; } bool bOutOfList; res = PopListItem(GetAsDOMNode(curNode), &bOutOfList); NS_ENSURE_SUCCESS(res, res); continue; } // do we have a blockquote that we are already committed to removing? if (curBlockQuote) { // if so, is this node a descendant? if (nsEditorUtils::IsDescendantOf(GetAsDOMNode(curNode), curBlockQuote)) { lastBQChild = GetAsDOMNode(curNode); continue; // then we don't need to do anything different for this node } else { // otherwise, we have progressed beyond end of curBlockQuote, // so lets handle it now. We need to remove the portion of // curBlockQuote that contains [firstBQChild - lastBQChild]. res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, curBlockQuoteIsIndentedWithCSS, address_of(rememberedLeftBQ), address_of(rememberedRightBQ)); NS_ENSURE_SUCCESS(res, res); curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; curBlockQuoteIsIndentedWithCSS = false; // fall out and handle curNode } } // are we inside a blockquote? nsCOMPtr 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) && mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(n) && (n->IsHTMLElement(nsGkAtoms::table) || !nsHTMLEditUtils::IsTableElement(n))) { if (!n->GetParentNode()) { break; } n = n->GetParentNode(); if (n->IsHTMLElement(nsGkAtoms::blockquote)) { // if so, remember it, and remember first node we are taking out of it. curBlockQuote = GetAsDOMNode(n); firstBQChild = GetAsDOMNode(curNode); lastBQChild = GetAsDOMNode(curNode); break; } else if (useCSS) { NS_ENSURE_STATE(mHTMLEditor); nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, GetAsDOMNode(curNode)); nsAutoString value; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(*n, *marginProperty, value); float f; nsCOMPtr unit; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (f > 0 && !(nsHTMLEditUtils::IsList(curParent) && nsHTMLEditUtils::IsList(curNode))) { curBlockQuote = GetAsDOMNode(n); firstBQChild = GetAsDOMNode(curNode); lastBQChild = GetAsDOMNode(curNode); curBlockQuoteIsIndentedWithCSS = true; break; } } } if (!curBlockQuote) { // could not find an enclosing blockquote for this node. handle list cases. if (nsHTMLEditUtils::IsList(curParent)) // move node out of list { if (nsHTMLEditUtils::IsList(curNode)) // just unwrap this sublist { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(GetAsDOMNode(curNode)); NS_ENSURE_SUCCESS(res, res); } // handled list item case above } else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out { nsCOMPtr child = curNode->GetLastChild(); while (child) { if (nsHTMLEditUtils::IsListItem(child)) { bool bOutOfList; res = PopListItem(GetAsDOMNode(child), &bOutOfList); NS_ENSURE_SUCCESS(res, res); } else if (nsHTMLEditUtils::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. NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(child, curParent, offset + 1); NS_ENSURE_SUCCESS(res, res); } else { // delete any non- list items for now NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(child); NS_ENSURE_SUCCESS(res, res); } child = curNode->GetLastChild(); } // delete the now-empty list NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(GetAsDOMNode(curNode)); NS_ENSURE_SUCCESS(res, res); } else if (useCSS) { nsCOMPtr element; nsCOMPtr textNode = do_QueryInterface(curNode); if (textNode) { // We want to outdent the parent of text nodes nsCOMPtr parent; textNode->GetParentNode(getter_AddRefs(parent)); element = do_QueryInterface(parent); } else { element = do_QueryInterface(curNode); } if (element) { RelativeChangeIndentationOfElementNode(element, -1); } } } } if (curBlockQuote) { // we have a blockquote we haven't finished handling res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, curBlockQuoteIsIndentedWithCSS, address_of(rememberedLeftBQ), address_of(rememberedRightBQ)); NS_ENSURE_SUCCESS(res, res); } } // 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 nsCOMPtr sNode; int32_t sOffset; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); if (rememberedLeftBQ && ((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ))) { // selection is inside rememberedLeftBQ - push it past it. sNode = nsEditor::GetNodeLocation(rememberedLeftBQ, &sOffset); sOffset++; aSelection->Collapse(sNode, sOffset); } // and pull selection before beginning of rememberedRightBQ NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); if (rememberedRightBQ && ((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ))) { // selection is inside rememberedRightBQ - push it before it. sNode = nsEditor::GetNodeLocation(rememberedRightBQ, &sOffset); aSelection->Collapse(sNode, sOffset); } } return NS_OK; } return res; } /////////////////////////////////////////////////////////////////////////////// // RemovePartOfBlock: Split aBlock and move aStartChild to aEndChild out of // aBlock. nsresult nsHTMLEditRules::RemovePartOfBlock(Element& aBlock, nsIContent& aStartChild, nsIContent& aEndChild) { nsresult res = SplitBlock(aBlock.AsDOMNode(), aStartChild.AsDOMNode(), aEndChild.AsDOMNode()); NS_ENSURE_SUCCESS(res, res); // Get rid of part of blockquote we are outdenting NS_ENSURE_STATE(mHTMLEditor); return mHTMLEditor->RemoveBlockContainer(aBlock.AsDOMNode()); } nsresult nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock, nsIDOMNode *aStartChild, nsIDOMNode *aEndChild, nsCOMPtr *aLeftNode, nsCOMPtr *aRightNode, nsCOMPtr *aMiddleNode) { NS_ENSURE_TRUE(aBlock && aStartChild && aEndChild, NS_ERROR_NULL_POINTER); nsCOMPtr leftNode, rightNode; int32_t startOffset, endOffset; // get split point location nsCOMPtr startParent = nsEditor::GetNodeLocation(aStartChild, &startOffset); // do the splits! nsCOMPtr block = do_QueryInterface(aBlock); NS_ENSURE_STATE(block || !aBlock); nsCOMPtr startParentContent = do_QueryInterface(startParent); NS_ENSURE_STATE(startParentContent || !startParent); NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->SplitNodeDeep(*block, *startParentContent, startOffset, nsHTMLEditor::EmptyContainers::no, getter_AddRefs(leftNode), getter_AddRefs(rightNode)); if (rightNode) { aBlock = GetAsDOMNode(rightNode); } // remember left portion of block if caller requested if (aLeftNode) *aLeftNode = GetAsDOMNode(leftNode); // get split point location nsCOMPtr endParent = nsEditor::GetNodeLocation(aEndChild, &endOffset); endOffset++; // want to be after lastBQChild // do the splits! nsCOMPtr endParentContent = do_QueryInterface(endParent); NS_ENSURE_STATE(endParentContent || !endParent); NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->SplitNodeDeep(*block, *endParentContent, endOffset, nsHTMLEditor::EmptyContainers::no, getter_AddRefs(leftNode), getter_AddRefs(rightNode)); if (leftNode) { aBlock = GetAsDOMNode(leftNode); } // remember right portion of block if caller requested if (aRightNode) *aRightNode = GetAsDOMNode(rightNode); if (aMiddleNode) *aMiddleNode = aBlock; return NS_OK; } nsresult nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock, nsIDOMNode *aStartChild, nsIDOMNode *aEndChild, bool aIsBlockIndentedWithCSS, nsCOMPtr *aLeftNode, nsCOMPtr *aRightNode) { nsCOMPtr middleNode; nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, aLeftNode, aRightNode, address_of(middleNode)); NS_ENSURE_SUCCESS(res, res); if (aIsBlockIndentedWithCSS) { res = RelativeChangeIndentationOfElementNode(middleNode, -1); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(middleNode); } return res; } /////////////////////////////////////////////////////////////////////////// // ConvertListType: convert list type and list item type. // // nsresult nsHTMLEditRules::ConvertListType(nsIDOMNode* aList, nsCOMPtr* outList, nsIAtom* aListType, nsIAtom* aItemType) { MOZ_ASSERT(aListType); MOZ_ASSERT(aItemType); NS_ENSURE_TRUE(aList && outList, NS_ERROR_NULL_POINTER); nsCOMPtr list = do_QueryInterface(aList); NS_ENSURE_STATE(list); nsCOMPtr outNode; nsresult rv = ConvertListType(list, getter_AddRefs(outNode), aListType, aItemType); *outList = outNode ? outNode->AsDOMNode() : nullptr; return rv; } nsresult nsHTMLEditRules::ConvertListType(Element* aList, dom::Element** aOutList, nsIAtom* aListType, nsIAtom* aItemType) { MOZ_ASSERT(aList); MOZ_ASSERT(aOutList); MOZ_ASSERT(aListType); MOZ_ASSERT(aItemType); nsCOMPtr child = aList->GetFirstChild(); while (child) { if (child->IsElement()) { dom::Element* element = child->AsElement(); if (nsHTMLEditUtils::IsListItem(element) && !element->IsHTMLElement(aItemType)) { child = mHTMLEditor->ReplaceContainer(element, aItemType); NS_ENSURE_STATE(child); } else if (nsHTMLEditUtils::IsList(element) && !element->IsHTMLElement(aListType)) { nsCOMPtr 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 list = aList->AsElement(); list.forget(aOutList); return NS_OK; } *aOutList = mHTMLEditor->ReplaceContainer(aList, aListType).take(); NS_ENSURE_STATE(aOutList); return NS_OK; } /////////////////////////////////////////////////////////////////////////// // CreateStyleForInsertText: take care of clearing and setting appropriate // style nodes for text insertion. // // nsresult nsHTMLEditRules::CreateStyleForInsertText(Selection* aSelection, nsIDOMDocument *aDoc) { MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState); bool weDidSomething = false; nsCOMPtr node, tmp; int32_t offset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); NS_ENSURE_SUCCESS(res, res); // 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); res = mHTMLEditor->GetInlinePropertyBase(*propItem->tag, &propItem->attr, nullptr, &bFirst, &bAny, &bAll, &curValue, false); NS_ENSURE_SUCCESS(res, res); if (!bAny) { // no style set for this prop/attr mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, propItem->value); } } nsCOMPtr rootElement; res = aDoc->GetDocumentElement(getter_AddRefs(rootElement)); NS_ENSURE_SUCCESS(res, res); // process clearing any styles first nsAutoPtr item(mHTMLEditor->mTypeInState->TakeClearProperty()); while (item && node != rootElement) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->ClearStyle(address_of(node), &offset, item->tag, &item->attr); NS_ENSURE_SUCCESS(res, res); 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 (mHTMLEditor->IsTextNode(node)) { // if we are in a text node, split it NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr content = do_QueryInterface(node); NS_ENSURE_STATE(content || !node); offset = mHTMLEditor->SplitNodeDeep(*content, *content, offset); NS_ENSURE_STATE(offset != -1); node->GetParentNode(getter_AddRefs(tmp)); node = tmp; } if (!mHTMLEditor->IsContainer(node)) { return NS_OK; } nsCOMPtr newNode; nsCOMPtr nodeAsText; res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER); newNode = do_QueryInterface(nodeAsText); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->InsertNode(newNode, node, offset); NS_ENSURE_SUCCESS(res, res); node = newNode; offset = 0; weDidSomething = true; if (relFontSize) { // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller int32_t dir = relFontSize > 0 ? 1 : -1; for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, 0, -1); NS_ENSURE_SUCCESS(res, res); } } while (item) { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr content = do_QueryInterface(node); NS_ENSURE_STATE(content || !node); res = mHTMLEditor->SetInlinePropertyOnNode(*content, *item->tag, &item->attr, item->value); NS_ENSURE_SUCCESS(res, res); item = mHTMLEditor->mTypeInState->TakeSetProperty(); } } if (weDidSomething) { return aSelection->Collapse(node, offset); } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsEmptyBlock: 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 nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, bool *outIsEmptyBlock, bool aMozBRDoesntCount, bool aListItemsNotEmpty) { NS_ENSURE_TRUE(aNode && outIsEmptyBlock, NS_ERROR_NULL_POINTER); *outIsEmptyBlock = true; // nsresult res = NS_OK; nsCOMPtr nodeToTest; if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode); // else nsCOMPtr block; // looks like I forgot to finish this. Wonder what I was going to do? NS_ENSURE_TRUE(nodeToTest, NS_ERROR_NULL_POINTER); return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock, aMozBRDoesntCount, aListItemsNotEmpty); } nsresult nsHTMLEditRules::WillAlign(Selection* aSelection, const nsAString *alignType, bool *aCancel, bool *aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; *aHandled = false; res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); nsAutoSelectionReset selectionResetter(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 *aHandled = true; nsTArray> nodeArray; res = GetNodesFromSelection(*aSelection, EditAction::align, nodeArray); NS_ENSURE_SUCCESS(res, res); // 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 = false; int32_t listCount = nodeArray.Length(); if (!listCount) emptyDiv = true; if (listCount == 1) { OwningNonNull theNode = nodeArray[0]; if (nsHTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(theNode))) { // the node is a table element, an horiz rule, 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 nsCOMPtr theElem = do_QueryInterface(theNode); res = AlignBlock(theElem, alignType, true); NS_ENSURE_SUCCESS(res, res); return NS_OK; } if (nsTextEditUtils::IsBreak(theNode)) { // 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 . // 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 nsCOMPtr parent; int32_t offset; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) emptyDiv = true; } } if (emptyDiv) { nsCOMPtr brNode, sib; NS_NAMED_LITERAL_STRING(divType, "div"); NS_ENSURE_STATE(aSelection->GetRangeAt(0)); nsCOMPtr parent = aSelection->GetRangeAt(0)->GetStartParent(); int32_t offset = aSelection->GetRangeAt(0)->StartOffset(); NS_ENSURE_STATE(parent); res = SplitAsNeeded(*nsGkAtoms::div, parent, offset); NS_ENSURE_SUCCESS(res, res); // consume a trailing br, if any. This is to keep an alignment from // creating extra lines, if possible. NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(GetAsDOMNode(parent), offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); if (brNode && nsTextEditUtils::IsBreak(brNode)) { // 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 its safe to consume it. NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLSibling(GetAsDOMNode(parent), offset, address_of(sib)); NS_ENSURE_SUCCESS(res, res); if (!IsBlockNode(sib)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); } } NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, parent, offset); NS_ENSURE_STATE(theDiv); // remember our new block for postprocessing mNewBlock = GetAsDOMNode(theDiv); // set up the alignment on the div, using HTML or CSS nsCOMPtr divElem = do_QueryInterface(theDiv); res = AlignBlock(divElem, alignType, true); NS_ENSURE_SUCCESS(res, res); *aHandled = true; // put in a moz-br so that it won't get deleted res = CreateMozBR(GetAsDOMNode(theDiv), 0); NS_ENSURE_SUCCESS(res, res); res = aSelection->Collapse(theDiv, 0); selectionResetter.Abort(); // don't reset our selection in this case. return res; } // 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 transitionList; MakeTransitionList(nodeArray, transitionList); // Ok, now go through all the nodes and give them an align attrib or put them in a div, // or whatever is appropriate. Wohoo! nsCOMPtr curParent; nsCOMPtr curDiv; bool useCSS = mHTMLEditor->IsCSSEnabled(); for (int32_t i = 0; i < listCount; ++i) { // here's where we actually figure out what to do nsCOMPtr curNode = nodeArray[i]->AsDOMNode(); nsCOMPtr curContent = do_QueryInterface(curNode); NS_ENSURE_STATE(curContent); // Ignore all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(curNode)) continue; curParent = curContent->GetParentNode(); int32_t offset = curParent ? curParent->IndexOf(curContent) : -1; // the node is a table element, an horiz rule, 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 (nsHTMLEditUtils::SupportsAlignAttr(curNode)) { nsCOMPtr curElem = do_QueryInterface(curNode); res = AlignBlock(curElem, alignType, false); NS_ENSURE_SUCCESS(res, res); // clear out curDiv so that we don't put nodes after this one into it curDiv = 0; continue; } // Skip insignificant formatting text nodes to prevent // unnecessary structure splitting! bool isEmptyTextNode = false; if (nsEditor::IsTextNode(curNode) && ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(GetAsDOMNode(curParent))) || nsHTMLEditUtils::IsList(curParent) || (NS_SUCCEEDED(mHTMLEditor->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 ( nsHTMLEditUtils::IsListItem(curNode) || nsHTMLEditUtils::IsList(curNode)) { res = RemoveAlignment(curNode, *alignType, true); NS_ENSURE_SUCCESS(res, res); if (useCSS) { NS_NAMED_LITERAL_STRING(attrName, "align"); int32_t count; mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nullptr, &attrName, alignType, &count, false); curDiv = 0; continue; } else if (nsHTMLEditUtils::IsList(curParent)) { // if we don't use CSS, add a contraint to list element : they have // to be inside another list, ie >= second level of nesting res = AlignInnerBlocks(*curContent, alignType); NS_ENSURE_SUCCESS(res, res); curDiv = 0; 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 (!mEditor->CanContainTag(*curParent, *nsGkAtoms::div)) { return NS_OK; // cancelled } res = SplitAsNeeded(*nsGkAtoms::div, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, curParent, offset); NS_ENSURE_STATE(curDiv); // remember our new block for postprocessing mNewBlock = GetAsDOMNode(curDiv); // set up the alignment on the div nsCOMPtr divElem = do_QueryInterface(curDiv); res = AlignBlock(divElem, alignType, true); //nsAutoString attr(NS_LITERAL_STRING("align")); //res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); //NS_ENSURE_SUCCESS(res, res); // curDiv is now the correct thing to put curNode in } // tuck the node into the end of the active div NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curContent, curDiv, -1); NS_ENSURE_SUCCESS(res, res); } return res; } /////////////////////////////////////////////////////////////////////////////// // AlignInnerBlocks: Align inside table cells or list items // nsresult nsHTMLEditRules::AlignInnerBlocks(nsINode& aNode, const nsAString* alignType) { NS_ENSURE_TRUE(alignType, NS_ERROR_NULL_POINTER); // Gather list of table cells or list items nsTArray> nodeArray; nsTableCellAndListItemFunctor functor; nsDOMIterator iter(aNode); iter.AppendList(functor, nodeArray); // Now that we have the list, align their contents as requested for (auto& node : nodeArray) { nsresult res = AlignBlockContents(GetAsDOMNode(node), alignType); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // AlignBlockContents: align contents of a block element // nsresult nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node && alignType, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; nsCOMPtr firstChild, lastChild; nsCOMPtr 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 divElem = do_QueryInterface(firstChild); if (useCSS) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); } NS_ENSURE_SUCCESS(res, res); } 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 divElem = do_QueryInterface(divNode); if (useCSS) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); } NS_ENSURE_SUCCESS(res, res); // tuck the children into the end of the active div while (lastChild && (lastChild != divNode)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(lastChild, divNode, 0); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); lastChild = mHTMLEditor->GetLastEditableChild(*node); } } return res; } /////////////////////////////////////////////////////////////////////////// // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle // case of deleting from inside an empty block. // nsresult nsHTMLEditRules::CheckForEmptyBlock(nsINode* aStartNode, Element* aBodyNode, Selection* aSelection, bool* aHandled) { // If the editing host is an inline element, bail out early. if (IsInlineNode(GetAsDOMNode(aBodyNode))) { return NS_OK; } NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(mHTMLEditor); // If we are inside an empty block, delete it. Note: do NOT delete table // elements this way. nsCOMPtr block = kungFuDeathGrip->GetBlock(*aStartNode); bool bIsEmptyNode; nsCOMPtr emptyBlock; nsresult res; if (block && block != aBodyNode) { // Efficiency hack, avoiding IsEmptyNode() call when in body res = kungFuDeathGrip->IsEmptyNode(block, &bIsEmptyNode, true, false); NS_ENSURE_SUCCESS(res, res); while (block && bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && block != aBodyNode) { emptyBlock = block; block = kungFuDeathGrip->GetBlockNodeParent(emptyBlock); if (block) { res = kungFuDeathGrip->IsEmptyNode(block, &bIsEmptyNode, true, false); NS_ENSURE_SUCCESS(res, res); } } } if (emptyBlock && emptyBlock->IsEditable()) { nsCOMPtr blockParent = emptyBlock->GetParentNode(); NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); int32_t offset = blockParent->IndexOf(emptyBlock); if (nsHTMLEditUtils::IsListItem(emptyBlock)) { // Are we the first list item in the list? bool bIsFirst; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsFirstEditableChild(GetAsDOMNode(emptyBlock), &bIsFirst); NS_ENSURE_SUCCESS(res, res); if (bIsFirst) { nsCOMPtr 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 (!nsHTMLEditUtils::IsList(listParent)) { // Create a br before list NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr br = mHTMLEditor->CreateBR(listParent, listOffset); NS_ENSURE_STATE(br); // Adjust selection to be right before it res = aSelection->Collapse(listParent, listOffset); NS_ENSURE_SUCCESS(res, res); } // Else just let selection percolate up. We'll adjust it in // AfterEdit() } } else { // Adjust selection to be right after it res = aSelection->Collapse(blockParent, offset + 1); NS_ENSURE_SUCCESS(res, res); } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(emptyBlock); *aHandled = true; NS_ENSURE_SUCCESS(res, res); } return NS_OK; } nsresult nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock, BRLocation aWhere, nsCOMPtr *outBRNode, int32_t aOffset) { nsCOMPtr block = do_QueryInterface(aBlock); NS_ENSURE_TRUE(block && outBRNode, NS_ERROR_NULL_POINTER); *outBRNode = nullptr; nsCOMPtr testNode; int32_t testOffset = 0; bool runTest = false; if (aWhere == kBlockEnd) { nsCOMPtr rightmostNode = // no block crossing GetAsDOMNode(mHTMLEditor->GetRightmostChild(block, true)); if (rightmostNode) { int32_t nodeOffset; nsCOMPtr nodeParent = nsEditor::GetNodeLocation(rightmostNode, &nodeOffset); runTest = true; testNode = nodeParent; // use offset + 1, because we want the last node included in our // evaluation testOffset = nodeOffset + 1; } } else if (aOffset) { runTest = true; testNode = aBlock; // we'll check everything to the left of the input position testOffset = aOffset; } if (runTest) { nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset); if (WSType::br == wsTester.mStartReason) { *outBRNode = GetAsDOMNode(wsTester.mStartReasonNode); } } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // GetInnerContent: 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 nsHTMLEditRules::GetInnerContent(nsINode& aNode, nsTArray>& aOutArrayOfNodes, int32_t* aIndex, Lists aLists, Tables aTables) { MOZ_ASSERT(aIndex); for (nsCOMPtr node = mHTMLEditor->GetFirstEditableChild(aNode); node; node = node->GetNextSibling()) { if ((aLists == Lists::yes && (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsListItem(node))) || (aTables == Tables::yes && nsHTMLEditUtils::IsTableElement(node))) { GetInnerContent(*node, aOutArrayOfNodes, aIndex, aLists, aTables); } else { aOutArrayOfNodes.InsertElementAt(*aIndex, *node); (*aIndex)++; } } } /////////////////////////////////////////////////////////////////////////// // ExpandSelectionForDeletion: this promotes our selection to include blocks // that have all their children selected. // nsresult nsHTMLEditRules::ExpandSelectionForDeletion(Selection* aSelection) { NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); // don't need to touch collapsed selections if (aSelection->Collapsed()) { return NS_OK; } int32_t rangeCount; nsresult res = aSelection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(res, res); // we don't need to mess with cell selections, and we assume multirange selections are those. if (rangeCount != 1) return NS_OK; // find current sel start and end RefPtr range = aSelection->GetRangeAt(0); NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); nsCOMPtr selStartNode, selEndNode, selCommon; int32_t selStartOffset, selEndOffset; res = range->GetStartContainer(getter_AddRefs(selStartNode)); NS_ENSURE_SUCCESS(res, res); res = range->GetStartOffset(&selStartOffset); NS_ENSURE_SUCCESS(res, res); res = range->GetEndContainer(getter_AddRefs(selEndNode)); NS_ENSURE_SUCCESS(res, res); res = range->GetEndOffset(&selEndOffset); NS_ENSURE_SUCCESS(res, res); // find current selection common block parent res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon)); NS_ENSURE_SUCCESS(res, res); if (!IsBlockNode(selCommon)) selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon); NS_ENSURE_STATE(selCommon); // set up for loops and cache our root element bool stillLooking = true; nsCOMPtr firstBRParent; nsCOMPtr unused; int32_t visOffset=0, firstBROffset=0; WSType wsType; nsCOMPtr rootContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr rootElement = do_QueryInterface(rootContent); NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); // find previous visible thingy before start of selection if ((selStartNode!=selCommon) && (selStartNode!=rootElement)) { while (stillLooking) { nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset); nsCOMPtr selStartNode_(do_QueryInterface(selStartNode)); wsObj.PriorVisibleNode(selStartNode_, selStartOffset, address_of(unused), &visOffset, &wsType); 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 (nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) || selCommon == GetAsDOMNode(wsObj.mStartReasonNode) || rootElement == GetAsDOMNode(wsObj.mStartReasonNode)) { stillLooking = false; } else { selStartNode = nsEditor::GetNodeLocation(GetAsDOMNode(wsObj.mStartReasonNode), &selStartOffset); } } else { stillLooking = false; } } } stillLooking = true; // find next visible thingy after end of selection if ((selEndNode!=selCommon) && (selEndNode!=rootElement)) { while (stillLooking) { nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset); nsCOMPtr selEndNode_(do_QueryInterface(selEndNode)); wsObj.NextVisibleNode(selEndNode_, selEndOffset, address_of(unused), &visOffset, &wsType); if (wsType == WSType::br) { if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode)) { stillLooking = false; } else { if (!firstBRParent) { firstBRParent = selEndNode; firstBROffset = selEndOffset; } selEndNode = nsEditor::GetNodeLocation(GetAsDOMNode(wsObj.mEndReasonNode), &selEndOffset); ++selEndOffset; } } 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 (nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) || selCommon == GetAsDOMNode(wsObj.mEndReasonNode) || rootElement == GetAsDOMNode(wsObj.mEndReasonNode)) { stillLooking = false; } else { selEndNode = nsEditor::GetNodeLocation(GetAsDOMNode(wsObj.mEndReasonNode), &selEndOffset); ++selEndOffset; } } else { stillLooking = false; } } } // now set the selection to the new range aSelection->Collapse(selStartNode, selStartOffset); // expand selection endpoint only if we didnt pass a br, // or if we really needed to pass that br (ie, its block is now // totally selected) bool doEndExpansion = true; if (firstBRParent) { // find block node containing br nsCOMPtr brBlock = firstBRParent; if (!IsBlockNode(brBlock)) brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock); bool nodeBefore=false, nodeAfter=false; // create a range that represents expanded selection nsCOMPtr node = do_QueryInterface(selStartNode); NS_ENSURE_STATE(node); RefPtr range = new nsRange(node); res = range->SetStart(selStartNode, selStartOffset); NS_ENSURE_SUCCESS(res, res); res = range->SetEnd(selEndNode, selEndOffset); NS_ENSURE_SUCCESS(res, res); // check if block is entirely inside range nsCOMPtr brContentBlock = do_QueryInterface(brBlock); if (brContentBlock) { res = nsRange::CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter); } // if block isn't contained, forgo grabbing the br in the expanded selection if (nodeBefore || nodeAfter) doEndExpansion = false; } if (doEndExpansion) { res = aSelection->Extend(selEndNode, selEndOffset); } else { // only expand to just before br res = aSelection->Extend(firstBRParent, firstBROffset); } return res; } /////////////////////////////////////////////////////////////////////////// // NormalizeSelection: tweak 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 nsHTMLEditRules::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 res = inSelection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(res, res); // we don't need to mess with cell selections, and we assume multirange selections are those. if (rangeCount != 1) return NS_OK; RefPtr range = inSelection->GetRangeAt(0); NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); nsCOMPtr startNode, endNode; int32_t startOffset, endOffset; nsCOMPtr newStartNode, newEndNode; int32_t newStartOffset, newEndOffset; res = range->GetStartContainer(getter_AddRefs(startNode)); NS_ENSURE_SUCCESS(res, res); res = range->GetStartOffset(&startOffset); NS_ENSURE_SUCCESS(res, res); res = range->GetEndContainer(getter_AddRefs(endNode)); NS_ENSURE_SUCCESS(res, res); res = range->GetEndOffset(&endOffset); NS_ENSURE_SUCCESS(res, res); // adjusted values default to original values newStartNode = startNode; newStartOffset = startOffset; newEndNode = endNode; newEndOffset = endOffset; // some locals we need for whitespace code nsCOMPtr unused; int32_t offset; WSType wsType; // let the whitespace code do the heavy lifting nsWSRunObject 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 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 child = GetAsDOMNode(mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, true)); if (child) { newEndNode = nsEditor::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 child; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child)); if (child) { newEndNode = nsEditor::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 = nsEditor::GetNodeLocation(GetAsDOMNode(wsEndObj.mStartReasonNode), &newEndOffset); } } // similar dealio for start of range nsWSRunObject 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 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 child = GetAsDOMNode(mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, true)); if (child) { newStartNode = nsEditor::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 child; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child)); if (child) { newStartNode = nsEditor::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 = nsEditor::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: figure out where a start or end point for a block // operation really is void nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode, int32_t aOffset, EditAction actionID, nsCOMPtr* outNode, int32_t* outOffset) { nsCOMPtr 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 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(mHTMLEditor, /* void */); mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset, &isSpace, &isNBSP, getter_AddRefs(temp), &offset); } else { NS_ENSURE_TRUE(mHTMLEditor, /* void */); 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
    // from us, and that are enclosed in the same block. NS_ENSURE_TRUE(mHTMLEditor, /* void */); nsCOMPtr priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); while (priorNode && priorNode->GetParentNode() && mHTMLEditor && !mHTMLEditor->IsVisBreak(priorNode->AsDOMNode()) && !IsBlockNode(priorNode->AsDOMNode())) { offset = priorNode->GetParentNode()->IndexOf(priorNode); node = priorNode->GetParentNode(); NS_ENSURE_TRUE(mHTMLEditor, /* void */); 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(mHTMLEditor, /* void */); nsCOMPtr 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 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(mHTMLEditor, /* void */); if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) && (blockLevelAction || !mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(node))) { NS_ENSURE_TRUE(mHTMLEditor, /* void */); break; } node = parent; offset = parentOffset; NS_ENSURE_TRUE(mHTMLEditor, /* void */); 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
    from // us, and that are enclosed in the same block. NS_ENSURE_TRUE(mHTMLEditor, /* void */); nsCOMPtr nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); while (nextNode && !IsBlockNode(nextNode->AsDOMNode()) && nextNode->GetParentNode()) { offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode); node = nextNode->GetParentNode(); NS_ENSURE_TRUE(mHTMLEditor, /* void */); if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) { break; } // Check for newlines in pre-formatted text nodes. bool isPRE; mHTMLEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE); if (isPRE) { nsCOMPtr textNode = do_QueryInterface(nextNode); if (textNode) { nsAutoString tempString; textNode->GetData(tempString); int32_t newlinePos = tempString.FindChar(nsCRT::LF); if (newlinePos >= 0) { if ((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(mHTMLEditor, /* void */); 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(mHTMLEditor, /* void */); nsCOMPtr nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); while (!nearNode && !node->IsHTMLElement(nsGkAtoms::body) && node->GetParentNode()) { int32_t parentOffset = node->GetParentNode()->IndexOf(node); nsCOMPtr 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(mHTMLEditor, /* void */); break; } node = parent; // we want to be AFTER nearNode offset = parentOffset + 1; NS_ENSURE_TRUE(mHTMLEditor, /* void */); nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); } *outNode = node->AsDOMNode(); *outOffset = offset; } /////////////////////////////////////////////////////////////////////////////// // GetPromotedRanges: Run all the selection range endpoint through // GetPromotedPoint() // void nsHTMLEditRules::GetPromotedRanges(Selection& aSelection, nsTArray>& outArrayOfRanges, EditAction inOperationType) { uint32_t rangeCount = aSelection.RangeCount(); for (uint32_t i = 0; i < rangeCount; i++) { RefPtr selectionRange = aSelection.GetRangeAt(i); MOZ_ASSERT(selectionRange); // Clone range so we don't muck with actual selection ranges RefPtr 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: Expand a range to include any parents for which all editable // children are already in range. // void nsHTMLEditRules::PromoteRange(nsRange& aRange, EditAction aOperationType) { NS_ENSURE_TRUE(mHTMLEditor, ); RefPtr kungFuDeathGrip(mHTMLEditor); nsCOMPtr startNode = aRange.GetStartParent(); nsCOMPtr 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
    . It's easier // to put a workaround here than to revamp GetPromotedPoint. :-( if (startNode == endNode && startOffset == endOffset) { nsCOMPtr block = kungFuDeathGrip->GetBlock(*startNode); if (block) { bool bIsEmptyNode = false; nsCOMPtr root = kungFuDeathGrip->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)) { kungFuDeathGrip->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 opStartNode; nsCOMPtr opEndNode; int32_t opStartOffset, opEndOffset; RefPtr opRange; 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 (!kungFuDeathGrip->IsDescendantOfEditorRoot( nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) || !kungFuDeathGrip->IsDescendantOfEditorRoot( nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) { return; } DebugOnly res = aRange.SetStart(opStartNode, opStartOffset); MOZ_ASSERT(NS_SUCCEEDED(res)); res = aRange.SetEnd(opEndNode, opEndOffset); MOZ_ASSERT(NS_SUCCEEDED(res)); } class nsUniqueFunctor : public nsBoolDomIterFunctor { public: explicit nsUniqueFunctor(nsTArray> &aArray) : mArray(aArray) { } // used to build list of all nodes iterator covers virtual bool operator()(nsINode* aNode) const { return !mArray.Contains(aNode); } private: nsTArray>& mArray; }; /////////////////////////////////////////////////////////////////////////////// // GetNodesForOperation: Run through the ranges in the array and construct a // new array of nodes to be acted on. // nsresult nsHTMLEditRules::GetNodesForOperation(nsTArray>& aArrayOfRanges, nsTArray>& aOutArrayOfNodes, EditAction aOperationType, TouchContent aTouchContent) { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(mHTMLEditor); int32_t rangeCount = aArrayOfRanges.Length(); nsresult res = NS_OK; 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 r = aArrayOfRanges[i]; nsCOMPtr endParent = do_QueryInterface(r->GetEndParent()); if (!kungFuDeathGrip->IsTextNode(endParent)) { continue; } nsCOMPtr textNode = do_QueryInterface(endParent); if (textNode) { int32_t offset = r->EndOffset(); nsAutoString tempString; textNode->GetData(tempString); if (0 < offset && offset < (int32_t)(tempString.Length())) { // Split the text node. nsCOMPtr tempNode; res = kungFuDeathGrip->SplitNode(endParent->AsDOMNode(), offset, getter_AddRefs(tempNode)); NS_ENSURE_SUCCESS(res, res); // Correct the range. // The new end parent becomes the parent node of the text. nsCOMPtr 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> rangeItemArray; rangeItemArray.AppendElements(rangeCount); // First register ranges for special editor gravity for (int32_t i = 0; i < rangeCount; i++) { rangeItemArray[i] = new nsRangeStore(); rangeItemArray[i]->StoreRange(aArrayOfRanges[0]); kungFuDeathGrip->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]); aArrayOfRanges.RemoveElementAt(0); } // Now bust up inlines. for (auto& item : Reversed(rangeItemArray)) { res = BustUpInlinesAtRangeEndpoints(*item); if (NS_FAILED(res)) { break; } } // Then unregister the ranges for (auto& item : rangeItemArray) { kungFuDeathGrip->mRangeUpdater.DropRangeItem(item); aArrayOfRanges.AppendElement(item->GetRange()); } NS_ENSURE_SUCCESS(res, res); } // Gather up a list of all the nodes for (auto& range : aArrayOfRanges) { nsDOMSubtreeIterator iter; res = iter.Init(*range); NS_ENSURE_SUCCESS(res, res); if (aOutArrayOfNodes.Length() == 0) { iter.AppendList(nsTrivialFunctor(), 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> nodes; iter.AppendList(nsUniqueFunctor(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 node = aOutArrayOfNodes[i]; if (nsHTMLEditUtils::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 node = aOutArrayOfNodes[i]; if (nsHTMLEditUtils::IsTableElementButNotTable(node)) { int32_t j = i; aOutArrayOfNodes.RemoveElementAt(i); GetInnerContent(*node, aOutArrayOfNodes, &j); } } } // Outdent should look inside of divs. if (aOperationType == EditAction::outdent && !kungFuDeathGrip->IsCSSEnabled()) { for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { OwningNonNull 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 node = aOutArrayOfNodes[i]; if (aTouchContent == TouchContent::yes && IsInlineNode(GetAsDOMNode(node)) && kungFuDeathGrip->IsContainer(node) && !kungFuDeathGrip->IsTextNode(node)) { nsTArray> arrayOfInlines; res = BustUpInlinesAtBRs(*node->AsContent(), arrayOfInlines); NS_ENSURE_SUCCESS(res, res); // Put these nodes in aOutArrayOfNodes, replacing the current node aOutArrayOfNodes.RemoveElementAt(i); aOutArrayOfNodes.InsertElementsAt(i, arrayOfInlines); } } } return NS_OK; } void nsHTMLEditRules::GetChildNodesForOperation(nsINode& aNode, nsTArray>& outArrayOfNodes) { for (nsCOMPtr child = aNode.GetFirstChild(); child; child = child->GetNextSibling()) { outArrayOfNodes.AppendElement(*child); } } nsresult nsHTMLEditRules::GetListActionNodes(nsTArray>& aOutArrayOfNodes, EntireList aEntireList, TouchContent aTouchContent) { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(mHTMLEditor); RefPtr selection = kungFuDeathGrip->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 range = selection->GetRangeAt(rangeIdx); for (nsCOMPtr parent = range->GetCommonAncestor(); parent; parent = parent->GetParentNode()) { if (nsHTMLEditUtils::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.Length()) { return NS_OK; } } { // We don't like other people messing with our selection! nsAutoTxnsConserveSelection dontSpazMySelection(kungFuDeathGrip); // contruct a list of nodes to act on. nsresult res = GetNodesFromSelection(*selection, EditAction::makeList, aOutArrayOfNodes, aTouchContent); NS_ENSURE_SUCCESS(res, res); } // Pre-process our list of nodes for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { OwningNonNull testNode = aOutArrayOfNodes[i]; // Remove all non-editable nodes. Leave them be. if (!kungFuDeathGrip->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 (nsHTMLEditUtils::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 nsHTMLEditRules::LookInsideDivBQandList(nsTArray>& aNodeArray) { NS_ENSURE_TRUE(mHTMLEditor, ); RefPtr kungFuDeathGrip(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. int32_t listCount = aNodeArray.Length(); if (listCount != 1) { return; } OwningNonNull curNode = aNodeArray[0]; while (curNode->IsHTMLElement(nsGkAtoms::div) || nsHTMLEditUtils::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 = kungFuDeathGrip->CountEditableChildren(curNode); if (numChildren != 1) { break; } // Keep diving! XXX One would expect to dive into the one editable node. nsCOMPtr child = curNode->GetFirstChild(); if (!child->IsHTMLElement(nsGkAtoms::div) && !nsHTMLEditUtils::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); } /////////////////////////////////////////////////////////////////////////// // GetDefinitionListItemTypes: // void nsHTMLEditRules::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 nsHTMLEditRules::GetParagraphFormatNodes(nsTArray>& outArrayOfNodes, TouchContent aTouchContent) { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(mHTMLEditor); RefPtr selection = kungFuDeathGrip->GetSelection(); NS_ENSURE_STATE(selection); // Contruct a list of nodes to act on. nsresult res = GetNodesFromSelection(*selection, EditAction::makeBasicBlock, outArrayOfNodes, aTouchContent); NS_ENSURE_SUCCESS(res, res); // Pre-process our list of nodes for (int32_t i = outArrayOfNodes.Length() - 1; i >= 0; i--) { OwningNonNull testNode = outArrayOfNodes[i]; // Remove all non-editable nodes. Leave them be. if (!kungFuDeathGrip->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 (nsHTMLEditUtils::IsTableElement(testNode) || nsHTMLEditUtils::IsList(testNode) || nsHTMLEditUtils::IsListItem(testNode)) { int32_t j = i; outArrayOfNodes.RemoveElementAt(i); GetInnerContent(testNode, outArrayOfNodes, &j); } } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // BustUpInlinesAtRangeEndpoints: // nsresult nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) { bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); nsCOMPtr endInline = do_QueryInterface(GetHighestInlineParent(GetAsDOMNode(item.endNode))); // if we have inline parents above range endpoints, split them if (endInline && !isCollapsed) { nsCOMPtr 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, nsEditor::EmptyContainers::no); NS_ENSURE_TRUE(resultEndOffset != -1, NS_ERROR_FAILURE); // reset range item.endNode = resultEndNode; item.endOffset = resultEndOffset; } nsCOMPtr startInline = do_QueryInterface(GetHighestInlineParent(GetAsDOMNode(item.startNode))); if (startInline) { nsCOMPtr resultStartNode = startInline->GetParentNode(); NS_ENSURE_STATE(mHTMLEditor); int32_t resultStartOffset = mHTMLEditor->SplitNodeDeep(*startInline, *item.startNode->AsContent(), item.startOffset, nsEditor::EmptyContainers::no); NS_ENSURE_TRUE(resultStartOffset != -1, NS_ERROR_FAILURE); // reset range item.startNode = resultStartNode; item.startOffset = resultStartOffset; } return NS_OK; } nsresult nsHTMLEditRules::BustUpInlinesAtBRs(nsIContent& aNode, nsTArray>& aOutArrayOfNodes) { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(mHTMLEditor); // First build up a list of all the break nodes inside the inline container. nsTArray> arrayOfBreaks; nsBRNodeFunctor functor; nsDOMIterator iter(aNode); iter.AppendList(functor, arrayOfBreaks); // If there aren't any breaks, just put inNode itself in the array if (!arrayOfBreaks.Length()) { aOutArrayOfNodes.AppendElement(aNode); return NS_OK; } // Else we need to bust up inNode along all the breaks nsCOMPtr inlineParentNode = aNode.GetParentNode(); nsCOMPtr splitDeepNode = &aNode; nsCOMPtr leftNode, rightNode; for (uint32_t i = 0; i < arrayOfBreaks.Length(); i++) { OwningNonNull breakNode = *arrayOfBreaks[i]->AsElement(); NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(breakNode->GetParent(), NS_ERROR_NULL_POINTER); OwningNonNull splitParentNode = *breakNode->GetParent(); int32_t splitOffset = splitParentNode->IndexOf(breakNode); int32_t resultOffset = kungFuDeathGrip->SplitNodeDeep(*splitDeepNode, splitParentNode, splitOffset, nsHTMLEditor::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 res = kungFuDeathGrip->MoveNode(breakNode, inlineParentNode, resultOffset); NS_ENSURE_SUCCESS(res, res); 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; } nsCOMPtr nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode) { NS_ENSURE_TRUE(aNode, nullptr); if (IsBlockNode(aNode)) return nullptr; nsCOMPtr inlineNode, node=aNode; while (node && IsInlineNode(node)) { inlineNode = node; inlineNode->GetParentNode(getter_AddRefs(node)); } return inlineNode; } /////////////////////////////////////////////////////////////////////////////// // GetNodesFromPoint: Given a particular operation, construct a list of nodes // from a point that will be operated on. // nsresult nsHTMLEditRules::GetNodesFromPoint(::DOMPoint aPoint, EditAction aOperation, nsTArray>& outArrayOfNodes, TouchContent aTouchContent) { NS_ENSURE_STATE(aPoint.node); RefPtr range = new nsRange(aPoint.node); nsresult res = range->SetStart(aPoint.node, aPoint.offset); MOZ_ASSERT(NS_SUCCEEDED(res)); // Expand the range to include adjacent inlines PromoteRange(*range, aOperation); // Make array of ranges nsTArray> arrayOfRanges; // Stuff new opRange into array arrayOfRanges.AppendElement(range); // Use these ranges to contruct a list of nodes to act on res = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation, aTouchContent); NS_ENSURE_SUCCESS(res, res); return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // GetNodesFromSelection: Given a particular operation, construct a list of // nodes from the selection that will be operated on. // nsresult nsHTMLEditRules::GetNodesFromSelection(Selection& aSelection, EditAction aOperation, nsTArray>& outArrayOfNodes, TouchContent aTouchContent) { // Promote selection ranges nsTArray> arrayOfRanges; GetPromotedRanges(aSelection, arrayOfRanges, aOperation); // Use these ranges to contruct a list of nodes to act on. nsresult res = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation, aTouchContent); NS_ENSURE_SUCCESS(res, res); return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // MakeTransitionList: Detect all the transitions in the array, where a // transition means that adjacent nodes in the array don't // have the same parent. void nsHTMLEditRules::MakeTransitionList(nsTArray>& aNodeArray, nsTArray& aTransitionArray) { nsCOMPtr 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(); } } /******************************************************** * main implementation methods ********************************************************/ /////////////////////////////////////////////////////////////////////////// // IsInListItem: 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. // already_AddRefed nsHTMLEditRules::IsInListItem(nsIDOMNode* aNode) { nsCOMPtr node = do_QueryInterface(aNode); nsCOMPtr retval = do_QueryInterface(IsInListItem(node)); return retval.forget(); } Element* nsHTMLEditRules::IsInListItem(nsINode* aNode) { NS_ENSURE_TRUE(aNode, nullptr); if (nsHTMLEditUtils::IsListItem(aNode)) { return aNode->AsElement(); } Element* parent = aNode->GetParentElement(); while (parent && mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(parent) && !nsHTMLEditUtils::IsTableElement(parent)) { if (nsHTMLEditUtils::IsListItem(parent)) { return parent; } parent = parent->GetParentElement(); } return nullptr; } /////////////////////////////////////////////////////////////////////////// // ReturnInHeader: do the right thing for returns pressed in headers // nsresult nsHTMLEditRules::ReturnInHeader(Selection* aSelection, nsIDOMNode *aHeader, nsIDOMNode *aNode, int32_t aOffset) { nsCOMPtr header = do_QueryInterface(aHeader); nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(aSelection && header && node, NS_ERROR_NULL_POINTER); // remeber where the header is int32_t offset; nsCOMPtr headerParent = nsEditor::GetNodeLocation(aHeader, &offset); // get ws code to adjust any ws NS_ENSURE_STATE(mHTMLEditor); nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(node), &aOffset); NS_ENSURE_SUCCESS(res, res); // split the header NS_ENSURE_STATE(node->IsContent()); NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->SplitNodeDeep(*header, *node->AsContent(), aOffset); // if the leftand heading is empty, put a mozbr in it nsCOMPtr prevItem; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsHeader(prevItem)) { bool bIsEmptyNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { res = CreateMozBR(prevItem, 0); NS_ENSURE_SUCCESS(res, res); } } // if the new (righthand) header node is empty, delete it bool isEmpty; res = IsEmptyBlock(aHeader, &isEmpty, true); NS_ENSURE_SUCCESS(res, res); if (isEmpty) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(aHeader); NS_ENSURE_SUCCESS(res, res); // layout tells the caret to blink in a weird place // if we don't place a break after the header. nsCOMPtr sibling; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling)); NS_ENSURE_SUCCESS(res, res); if (!sibling || !nsTextEditUtils::IsBreak(sibling)) { ClearCachedStyles(); NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mTypeInState->ClearAllProps(); // create a paragraph NS_NAMED_LITERAL_STRING(pType, "p"); nsCOMPtr pNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode)); NS_ENSURE_SUCCESS(res, res); // append a
    to it nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); // set selection to before the break res = aSelection->Collapse(pNode, 0); } else { headerParent = nsEditor::GetNodeLocation(sibling, &offset); // put selection after break res = aSelection->Collapse(headerParent,offset+1); } } else { // put selection at front of righthand heading res = aSelection->Collapse(aHeader,0); } return res; } /////////////////////////////////////////////////////////////////////////// // ReturnInParagraph: do the right thing for returns pressed in paragraphs // nsresult nsHTMLEditRules::ReturnInParagraph(Selection* aSelection, nsIDOMNode* aPara, nsIDOMNode* aNode, int32_t aOffset, bool* aCancel, bool* aHandled) { if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } *aCancel = false; *aHandled = false; nsresult res; int32_t offset; nsCOMPtr parent = nsEditor::GetNodeLocation(aNode, &offset); NS_ENSURE_STATE(mHTMLEditor); bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(); bool newBRneeded = false; nsCOMPtr sibling; NS_ENSURE_STATE(mHTMLEditor); if (aNode == aPara && doesCRCreateNewP) { // we are at the edges of the block, newBRneeded not needed! sibling = aNode; } else if (mHTMLEditor->IsTextNode(aNode)) { nsCOMPtr textNode = do_QueryInterface(aNode); uint32_t strLength; res = textNode->GetLength(&strLength); NS_ENSURE_SUCCESS(res, res); // at beginning of text node? if (!aOffset) { // is there a BR prior to it? NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(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); res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling)) { NS_ENSURE_STATE(mHTMLEditor); newBRneeded = true; offset++; } } else { if (doesCRCreateNewP) { nsCOMPtr tmp; res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp)); NS_ENSURE_SUCCESS(res, res); aNode = tmp; } newBRneeded = true; offset++; } } else { // not in a text node. // is there a BR prior to it? nsCOMPtr nearNode, selNode = aNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode)) { // is there a BR after it? NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode)) { newBRneeded = 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); nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); sibling = brNode; } nsCOMPtr selNode = aNode; *aHandled = true; return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset); } /////////////////////////////////////////////////////////////////////////// // SplitParagraph: split a paragraph at selection point, possibly deleting a br // nsresult nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara, nsIDOMNode *aBRNode, Selection* aSelection, nsCOMPtr *aSelNode, int32_t *aOffset) { nsCOMPtr para = do_QueryInterface(aPara); NS_ENSURE_TRUE(para && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; // split para // get ws code to adjust any ws nsCOMPtr leftPara, rightPara; NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr selNode(do_QueryInterface(*aSelNode)); res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), aOffset); *aSelNode = GetAsDOMNode(selNode); NS_ENSURE_SUCCESS(res, res); // split the paragraph NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(selNode->IsContent()); mHTMLEditor->SplitNodeDeep(*para, *selNode->AsContent(), *aOffset, nsHTMLEditor::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); res = mHTMLEditor->DeleteNode(aBRNode); NS_ENSURE_SUCCESS(res, res); } // remove ID attribute on the paragraph we just created nsCOMPtr rightElt = do_QueryInterface(rightPara); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id")); NS_ENSURE_SUCCESS(res, res); // check both halves of para to see if we need mozBR res = InsertMozBRIfNeeded(GetAsDOMNode(leftPara)); NS_ENSURE_SUCCESS(res, res); res = InsertMozBRIfNeeded(GetAsDOMNode(rightPara)); NS_ENSURE_SUCCESS(res, res); // selection to beginning of right hand para; // look inside any containers that are up front. nsCOMPtr rightParaNode = do_QueryInterface(rightPara); NS_ENSURE_STATE(mHTMLEditor && rightParaNode); nsCOMPtr child = GetAsDOMNode(mHTMLEditor->GetLeftmostChild(rightParaNode, true)); if (mHTMLEditor->IsTextNode(child) || mHTMLEditor->IsContainer(child)) { aSelection->Collapse(child,0); } else { int32_t offset; nsCOMPtr parent = nsEditor::GetNodeLocation(child, &offset); aSelection->Collapse(parent,offset); } return res; } /////////////////////////////////////////////////////////////////////////// // ReturnInListItem: do the right thing for returns pressed in list items // nsresult nsHTMLEditRules::ReturnInListItem(Selection* aSelection, nsIDOMNode *aListItem, nsIDOMNode *aNode, int32_t aOffset) { nsCOMPtr listItem = do_QueryInterface(aListItem); NS_ENSURE_TRUE(aSelection && listItem && aNode, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; nsCOMPtr listitem; // sanity check NS_PRECONDITION(true == nsHTMLEditUtils::IsListItem(aListItem), "expected a list item and didn't get one"); // get the listitem parent and the active editing host. NS_ENSURE_STATE(mHTMLEditor); nsIContent* rootContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr rootNode = do_QueryInterface(rootContent); nsCOMPtr list = listItem->GetParentNode(); int32_t itemOffset = list ? list->IndexOf(listItem) : -1; // if we are in an empty listitem, then we want to pop up out of the list // but only if prefs says it's ok and if the parent isn't the active editing host. bool isEmpty; res = IsEmptyBlock(aListItem, &isEmpty, true, false); NS_ENSURE_SUCCESS(res, res); if (isEmpty && (rootNode != GetAsDOMNode(list)) && mReturnInEmptyLIKillsList) { // get the list offset now -- before we might eventually split the list nsCOMPtr listParent = list->GetParentNode(); int32_t offset = listParent ? listParent->IndexOf(list) : -1; // are we the last list item in the list? bool bIsLast; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast); NS_ENSURE_SUCCESS(res, res); if (!bIsLast) { // we need to split the list! nsCOMPtr tempNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SplitNode(GetAsDOMNode(list), itemOffset, getter_AddRefs(tempNode)); NS_ENSURE_SUCCESS(res, res); } // are we in a sublist? if (nsHTMLEditUtils::IsList(listParent)) { // if so, move this list item out of this list and into the grandparent list NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(listItem, listParent, offset + 1); NS_ENSURE_SUCCESS(res, res); res = aSelection->Collapse(aListItem,0); } else { // otherwise kill this listitem NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(aListItem); NS_ENSURE_SUCCESS(res, res); // time to insert a paragraph NS_NAMED_LITERAL_STRING(pType, "p"); nsCOMPtr pNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateNode(pType, GetAsDOMNode(listParent), offset + 1, getter_AddRefs(pNode)); NS_ENSURE_SUCCESS(res, res); // append a
    to it nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); // set selection to before the break res = aSelection->Collapse(pNode, 0); } return res; } // else we want a new list item at the same list level. // get ws code to adjust any ws nsCOMPtr selNode(do_QueryInterface(aNode)); NS_ENSURE_STATE(mHTMLEditor); res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); NS_ENSURE_SUCCESS(res, res); // now split list item NS_ENSURE_STATE(mHTMLEditor); NS_ENSURE_STATE(selNode->IsContent()); mHTMLEditor->SplitNodeDeep(*listItem, *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 prevItem; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsListItem(prevItem)) { bool bIsEmptyNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { res = CreateMozBR(prevItem, 0); NS_ENSURE_SUCCESS(res, res); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, true); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { nsCOMPtr nodeAtom = nsEditor::GetTag(aListItem); if (nodeAtom == nsGkAtoms::dd || nodeAtom == nsGkAtoms::dt) { int32_t itemOffset; nsCOMPtr list = nsEditor::GetNodeLocation(aListItem, &itemOffset); nsAutoString listTag(nodeAtom == nsGkAtoms::dt ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt")); nsCOMPtr newListItem; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem)); NS_ENSURE_SUCCESS(res, res); res = mEditor->DeleteNode(aListItem); NS_ENSURE_SUCCESS(res, res); return aSelection->Collapse(newListItem, 0); } nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode)); NS_ENSURE_SUCCESS(res, res); if (brNode) { int32_t offset; nsCOMPtr brParent = nsEditor::GetNodeLocation(brNode, &offset); return aSelection->Collapse(brParent, offset); } } else { NS_ENSURE_STATE(mHTMLEditor); nsWSRunObject wsObj(mHTMLEditor, aListItem, 0); nsCOMPtr visNode_; int32_t visOffset = 0; WSType wsType; nsCOMPtr aListItem_(do_QueryInterface(aListItem)); wsObj.NextVisibleNode(aListItem_, 0, address_of(visNode_), &visOffset, &wsType); nsCOMPtr visNode(GetAsDOMNode(visNode_)); if (wsType == WSType::special || wsType == WSType::br || nsHTMLEditUtils::IsHR(visNode)) { int32_t offset; nsCOMPtr parent = nsEditor::GetNodeLocation(visNode, &offset); return aSelection->Collapse(parent, offset); } else { return aSelection->Collapse(visNode, visOffset); } } } } res = aSelection->Collapse(aListItem,0); return res; } /////////////////////////////////////////////////////////////////////////////// // MakeBlockquote: Put the list of nodes into one or more blockquotes. // nsresult nsHTMLEditRules::MakeBlockquote(nsTArray>& 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). nsresult res; nsCOMPtr curBlock; nsCOMPtr 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 (nsHTMLEditUtils::IsTableElementButNotTable(curNode) || nsHTMLEditUtils::IsListItem(curNode)) { // Forget any previous block curBlock = nullptr; // Recursion time nsTArray> childArray; GetChildNodesForOperation(*curNode, childArray); res = MakeBlockquote(childArray); NS_ENSURE_SUCCESS(res, res); } // 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 curParent = curNode->GetParentNode(); int32_t offset = curParent ? curParent->IndexOf(curNode) : -1; res = SplitAsNeeded(*nsGkAtoms::blockquote, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curBlock = mHTMLEditor->CreateNode(nsGkAtoms::blockquote, curParent, offset); NS_ENSURE_STATE(curBlock); // remember our new block for postprocessing mNewBlock = curBlock->AsDOMNode(); // note: doesn't matter if we set mNewBlock multiple times. } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode->AsContent(), curBlock, -1); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // RemoveBlockStyle: Make the nodes have no special block type. nsresult nsHTMLEditRules::RemoveBlockStyle(nsTArray>& aNodeArray) { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(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... nsresult res; nsCOMPtr curBlock; nsCOMPtr firstNode, lastNode; for (auto& curNode : aNodeArray) { // If curNode is a address, p, header, address, or pre, remove it if (nsHTMLEditUtils::IsFormatNode(curNode)) { // Process any partial progress saved if (curBlock) { res = RemovePartOfBlock(*curBlock, *firstNode, *lastNode); NS_ENSURE_SUCCESS(res, res); firstNode = lastNode = curBlock = nullptr; } // Remove current block res = kungFuDeathGrip->RemoveBlockContainer(curNode->AsDOMNode()); NS_ENSURE_SUCCESS(res, res); } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody, nsGkAtoms::td, nsGkAtoms::li, nsGkAtoms::blockquote, nsGkAtoms::div) || nsHTMLEditUtils::IsList(curNode)) { // Process any partial progress saved if (curBlock) { res = RemovePartOfBlock(*curBlock, *firstNode, *lastNode); NS_ENSURE_SUCCESS(res, res); firstNode = lastNode = curBlock = nullptr; } // Recursion time nsTArray> childArray; GetChildNodesForOperation(*curNode, childArray); res = RemoveBlockStyle(childArray); NS_ENSURE_SUCCESS(res, res); } else if (IsInlineNode(GetAsDOMNode(curNode))) { if (curBlock) { // If so, is this node a descendant? if (nsEditorUtils::IsDescendantOf(curNode, curBlock)) { // Then we don't need to do anything different for this node lastNode = curNode->AsContent(); continue; } else { // 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]. res = RemovePartOfBlock(*curBlock, *firstNode, *lastNode); NS_ENSURE_SUCCESS(res, res); firstNode = lastNode = curBlock = nullptr; // Fall out and handle curNode } } curBlock = kungFuDeathGrip->GetBlockNodeParent(curNode); if (curBlock && nsHTMLEditUtils::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. res = RemovePartOfBlock(*curBlock, *firstNode, *lastNode); NS_ENSURE_SUCCESS(res, res); firstNode = lastNode = curBlock = nullptr; } } // Process any partial progress saved if (curBlock) { res = RemovePartOfBlock(*curBlock, *firstNode, *lastNode); NS_ENSURE_SUCCESS(res, res); firstNode = lastNode = curBlock = nullptr; } return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // ApplyBlockStyle: Do whatever it takes to make the list of nodes into one or // more blocks of type aBlockTag. nsresult nsHTMLEditRules::ApplyBlockStyle(nsTArray>& 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 kungFuDeathGrip(mHTMLEditor); nsresult res; // Remove all non-editable nodes. Leave them be. for (int32_t i = aNodeArray.Length() - 1; i >= 0; i--) { if (!kungFuDeathGrip->IsEditable(aNodeArray[i])) { aNodeArray.RemoveElementAt(i); } } nsCOMPtr newBlock; nsCOMPtr curBlock; for (auto& curNode : aNodeArray) { nsCOMPtr 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 (nsHTMLEditUtils::IsMozDiv(curNode) || nsHTMLEditUtils::IsFormatNode(curNode)) { // Forget any previous block used for previous inline nodes curBlock = nullptr; newBlock = kungFuDeathGrip->ReplaceContainer(curNode->AsElement(), &aBlockTag, nullptr, nullptr, nsEditor::eCloneAttributes); NS_ENSURE_STATE(newBlock); } else if (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::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> childArray; GetChildNodesForOperation(*curNode, childArray); if (childArray.Length()) { res = ApplyBlockStyle(childArray, aBlockTag); NS_ENSURE_SUCCESS(res, res); } else { // Make sure we can put a block here res = SplitAsNeeded(aBlockTag, curParent, offset); NS_ENSURE_SUCCESS(res, res); nsCOMPtr theBlock = kungFuDeathGrip->CreateNode(&aBlockTag, curParent, offset); NS_ENSURE_STATE(theBlock); // Remember our new block for postprocessing mNewBlock = theBlock->AsDOMNode(); } } 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; res = kungFuDeathGrip->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); } else { // The break is the first (or even only) node we encountered. Create a // block for it. res = SplitAsNeeded(aBlockTag, curParent, offset); NS_ENSURE_SUCCESS(res, res); curBlock = kungFuDeathGrip->CreateNode(&aBlockTag, curParent, offset); NS_ENSURE_STATE(curBlock); // Remember our new block for postprocessing mNewBlock = curBlock->AsDOMNode(); // Note: doesn't matter if we set mNewBlock multiple times. res = kungFuDeathGrip->MoveNode(curNode->AsContent(), curBlock, -1); NS_ENSURE_SUCCESS(res, res); } } else if (IsInlineNode(GetAsDOMNode(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
    .
          if (&aBlockTag == nsGkAtoms::pre && !kungFuDeathGrip->IsEditable(curNode)) {
            // Do nothing to this block
            continue;
          }
    
          // If no curBlock, make one
          if (!curBlock) {
            res = SplitAsNeeded(aBlockTag, curParent, offset);
            NS_ENSURE_SUCCESS(res, res);
            curBlock = kungFuDeathGrip->CreateNode(&aBlockTag, curParent, offset);
            NS_ENSURE_STATE(curBlock);
            // Remember our new block for postprocessing
            mNewBlock = curBlock->AsDOMNode();
            // Note: doesn't matter if we set mNewBlock multiple times.
          }
    
          // XXX If curNode is a br, replace it with a return if going to 
    
          // This is a continuation of some inline nodes that belong together in
          // the same block item.  Use curBlock.
          res = kungFuDeathGrip->MoveNode(curNode->AsContent(), curBlock, -1);
          NS_ENSURE_SUCCESS(res, res);
        }
      }
      return NS_OK;
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // SplitAsNeeded: 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
    nsHTMLEditRules::SplitAsNeeded(nsIAtom& aTag,
                                   nsCOMPtr& 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 tagParent, splitNode;
      for (nsCOMPtr 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)) {
          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.
     */
    ::DOMPoint
    nsHTMLEditRules::JoinNodesSmart(nsIContent& aNodeLeft, nsIContent& aNodeRight)
    {
      // Caller responsible for left and right node being the same type
      nsCOMPtr parent = aNodeLeft.GetParentNode();
      NS_ENSURE_TRUE(parent, ::DOMPoint());
      int32_t parOffset = parent->IndexOf(&aNodeLeft);
      nsCOMPtr rightParent = aNodeRight.GetParentNode();
    
      // If they don't have the same parent, first move the right node to after the
      // left one
      nsresult res;
      if (parent != rightParent) {
        NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint());
        res = mHTMLEditor->MoveNode(&aNodeRight, parent, parOffset);
        NS_ENSURE_SUCCESS(res, ::DOMPoint());
      }
    
      ::DOMPoint ret(&aNodeRight, aNodeLeft.Length());
    
      // Separate join rules for differing blocks
      if (nsHTMLEditUtils::IsList(&aNodeLeft) || aNodeLeft.GetAsText()) {
        // For lists, merge shallow (wouldn't want to combine list items)
        res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
        NS_ENSURE_SUCCESS(res, ::DOMPoint());
        return ret;
      }
    
      // Remember the last left child, and first right child
      NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint());
      nsCOMPtr lastLeft = mHTMLEditor->GetLastEditableChild(aNodeLeft);
      NS_ENSURE_TRUE(lastLeft, ::DOMPoint());
    
      NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint());
      nsCOMPtr firstRight = mHTMLEditor->GetFirstEditableChild(aNodeRight);
      NS_ENSURE_TRUE(firstRight, ::DOMPoint());
    
      // For list items, divs, etc., merge smart
      NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint());
      res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
      NS_ENSURE_SUCCESS(res, ::DOMPoint());
    
      if (lastLeft && firstRight && mHTMLEditor &&
          mHTMLEditor->AreNodesSameType(lastLeft, firstRight) &&
          (lastLeft->GetAsText() || !mHTMLEditor ||
           (lastLeft->IsElement() && firstRight->IsElement() &&
            mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft->AsElement(),
                                                      firstRight->AsElement())))) {
        NS_ENSURE_TRUE(mHTMLEditor, ::DOMPoint());
        return JoinNodesSmart(*lastLeft, *firstRight);
      }
      return ret;
    }
    
    
    Element*
    nsHTMLEditRules::GetTopEnclosingMailCite(nsINode& aNode)
    {
      nsCOMPtr ret;
    
      for (nsCOMPtr node = &aNode; node; node = node->GetParentNode()) {
        if ((IsPlaintextEditor() && node->IsHTMLElement(nsGkAtoms::pre)) ||
            nsHTMLEditUtils::IsMailCite(node)) {
          ret = node->AsElement();
        }
        if (node->IsHTMLElement(nsGkAtoms::body)) {
          break;
        }
      }
    
      return ret;
    }
    
    
    nsresult
    nsHTMLEditRules::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)
      {
        bool isSet = false;
        nsAutoString outValue;
        // Don't use CSS for , 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->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode,
            mCachedStyles[j].tag, &(mCachedStyles[j].attr), isSet, outValue,
            nsHTMLCSSUtils::eComputed);
        }
        if (isSet)
        {
          mCachedStyles[j].mPresent = true;
          mCachedStyles[j].value.Assign(outValue);
        }
      }
      return NS_OK;
    }
    
    
    nsresult
    nsHTMLEditRules::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 = mHTMLEditor->GetSelection();
      MOZ_ASSERT(selection);
      if (!selection->RangeCount()) {
        // Nothing to do
        return NS_OK;
      }
      nsCOMPtr 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->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
              selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue,
              nsHTMLCSSUtils::eComputed);
          }
          if (!bAny) {
            // then check typeinstate and html style
            NS_ENSURE_STATE(mHTMLEditor);
            nsresult res = mHTMLEditor->GetInlinePropertyBase(
                                                         *mCachedStyles[i].tag,
                                                         &(mCachedStyles[i].attr),
                                                         &(mCachedStyles[i].value),
                                                         &bFirst, &bAny, &bAll,
                                                         &curValue, false);
            NS_ENSURE_SUCCESS(res, res);
          }
          // 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
    nsHTMLEditRules::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
    nsHTMLEditRules::AdjustSpecialBreaks()
    {
      NS_ENSURE_TRUE(mHTMLEditor, );
    
      // Gather list of empty nodes
      nsTArray> nodeArray;
      nsEmptyEditableFunctor functor(mHTMLEditor);
      nsDOMIterator iter;
      nsresult res = iter.Init(*mDocChangeRange);
      NS_ENSURE_SUCCESS(res, );
      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 res = CreateMozBR(node->AsDOMNode(), (int32_t)node->Length());
        NS_ENSURE_SUCCESS(res, );
      }
    }
    
    nsresult
    nsHTMLEditRules::AdjustWhitespace(Selection* aSelection)
    {
      // get selection point
      nsCOMPtr selNode;
      int32_t selOffset;
      NS_ENSURE_STATE(mHTMLEditor);
      nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
      NS_ENSURE_SUCCESS(res, res);
    
      // ask whitespace object to tweak nbsp's
      NS_ENSURE_STATE(mHTMLEditor);
      return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
    }
    
    nsresult
    nsHTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
    {
      NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
      if (!aSelection->Collapsed()) {
        return NS_OK;
      }
    
      // get the (collapsed) selection location
      nsCOMPtr selNode, temp;
      int32_t selOffset;
      NS_ENSURE_STATE(mHTMLEditor);
      nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
      NS_ENSURE_SUCCESS(res, res);
      temp = selNode;
    
      // use ranges and sRangeHelper to compare sel point to new block
      nsCOMPtr node = do_QueryInterface(selNode);
      NS_ENSURE_STATE(node);
      RefPtr range = new nsRange(node);
      res = range->SetStart(selNode, selOffset);
      NS_ENSURE_SUCCESS(res, res);
      res = range->SetEnd(selNode, selOffset);
      NS_ENSURE_SUCCESS(res, res);
      nsCOMPtr block (do_QueryInterface(mNewBlock));
      NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
      bool nodeBefore, nodeAfter;
      res = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
      NS_ENSURE_SUCCESS(res, res);
    
      if (nodeBefore && nodeAfter)
        return NS_OK;  // selection is inside block
      else if (nodeBefore)
      {
        // selection is after block.  put at end of block.
        nsCOMPtr tmp = mNewBlock;
        NS_ENSURE_STATE(mHTMLEditor);
        tmp = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*block));
        uint32_t endPoint;
        if (mHTMLEditor->IsTextNode(tmp) ||
            mHTMLEditor->IsContainer(tmp))
        {
          res = nsEditor::GetLengthOfDOMNode(tmp, endPoint);
          NS_ENSURE_SUCCESS(res, res);
        }
        else
        {
          tmp = nsEditor::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 tmp = mNewBlock;
        tmp = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*block));
        int32_t offset;
        if (mHTMLEditor->IsTextNode(tmp) ||
            mHTMLEditor->IsContainer(tmp))
        {
          tmp = nsEditor::GetNodeLocation(tmp, &offset);
        }
        return aSelection->Collapse(tmp, 0);
      }
    }
    
    nsresult
    nsHTMLEditRules::CheckInterlinePosition(Selection* aSelection)
    {
      NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
    
      // if the selection isn't collapsed, do nothing.
      if (!aSelection->Collapsed()) {
        return NS_OK;
      }
    
      // get the (collapsed) selection location
      nsCOMPtr selNode, node;
      int32_t selOffset;
      NS_ENSURE_STATE(mHTMLEditor);
      nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
      NS_ENSURE_SUCCESS(res, res);
    
      // First, let's check to see if we are after a 
    . We take care of this // special-case first so that we don't accidentally fall through into one // of the other conditionals. NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), true); if (node && nsTextEditUtils::IsBreak(node)) { aSelection->SetInterlinePosition(true); return NS_OK; } // are we after a block? If so try set caret to following content NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node)); if (node && IsBlockNode(node)) { aSelection->SetInterlinePosition(true); return NS_OK; } // are we before a block? If so try set caret to prior content NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node)); if (node && IsBlockNode(node)) aSelection->SetInterlinePosition(false); return NS_OK; } nsresult nsHTMLEditRules::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 selNode, temp; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); 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 = nsEditor::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
    in the block if allowed. NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr theblock = mHTMLEditor->GetBlock(*selNode); if (theblock && mHTMLEditor->IsEditable(theblock)) { bool bIsEmptyNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false); NS_ENSURE_SUCCESS(res, res); // 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 rootNode = mHTMLEditor->GetRoot(); NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); if (selNode == rootNode) { // Our root node is completely empty. Don't add a
    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 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 nearNode = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset); if (nearNode) { // is nearNode also a descendant of same block? NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr block = mHTMLEditor->GetBlock(*selNode); nsCOMPtr nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode); if (block && block == nearBlock) { if (nearNode && nsTextEditUtils::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 brNode; res = CreateMozBR(GetAsDOMNode(selNode), selOffset, getter_AddRefs(brNode)); NS_ENSURE_SUCCESS(res, res); nsCOMPtr brParent = nsEditor::GetNodeLocation(brNode, &selOffset); // selection stays *before* moz-br, sticking to it aSelection->SetInterlinePosition(true); res = aSelection->Collapse(brParent, selOffset); NS_ENSURE_SUCCESS(res, res); } else { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr nextNode = mHTMLEditor->GetNextHTMLNode(nearNode, true); if (nextNode && nsTextEditUtils::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 && (nsTextEditUtils::IsBreak(nearNode) || nsEditor::IsTextNode(nearNode) || nsHTMLEditUtils::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 && (nsTextEditUtils::IsBreak(nearNode) || nsEditor::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 nearNodeDOM = GetAsDOMNode(nearNode); res = FindNearSelectableNode(GetAsDOMNode(selNode), selOffset, aAction, address_of(nearNodeDOM)); NS_ENSURE_SUCCESS(res, res); nearNode = do_QueryInterface(nearNodeDOM); if (nearNode) { // is the nearnode a text node? textNode = do_QueryInterface(nearNode); if (textNode) { int32_t offset = 0; // put selection in right place: if (aAction == nsIEditor::ePrevious) textNode->GetLength((uint32_t*)&offset); res = aSelection->Collapse(nearNode,offset); } else // must be break or image { selNode = nsEditor::GetNodeLocation(nearNode, &selOffset); if (aAction == nsIEditor::ePrevious) selOffset++; // want to be beyond it if we backed up to it res = aSelection->Collapse(selNode, selOffset); } } return res; } nsresult nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode, int32_t aSelOffset, nsIEditor::EDirection &aDirection, nsCOMPtr *outSelectableNode) { NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER); *outSelectableNode = nullptr; nsresult res = NS_OK; nsCOMPtr nearNode, curNode; if (aDirection == nsIEditor::ePrevious) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); } NS_ENSURE_SUCCESS(res, res); if (!nearNode) // try the other direction then { if (aDirection == nsIEditor::ePrevious) aDirection = nsIEditor::eNext; else aDirection = nsIEditor::ePrevious; if (aDirection == nsIEditor::ePrevious) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); } NS_ENSURE_SUCCESS(res, res); } // 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) || nsTextEditUtils::IsBreak(nearNode) || nsHTMLEditUtils::IsImage(nearNode))) { curNode = nearNode; if (aDirection == nsIEditor::ePrevious) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode)); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode)); } NS_ENSURE_SUCCESS(res, res); 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 res; } bool nsHTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1, nsIDOMNode* aNode2) { nsCOMPtr node1 = do_QueryInterface(aNode1); nsCOMPtr node2 = do_QueryInterface(aNode2); return InDifferentTableElements(node1, node2); } bool nsHTMLEditRules::InDifferentTableElements(nsINode* aNode1, nsINode* aNode2) { MOZ_ASSERT(aNode1 && aNode2); while (aNode1 && !nsHTMLEditUtils::IsTableElement(aNode1)) { aNode1 = aNode1->GetParentNode(); } while (aNode2 && !nsHTMLEditUtils::IsTableElement(aNode2)) { aNode2 = aNode2->GetParentNode(); } return aNode1 != aNode2; } nsresult nsHTMLEditRules::RemoveEmptyNodes() { NS_ENSURE_STATE(mHTMLEditor); RefPtr kungFuDeathGrip(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 iter = NS_NewContentIterator(); nsresult res = iter->Init(mDocChangeRange); NS_ENSURE_SUCCESS(res, res); nsTArray> arrayOfEmptyNodes, arrayOfEmptyCites, skipList; // Check for empty nodes while (!iter->IsDone()) { OwningNonNull node = *iter->GetCurrentNode(); nsCOMPtr 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 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 = nsHTMLEditUtils::IsMailCite(node)) || node->IsHTMLElement(nsGkAtoms::a) || nsHTMLEditUtils::IsInlineStyle(node) || nsHTMLEditUtils::IsList(node) || node->IsHTMLElement(nsGkAtoms::div)) { // Only consider certain nodes to be empty for purposes of removal bIsCandidate = true; } else if (nsHTMLEditUtils::IsFormatNode(node) || nsHTMLEditUtils::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; res = SelectionEndpointInNode(node, &bIsSelInNode); NS_ENSURE_SUCCESS(res, res); 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. res = kungFuDeathGrip->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode, bIsMailCite, true); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { if (bIsMailCite) { // mailcites go on a separate list from other empty nodes arrayOfEmptyCites.AppendElement(*node); } else { arrayOfEmptyNodes.AppendElement(*node); } } } if (!bIsEmptyNode) { // put parent on skip list skipList.AppendElement(*parent); } } iter->Next(); } // now delete the empty nodes for (auto& delNode : arrayOfEmptyNodes) { if (kungFuDeathGrip->IsModifiableNode(delNode)) { res = kungFuDeathGrip->DeleteNode(delNode); NS_ENSURE_SUCCESS(res, res); } } // 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; res = kungFuDeathGrip->IsEmptyNode(delNode, &bIsEmptyNode, false, true); NS_ENSURE_SUCCESS(res, res); if (!bIsEmptyNode) { // We are deleting a cite that has just a br. We want to delete cite, // but preserve br. nsCOMPtr parent = delNode->GetParentNode(); int32_t offset = parent ? parent->IndexOf(delNode) : -1; nsCOMPtr br = kungFuDeathGrip->CreateBR(parent, offset); NS_ENSURE_STATE(br); } res = kungFuDeathGrip->DeleteNode(delNode); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } nsresult nsHTMLEditRules::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 = mHTMLEditor->GetSelection(); NS_ENSURE_STATE(selection); uint32_t rangeCount = selection->RangeCount(); for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { RefPtr range = selection->GetRangeAt(rangeIdx); nsCOMPtr startParent, endParent; range->GetStartContainer(getter_AddRefs(startParent)); if (startParent) { if (node == startParent) { *aResult = true; return NS_OK; } if (nsEditorUtils::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 (nsEditorUtils::IsDescendantOf(endParent, node)) { *aResult = true; return NS_OK; } } } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsEmptyInline: return true if aNode is an empty inline container // // bool nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode) { if (aNode && IsInlineNode(aNode) && mHTMLEditor && mHTMLEditor->IsContainer(aNode)) { bool bEmpty; NS_ENSURE_TRUE(mHTMLEditor, false); mHTMLEditor->IsEmptyNode(aNode, &bEmpty); return bEmpty; } return false; } bool nsHTMLEditRules::ListIsEmptyLine(nsTArray>& 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 kungFuDeathGrip(mHTMLEditor); int32_t brCount = 0; for (auto& node : aArrayOfNodes) { if (!kungFuDeathGrip->IsEditable(node)) { continue; } if (nsTextEditUtils::IsBreak(node)) { // First break doesn't count if (brCount) { return false; } brCount++; } else if (IsEmptyInline(GetAsDOMNode(node))) { // Empty inline, keep looking } else { return false; } } return true; } nsresult nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, bool *aOutOfList) { nsCOMPtr listItem = do_QueryInterface(aListItem); // check parms NS_ENSURE_TRUE(listItem && aOutOfList, NS_ERROR_NULL_POINTER); // init out params *aOutOfList = false; nsCOMPtr curParent = listItem->GetParentNode(); int32_t offset = curParent ? curParent->IndexOf(listItem) : -1; if (!nsHTMLEditUtils::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 curParPar = curParent->GetParentNode(); int32_t parOffset = curParPar ? curParPar->IndexOf(curParent) : -1; bool bIsFirstListItem; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->IsFirstEditableChild(aListItem, &bIsFirstListItem); NS_ENSURE_SUCCESS(res, res); bool bIsLastListItem; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLastListItem); NS_ENSURE_SUCCESS(res, res); if (!bIsFirstListItem && !bIsLastListItem) { // split the list nsCOMPtr newBlock; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SplitNode(GetAsDOMNode(curParent), offset, getter_AddRefs(newBlock)); NS_ENSURE_SUCCESS(res, res); } if (!bIsFirstListItem) parOffset++; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(listItem, curParPar, parOffset); NS_ENSURE_SUCCESS(res, res); // unwrap list item contents if they are no longer in a list if (!nsHTMLEditUtils::IsList(curParPar) && nsHTMLEditUtils::IsListItem(listItem)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(GetAsDOMNode(listItem)); NS_ENSURE_SUCCESS(res, res); *aOutOfList = true; } return res; } nsresult nsHTMLEditRules::RemoveListStructure(nsIDOMNode *aList) { NS_ENSURE_ARG_POINTER(aList); nsresult res; nsCOMPtr child; aList->GetFirstChild(getter_AddRefs(child)); while (child) { if (nsHTMLEditUtils::IsListItem(child)) { bool bOutOfList; do { res = PopListItem(child, &bOutOfList); NS_ENSURE_SUCCESS(res, res); } while (!bOutOfList); // keep popping it out until it's not in a list anymore } else if (nsHTMLEditUtils::IsList(child)) { res = RemoveListStructure(child); NS_ENSURE_SUCCESS(res, res); } else { // delete any non- list items for now NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(child); NS_ENSURE_SUCCESS(res, res); } aList->GetFirstChild(getter_AddRefs(child)); } // delete the now-empty list NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveBlockContainer(aList); NS_ENSURE_SUCCESS(res, res); return res; } nsresult nsHTMLEditRules::ConfirmSelectionInBody() { // get the body NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr rootElement = do_QueryInterface(mHTMLEditor->GetRoot()); NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED); // get the selection NS_ENSURE_STATE(mHTMLEditor); RefPtr selection = mHTMLEditor->GetSelection(); NS_ENSURE_STATE(selection); // get the selection start location nsCOMPtr selNode, temp, parent; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); temp = selNode; // check that selNode is inside body while (temp && !nsTextEditUtils::IsBody(temp)) { res = 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); res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); temp = selNode; // check that selNode is inside body while (temp && !nsTextEditUtils::IsBody(temp)) { res = 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); } return res; } nsresult nsHTMLEditRules::UpdateDocChangeRange(nsRange* aRange) { nsresult res = NS_OK; // 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 startNode; res = aRange->GetStartContainer(getter_AddRefs(startNode)); NS_ENSURE_SUCCESS(res, res); 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 res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, aRange, &result); if (res == 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; res = NS_OK; } NS_ENSURE_SUCCESS(res, res); if (result > 0) // positive result means mDocChangeRange start is after aRange start { int32_t startOffset; res = aRange->GetStartOffset(&startOffset); NS_ENSURE_SUCCESS(res, res); res = mDocChangeRange->SetStart(startNode, startOffset); NS_ENSURE_SUCCESS(res, res); } // compare ends of ranges res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange, &result); NS_ENSURE_SUCCESS(res, res); if (result < 0) // negative result means mDocChangeRange end is before aRange end { nsCOMPtr endNode; int32_t endOffset; res = aRange->GetEndContainer(getter_AddRefs(endNode)); NS_ENSURE_SUCCESS(res, res); res = aRange->GetEndOffset(&endOffset); NS_ENSURE_SUCCESS(res, res); res = mDocChangeRange->SetEnd(endNode, endOffset); NS_ENSURE_SUCCESS(res, res); } } return res; } nsresult nsHTMLEditRules::InsertMozBRIfNeeded(nsIDOMNode *aNode) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); if (!IsBlockNode(aNode)) return NS_OK; bool isEmpty; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->IsEmptyNode(aNode, &isEmpty); NS_ENSURE_SUCCESS(res, res); if (!isEmpty) { return NS_OK; } return CreateMozBR(aNode, 0); } NS_IMETHODIMP nsHTMLEditRules::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::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 res = mUtilRange->SelectNode(aNode); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::DidInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition, nsresult aResult) { if (!mListenerEnabled) { return NS_OK; } nsresult res = mUtilRange->SelectNode(aNode); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillDeleteNode(nsIDOMNode *aChild) { if (!mListenerEnabled) { return NS_OK; } nsresult res = mUtilRange->SelectNode(aChild); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::DidSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset, nsIDOMNode *aNewLeftNode, nsresult aResult) { if (!mListenerEnabled) { return NS_OK; } nsresult res = mUtilRange->SetStart(aNewLeftNode, 0); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetEnd(aExistingRightNode, 0); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) { if (!mListenerEnabled) { return NS_OK; } // remember split point nsresult res = nsEditor::GetLengthOfDOMNode(aLeftNode, mJoinOffset); return res; } NS_IMETHODIMP nsHTMLEditRules::DidJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent, nsresult aResult) { if (!mListenerEnabled) { return NS_OK; } // assumption that Join keeps the righthand node nsresult res = mUtilRange->SetStart(aRightNode, mJoinOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetEnd(aRightNode, mJoinOffset); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::DidInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString, nsresult aResult) { if (!mListenerEnabled) { return NS_OK; } int32_t length = aString.Length(); nsCOMPtr theNode = do_QueryInterface(aTextNode); nsresult res = mUtilRange->SetStart(theNode, aOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetEnd(theNode, aOffset+length); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) { return NS_OK; } NS_IMETHODIMP nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength, nsresult aResult) { if (!mListenerEnabled) { return NS_OK; } nsCOMPtr theNode = do_QueryInterface(aTextNode); nsresult res = mUtilRange->SetStart(theNode, aOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetEnd(theNode, aOffset); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::WillDeleteSelection(nsISelection* aSelection) { if (!mListenerEnabled) { return NS_OK; } RefPtr selection = static_cast(aSelection); // get the (collapsed) selection location nsCOMPtr selNode; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); nsresult res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetStart(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetEnd(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); res = UpdateDocChangeRange(mUtilRange); return res; } NS_IMETHODIMP nsHTMLEditRules::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 nsHTMLEditRules::RemoveAlignment(nsIDOMNode * aNode, const nsAString & aAlignType, bool aChildrenOnly) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsTextNode(aNode) || nsHTMLEditUtils::IsTable(aNode)) return NS_OK; nsresult res = NS_OK; nsCOMPtr 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); res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock); NS_ENSURE_SUCCESS(res, res); if (nsEditor::NodeIsType(child, nsGkAtoms::center)) { // the current node is a CENTER element // first remove children's alignment res = RemoveAlignment(child, aAlignType, true); NS_ENSURE_SUCCESS(res, res); // 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 res = MakeSureElemStartsOrEndsOnCR(child); NS_ENSURE_SUCCESS(res, res); // now remove the CENTER container NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr childAsElement = do_QueryInterface(child); NS_ENSURE_STATE(childAsElement); res = mHTMLEditor->RemoveContainer(childAsElement); NS_ENSURE_SUCCESS(res, res); } else if (isBlock || nsHTMLEditUtils::IsHR(child)) { // the current node is a block element nsCOMPtr curElem = do_QueryInterface(child); if (nsHTMLEditUtils::SupportsAlignAttr(child)) { // remove the ALIGN attribute if this element can have it NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align")); NS_ENSURE_SUCCESS(res, res); } if (useCSS) { if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, false); } else { nsAutoString dummyCssValue; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsGkAtoms::textAlign, dummyCssValue); } NS_ENSURE_SUCCESS(res, res); } if (!nsHTMLEditUtils::IsTable(child)) { // unless this is a table, look at children res = RemoveAlignment(child, aAlignType, true); NS_ENSURE_SUCCESS(res, res); } } 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 nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode, bool aStarts) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); nsCOMPtr child; nsresult res; 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); res = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock); NS_ENSURE_SUCCESS(res, res); bool foundCR = false; if (isChildBlock || nsTextEditUtils::IsBreak(child)) { foundCR = true; } else { nsCOMPtr sibling; if (aStarts) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); } else { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); } NS_ENSURE_SUCCESS(res, res); if (sibling) { bool isBlock; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock); NS_ENSURE_SUCCESS(res, res); if (isBlock || nsTextEditUtils::IsBreak(sibling)) { foundCR = true; } } else { foundCR = true; } } if (!foundCR) { int32_t offset = 0; if (!aStarts) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_STATE(node); offset = node->GetChildCount(); } nsCOMPtr brNode; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } nsresult nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode) { nsresult res = MakeSureElemStartsOrEndsOnCR(aNode, false); NS_ENSURE_SUCCESS(res, res); res = MakeSureElemStartsOrEndsOnCR(aNode, true); return res; } nsresult nsHTMLEditRules::AlignBlock(nsIDOMElement * aElement, const nsAString * aAlignType, bool aContentsOnly) { NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); nsCOMPtr node = do_QueryInterface(aElement); bool isBlock = IsBlockNode(node); if (!isBlock && !nsHTMLEditUtils::IsHR(node)) { // we deal only with blocks; early way out return NS_OK; } nsresult res = RemoveAlignment(node, *aAlignType, aContentsOnly); NS_ENSURE_SUCCESS(res, res); NS_NAMED_LITERAL_STRING(attr, "align"); NS_ENSURE_STATE(mHTMLEditor); if (mHTMLEditor->IsCSSEnabled()) { // let's use CSS alignment; we use margin-left and margin-right for tables // and text-align for other block-level elements NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttributeOrEquivalent(aElement, attr, *aAlignType, false); NS_ENSURE_SUCCESS(res, res); } else { // HTML case; this code is supposed to be called ONLY if the element // supports the align attribute but we'll never know... if (nsHTMLEditUtils::SupportsAlignAttr(node)) { NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->SetAttribute(aElement, attr, *aAlignType); NS_ENSURE_SUCCESS(res, res); } } return NS_OK; } nsresult nsHTMLEditRules::RelativeChangeIndentationOfElementNode(nsIDOMNode *aNode, int8_t aRelativeChange) { NS_ENSURE_ARG_POINTER(aNode); if (aRelativeChange != 1 && aRelativeChange != -1) { return NS_ERROR_ILLEGAL_VALUE; } nsCOMPtr element = do_QueryInterface(aNode); if (!element) { return NS_OK; } NS_ENSURE_STATE(mHTMLEditor); nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, GetAsDOMNode(element)); nsAutoString value; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(*element, *marginProperty, value); float f; nsCOMPtr unit; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (0 == f) { nsAutoString defaultLengthUnit; NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit); unit = do_GetAtom(defaultLengthUnit); } if (nsGkAtoms::in == unit) { f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange; } else if (nsGkAtoms::cm == unit) { f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange; } else if (nsGkAtoms::mm == unit) { f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange; } else if (nsGkAtoms::pt == unit) { f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange; } else if (nsGkAtoms::pc == unit) { f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange; } else if (nsGkAtoms::em == unit) { f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange; } else if (nsGkAtoms::ex == unit) { f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange; } else if (nsGkAtoms::px == unit) { f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange; } else if (nsGkAtoms::percentage == unit) { f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange; } if (0 < f) { nsAutoString newValue; newValue.AppendFloat(f); newValue.Append(nsDependentAtomString(unit)); NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(*element, *marginProperty, newValue); return NS_OK; } NS_ENSURE_STATE(mHTMLEditor); mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(*element, *marginProperty, value); // remove unnecessary DIV blocks: // we could skip this section but that would cause a FAIL in // editor/libeditor/tests/browserscope/richtext.html, which expects // to unapply a CSS "indent" (
    ) by // removing the DIV container instead of just removing the CSS property. nsCOMPtr node = do_QueryInterface(aNode); if (!node || !node->IsHTMLElement(nsGkAtoms::div) || !mHTMLEditor || node == mHTMLEditor->GetActiveEditingHost() || !mHTMLEditor->IsDescendantOfEditorRoot(node) || nsHTMLEditor::HasAttributes(node)) { NS_ENSURE_STATE(mHTMLEditor); return NS_OK; } NS_ENSURE_STATE(mHTMLEditor); return mHTMLEditor->RemoveContainer(node); } // // Support for Absolute Positioning // nsresult nsHTMLEditRules::WillAbsolutePosition(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore result of WillInsert() *aCancel = false; *aHandled = true; nsCOMPtr focusElement; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetSelectionContainer(getter_AddRefs(focusElement)); if (focusElement) { nsCOMPtr node = do_QueryInterface(focusElement); if (nsHTMLEditUtils::IsImage(node)) { mNewBlock = node; return NS_OK; } } res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(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> arrayOfRanges; GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::setAbsolutePosition); // use these ranges to contruct a list of nodes to act on. nsTArray> arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::setAbsolutePosition); NS_ENSURE_SUCCESS(res, res); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { // get selection location NS_ENSURE_STATE(aSelection->RangeCount()); nsCOMPtr parent = aSelection->GetRangeAt(0)->GetStartParent(); int32_t offset = aSelection->GetRangeAt(0)->StartOffset(); NS_ENSURE_STATE(parent); // make sure we can put a block here res = SplitAsNeeded(*nsGkAtoms::div, parent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr thePositionedDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, parent, offset); NS_ENSURE_STATE(thePositionedDiv); // remember our new block for postprocessing mNewBlock = thePositionedDiv->AsDOMNode(); // delete anything that was in the list of nodes while (!arrayOfNodes.IsEmpty()) { OwningNonNull curNode = arrayOfNodes[0]; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveElementAt(0); } // put selection in new block res = aSelection->Collapse(thePositionedDiv,0); selectionResetter.Abort(); // to prevent selection reseter from overriding us. *aHandled = true; return res; } // Ok, now go through all the nodes and put them in a blockquote, // or whatever is appropriate. Wohoo! nsCOMPtr curParent; nsCOMPtr indentedLI, sibling; nsCOMPtr curList, curPositionedDiv; 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()); nsCOMPtr 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 (nsHTMLEditUtils::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) { NS_ENSURE_STATE(mHTMLEditor); sibling = GetAsDOMNode(mHTMLEditor->GetPriorHTMLSibling(curNode)); } if (!curList || (sibling && sibling != GetAsDOMNode(curList))) { // create a new nested list of correct type res = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_SUCCESS(res, res); if (!curPositionedDiv) { nsCOMPtr curParentParent = curParent->GetParentNode(); int32_t parentOffset = curParentParent ? curParentParent->IndexOf(curParent) : -1; NS_ENSURE_STATE(mHTMLEditor); curPositionedDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, curParentParent, parentOffset); mNewBlock = GetAsDOMNode(curPositionedDiv); } NS_ENSURE_STATE(mHTMLEditor); curList = mHTMLEditor->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 // mNewBlock = curList; } // tuck the node into the end of the active list NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); // forget curPositionedDiv, if any // curPositionedDiv = nullptr; } 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 listItem = IsInListItem(curNode); if (listItem) { if (indentedLI == GetAsDOMNode(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) { NS_ENSURE_STATE(mHTMLEditor); sibling = GetAsDOMNode(mHTMLEditor->GetPriorHTMLSibling(curNode)); } if (!curList || (sibling && sibling != GetAsDOMNode(curList))) { // create a new nested list of correct type res = SplitAsNeeded(*curParent->NodeInfo()->NameAtom(), curParent, offset); NS_ENSURE_SUCCESS(res, res); if (!curPositionedDiv) { nsCOMPtr curParentParent = curParent->GetParentNode(); int32_t parentOffset = curParentParent ? curParentParent->IndexOf(curParent) : -1; NS_ENSURE_STATE(mHTMLEditor); curPositionedDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, curParentParent, parentOffset); mNewBlock = GetAsDOMNode(curPositionedDiv); } NS_ENSURE_STATE(mHTMLEditor); curList = mHTMLEditor->CreateNode(curParent->NodeInfo()->NameAtom(), curPositionedDiv, -1); NS_ENSURE_STATE(curList); } NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(listItem, curList, -1); NS_ENSURE_SUCCESS(res, res); // remember we indented this li indentedLI = GetAsDOMNode(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 = GetAsDOMNode(curPositionedDiv); curList = nullptr; continue; } res = SplitAsNeeded(*nsGkAtoms::div, curParent, offset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); curPositionedDiv = mHTMLEditor->CreateNode(nsGkAtoms::div, curParent, offset); NS_ENSURE_STATE(curPositionedDiv); // remember our new block for postprocessing mNewBlock = GetAsDOMNode(curPositionedDiv); // curPositionedDiv is now the correct thing to put curNode in } // tuck the node into the end of the active blockquote NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1); NS_ENSURE_SUCCESS(res, res); // forget curList, if any curList = nullptr; } } } return res; } nsresult nsHTMLEditRules::DidAbsolutePosition() { NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr absPosHTMLEditor = mHTMLEditor; nsCOMPtr elt = do_QueryInterface(mNewBlock); return absPosHTMLEditor->AbsolutelyPositionElement(elt, true); } nsresult nsHTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection, bool* aCancel, bool* aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore aCancel from WillInsert() *aCancel = false; *aHandled = true; nsCOMPtr elt; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr absPosHTMLEditor = mHTMLEditor; return absPosHTMLEditor->AbsolutelyPositionElement(elt, false); } nsresult nsHTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection, int32_t aChange, bool *aCancel, bool * aHandled) { if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } nsresult res = WillInsert(aSelection, aCancel); NS_ENSURE_SUCCESS(res, res); // initialize out param // we want to ignore aCancel from WillInsert() *aCancel = false; *aHandled = true; nsCOMPtr elt; NS_ENSURE_STATE(mHTMLEditor); res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_STATE(mHTMLEditor); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); NS_ENSURE_STATE(mHTMLEditor); nsCOMPtr absPosHTMLEditor = mHTMLEditor; int32_t zIndex; return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex); } NS_IMETHODIMP nsHTMLEditRules::DocumentModified() { nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, &nsHTMLEditRules::DocumentModifiedWorker)); return NS_OK; } void nsHTMLEditRules::DocumentModifiedWorker() { if (!mHTMLEditor) { return; } // DeleteNode below may cause a flush, which could destroy the editor nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker; RefPtr kungFuDeathGrip(mHTMLEditor); RefPtr selection = kungFuDeathGrip->GetSelection(); if (!selection) { return; } // Delete our bogus node, if we have one, since the document might not be // empty any more. if (mBogusNode) { mEditor->DeleteNode(mBogusNode); mBogusNode = nullptr; } // Try to recreate the bogus node if needed. CreateBogusNodeIfNeeded(selection); }