/* -*- 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 #include "mozilla/Assertions.h" #include "mozilla/Preferences.h" #include "mozilla/Selection.h" #include "mozilla/dom/Element.h" #include "mozilla/mozalloc.h" #include "nsAString.h" #include "nsAlgorithm.h" #include "nsCOMArray.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsEditProperty.h" #include "nsEditor.h" #include "nsEditorUtils.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsHTMLCSSUtils.h" #include "nsHTMLEditRules.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 "nsIDOMRange.h" #include "nsIDOMText.h" #include "nsIEnumerator.h" #include "nsIHTMLAbsPosEditor.h" #include "nsIHTMLDocument.h" #include "nsINode.h" #include "nsISelection.h" #include "nsISelectionPrivate.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" class nsISupports; class nsRulesInfo; using namespace mozilla; //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: virtual bool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers { if (nsHTMLEditUtils::IsTableCell(aNode)) return true; if (nsHTMLEditUtils::IsListItem(aNode)) return true; return false; } }; class nsBRNodeFunctor : public nsBoolDomIterFunctor { public: virtual bool operator()(nsIDOMNode* aNode) { if (nsTextEditUtils::IsBreak(aNode)) return true; return false; } }; class nsEmptyEditableFunctor : public nsBoolDomIterFunctor { public: nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} virtual bool operator()(nsIDOMNode* aNode) { if (mHTMLEditor->IsEditable(aNode) && (nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCellOrCaption(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: nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} virtual bool operator()(nsIDOMNode* aNode) { if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode)) { return true; } return false; } protected: nsHTMLEditor* mHTMLEditor; }; /******************************************************** * Constructor/Destructor ********************************************************/ nsHTMLEditRules::nsHTMLEditRules() : mDocChangeRange(nullptr) ,mListenerEnabled(true) ,mReturnInEmptyLIKillsList(true) ,mDidDeleteSelection(false) ,mDidRangedDelete(false) ,mRestoreContentEditableCount(false) ,mUtilRange(nullptr) ,mJoinOffset(0) { // populate mCachedStyles mCachedStyles[0] = StyleCache(nsEditProperty::b, EmptyString(), EmptyString()); mCachedStyles[1] = StyleCache(nsEditProperty::i, EmptyString(), EmptyString()); mCachedStyles[2] = StyleCache(nsEditProperty::u, EmptyString(), EmptyString()); mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), EmptyString()); mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), EmptyString()); mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), EmptyString()); mCachedStyles[6] = StyleCache(nsEditProperty::tt, EmptyString(), EmptyString()); mCachedStyles[7] = StyleCache(nsEditProperty::em, EmptyString(), EmptyString()); mCachedStyles[8] = StyleCache(nsEditProperty::strong, EmptyString(), EmptyString()); mCachedStyles[9] = StyleCache(nsEditProperty::dfn, EmptyString(), EmptyString()); mCachedStyles[10] = StyleCache(nsEditProperty::code, EmptyString(), EmptyString()); mCachedStyles[11] = StyleCache(nsEditProperty::samp, EmptyString(), EmptyString()); mCachedStyles[12] = StyleCache(nsEditProperty::var, EmptyString(), EmptyString()); mCachedStyles[13] = StyleCache(nsEditProperty::cite, EmptyString(), EmptyString()); mCachedStyles[14] = StyleCache(nsEditProperty::abbr, EmptyString(), EmptyString()); mCachedStyles[15] = StyleCache(nsEditProperty::acronym, EmptyString(), EmptyString()); mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, EmptyString(), EmptyString()); mCachedStyles[17] = StyleCache(nsEditProperty::sub, EmptyString(), EmptyString()); mCachedStyles[18] = StyleCache(nsEditProperty::sup, EmptyString(), EmptyString()); mRangeItem = new nsRangeStore(); } 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_INHERITED1(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener) /******************************************************** * Public methods ********************************************************/ NS_IMETHODIMP nsHTMLEditRules::Init(nsPlaintextEditor *aEditor) { 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 mUtilRange = new nsRange(); // set up mDocChangeRange to be whole doc nsCOMPtr rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); if (rootElem) { // temporarily turn off rules sniffing nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); if (!mDocChangeRange) { mDocChangeRange = new nsRange(); } mDocChangeRange->SelectNode(rootElem); res = AdjustSpecialBreaks(); NS_ENSURE_SUCCESS(res, res); } // add ourselves as a listener to edit actions res = mHTMLEditor->AddEditActionListener(this); return res; } NS_IMETHODIMP nsHTMLEditRules::DetachEditor() { 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 nsCOMPtr selection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); // get the selection start location nsCOMPtr selStartNode, selEndNode; int32_t selOffset; res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selOffset); NS_ENSURE_SUCCESS(res, res); mRangeItem->startNode = selStartNode; mRangeItem->startOffset = selOffset; // get the selection end location res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selOffset); NS_ENSURE_SUCCESS(res, res); mRangeItem->endNode = selEndNode; mRangeItem->endOffset = selOffset; // register this range with range updater to track this as we perturb the doc (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; res = CacheInlineStyles(selNode); NS_ENSURE_SUCCESS(res, res); } // Stabilize the document against contenteditable count changes 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 (mHTMLEditor->mRangeUpdater).DropRangeItem(mRangeItem); // Reset the contenteditable count to its previous value if (mRestoreContentEditableCount) { 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; nsCOMPtrselection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); 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; } 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. nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); // expand the "changed doc range" as needed res = PromoteRange(mDocChangeRange, action); NS_ENSURE_SUCCESS(res, res); // if we did a ranged deletion, 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. res = AdjustSpecialBreaks(); NS_ENSURE_SUCCESS(res, res); // merge any adjacent text nodes if ( (action != EditAction::insertText && action != EditAction::insertIMEText) ) { 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. 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) { 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)) { mHTMLEditor->mTypeInState->UpdateSelState(selection); res = ReapplyCachedStyles(); NS_ENSURE_SUCCESS(res, res); res = ClearCachedStyles(); NS_ENSURE_SUCCESS(res, res); } } res = mHTMLEditor->HandleInlineSpellCheck(action, selection, 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->GetRangeCount(), NS_OK); nsRefPtr range = aSelection->GetRangeAt(0); nsCOMPtr selStartNode = range->GetStartParent(); if (!mHTMLEditor->IsModifiableNode(selStartNode)) { *aCancel = true; return NS_OK; } nsCOMPtr selEndNode = range->GetEndParent(); if (selStartNode != selEndNode) { if (!mHTMLEditor->IsModifiableNode(selEndNode)) { *aCancel = true; return NS_OK; } if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) { *aCancel = true; return NS_OK; } } switch (info->action) { case EditAction::insertText: case EditAction::insertIMEText: return WillInsertText(info->action, aSelection, aCancel, aHandled, info->inString, info->outString, info->maxLength); case EditAction::loadHTML: return WillLoadHTML(aSelection, aCancel); case EditAction::insertBreak: 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(nsISelection *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; nsCOMArray arrayOfNodes; nsresult res = GetListActionNodes(arrayOfNodes, false, true); NS_ENSURE_SUCCESS(res, res); // Examine list type for nodes in selection. int32_t listCount = arrayOfNodes.Count(); for (int32_t i = listCount - 1; i >= 0; --i) { nsIDOMNode* curDOMNode = arrayOfNodes[i]; nsCOMPtr curElement = do_QueryInterface(curDOMNode); if (!curElement) { bNonList = true; } else if (curElement->IsHTML(nsGkAtoms::ul)) { *aUL = true; } else if (curElement->IsHTML(nsGkAtoms::ol)) { *aOL = true; } else if (curElement->IsHTML(nsGkAtoms::li)) { if (dom::Element* parent = curElement->GetElementParent()) { if (parent->IsHTML(nsGkAtoms::ul)) { *aUL = true; } else if (parent->IsHTML(nsGkAtoms::ol)) { *aOL = true; } } } else if (curElement->IsHTML(nsGkAtoms::dl) || curElement->IsHTML(nsGkAtoms::dt) || curElement->IsHTML(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; nsCOMArray arrayOfNodes; nsresult res = GetListActionNodes(arrayOfNodes, false, true); NS_ENSURE_SUCCESS(res, res); // examine list type for nodes in selection int32_t listCount = arrayOfNodes.Count(); for (int32_t i = listCount - 1; i >= 0; --i) { nsIDOMNode* curNode = arrayOfNodes[i]; nsCOMPtr element = do_QueryInterface(curNode); if (!element) { bNonList = true; } else if (element->IsHTML(nsGkAtoms::ul) || element->IsHTML(nsGkAtoms::ol) || element->IsHTML(nsGkAtoms::li)) { *aLI = true; } else if (element->IsHTML(nsGkAtoms::dt)) { *aDT = true; } else if (element->IsHTML(nsGkAtoms::dd)) { *aDD = true; } else if (element->IsHTML(nsGkAtoms::dl)) { // need to look inside dl and see which types of items it has bool bDT, bDD; GetDefinitionListItemTypes(element, &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 nsCOMPtrselection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); // get selection location nsCOMPtr rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE); int32_t offset, rootOffset; nsCOMPtr parent = nsEditor::GetNodeLocation(rootElem, &rootOffset); 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->IsTextNode(parent)) { // if we are in a text node, then that is the node of interest nodeToExamine = parent; } else if (nsEditor::NodeIsType(parent, nsEditProperty::html) && offset == rootOffset) { // if we have selected the body, let's look at the first editable node mHTMLEditor->GetNextNode(parent, offset, true, address_of(nodeToExamine)); } else { nsCOMArray arrayOfRanges; res = GetPromotedRanges(selection, arrayOfRanges, EditAction::align); NS_ENSURE_SUCCESS(res, res); // use these ranges to construct a list of nodes to act on. nsCOMArray arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::align, true); NS_ENSURE_SUCCESS(res, res); nodeToExamine = arrayOfNodes.SafeObjectAt(0); } NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); NS_NAMED_LITERAL_STRING(typeAttrName, "align"); nsIAtom *dummyProperty = nullptr; nsCOMPtr blockParent; if (mHTMLEditor->IsBlockNode(nodeToExamine)) blockParent = nodeToExamine; else blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine); NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); if (mHTMLEditor->IsCSSEnabled()) { nsCOMPtr blockParentContent = do_QueryInterface(blockParent); if (blockParentContent && mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParentContent, 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 mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet( blockParentContent, 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 nsCOMPtr temp = nodeToExamine; 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(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; res = nodeToExamine->GetParentNode(getter_AddRefs(temp)); if (NS_FAILED(res)) temp = nullptr; nodeToExamine = temp; } return NS_OK; } nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) { nsAutoString direction; aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction); return direction.EqualsLiteral("rtl") ? nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft; } nsresult nsHTMLEditRules::GetIndentState(bool *aCanIndent, bool *aCanOutdent) { NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE); *aCanIndent = true; *aCanOutdent = false; // get selection nsCOMPtrselection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); nsCOMPtr selPriv(do_QueryInterface(selection)); NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE); // contruct a list of nodes to act on. nsCOMArray arrayOfNodes; res = GetNodesFromSelection(selection, EditAction::indent, arrayOfNodes, true); 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. int32_t listCount = arrayOfNodes.Count(); int32_t i; bool useCSS = mHTMLEditor->IsCSSEnabled(); for (i=listCount-1; i>=0; i--) { nsCOMPtr curNode = arrayOfNodes[i]; if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode)) { *aCanOutdent = true; break; } else if (useCSS) { // we are in CSS mode, indentation is done using the margin-left (or margin-right) property nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); nsAutoString value; // retrieve its specified value mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); float f; nsCOMPtr unit; // get its number part and its unit 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 nsCOMPtr parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot()); NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); nsCOMPtr selection; int32_t selOffset; res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); // test start parent hierarchy 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 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")); nsCOMArray arrayOfNodes; nsresult res = GetParagraphFormatNodes(arrayOfNodes, true); 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. int32_t listCount = arrayOfNodes.Count(); int32_t i; for (i=listCount-1; i>=0; i--) { nsCOMPtr curNode = arrayOfNodes[i]; nsAutoString format; // if it is a known format node we have it easy if (IsBlockNode(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 listCount = arrayOfNodes.Count(); if (!listCount) { nsCOMPtr selNode; int32_t selOffset; nsCOMPtrselection; res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER); arrayOfNodes.AppendObject(selNode); listCount = 1; } // remember root node 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 (i=listCount-1; i>=0; i--) { nsCOMPtr curNode = arrayOfNodes[i]; nsAutoString format; // if it is a known format node we have it easy if (nsHTMLEditUtils::IsFormatNode(curNode)) GetFormatString(curNode, format); else if (IsBlockNode(curNode)) { // this is a div or some other non-format block. // we should ignore it. Its children were appended to this list // by AppendInnerFormatNodes() call above. We will get needed // info when we examine them instead. continue; } else { nsCOMPtr node, tmp = 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(nsCOMArray& aArray, nsIDOMNode *aNode) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); return AppendInnerFormatNodes(aArray, node); } nsresult nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray& 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.AppendObject(child->AsDOMNode()); } else if (!foundInline) { // if this is the first inline we've found, use it foundInline = true; aArray.AppendObject(child->AsDOMNode()); } } 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(nsISelection *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 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // get prior node 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 block1 = mHTMLEditor->GetBlockNodeParent(selNode); block2 = mHTMLEditor->GetBlockNodeParent(priorNode); if (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)) { res = ClearCachedStyles(); NS_ENSURE_SUCCESS(res, res); } 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; nsCOMPtr selNode; int32_t selOffset; // 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()) { 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 nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); // for every property that is set, insert a new inline style node res = CreateStyleForInsertText(aSelection, doc); NS_ENSURE_SUCCESS(res, res); // get the (collapsed) selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // dont put text in places that can't have it if (!mHTMLEditor->IsTextNode(selNode) && !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. 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; res = mHTMLEditor->IsPreformatted(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 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); nsAutoString tString(*inString); const PRUnichar *unicodeBuf = tString.get(); nsCOMPtr unused; 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)) { res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); pos++; } else { 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); 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)) { res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); NS_ENSURE_SUCCESS(res, res); pos++; } else { res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc); NS_ENSURE_SUCCESS(res, res); } NS_ENSURE_SUCCESS(res, res); } } nsCOMPtr selection(aSelection); nsCOMPtr selPriv(do_QueryInterface(selection)); selPriv->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(); } 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(nsISelection *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()) { 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, IsPlaintextEditor(), aHandled); NS_ENSURE_SUCCESS(res, res); if (*aHandled) { return NS_OK; } } // smart splitting rules nsCOMPtr node; int32_t offset; 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 if (!mHTMLEditor->IsModifiableNode(node)) { *aCancel = true; return NS_OK; } // identify the block nsCOMPtr blockParent; if (IsBlockNode(node)) { blockParent = node; } else { 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. 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; res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen); NS_ENSURE_SUCCESS(res, res); nsCOMPtr brNode; 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, nsISelection* aSelection) { nsCOMPtr brNode; bool bAfterBlock = false; bool bBeforeBlock = false; nsresult res = NS_OK; nsCOMPtr node(aNode); nsCOMPtr selPriv(do_QueryInterface(aSelection)); if (IsPlaintextEditor()) { res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode)); } else { nsWSRunObject wsObj(mHTMLEditor, node, aOffset); nsCOMPtr visNode, linkNode; int32_t visOffset = 0, newOffset; WSType wsType; 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; } if (mHTMLEditor->IsInLink(node, address_of(linkNode))) { // split the link nsCOMPtr linkParent; res = linkNode->GetParentNode(getter_AddRefs(linkParent)); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset, &newOffset, true); NS_ENSURE_SUCCESS(res, res); // reset {node,aOffset} to the point where link was split node = linkParent; aOffset = newOffset; } res = wsObj.InsertBreak(address_of(node), &aOffset, address_of(brNode), nsIEditor::eNone); } 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. selPriv->SetInterlinePosition(true); res = aSelection->Collapse(node, aOffset); } else { nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1); nsCOMPtr secondBR; int32_t visOffset = 0; WSType wsType; 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(secondBR, &brOffset); if (brParent != node || brOffset != aOffset + 1) { res = mHTMLEditor->MoveNode(secondBR, 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)) { selPriv->SetInterlinePosition(false); } else { selPriv->SetInterlinePosition(true); } res = aSelection->Collapse(node, aOffset+1); } return res; } nsresult nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) { return NS_OK; } nsresult nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool *aHandled) { NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER); nsCOMPtr selPriv(do_QueryInterface(aSelection)); nsCOMPtr citeNode, selNode, leftCite, rightCite; int32_t selOffset, newOffset; nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext); 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. 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? int32_t unused; if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused)) { // it is. so lets reset our selection to be just after it. selNode = mHTMLEditor->GetNodeLocation(visNode, &selOffset); ++selOffset; } } nsCOMPtr brNode; res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset, true, address_of(leftCite), address_of(rightCite)); NS_ENSURE_SUCCESS(res, res); res = citeNode->GetParentNode(getter_AddRefs(selNode)); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); // want selection before the break, and on same line selPriv->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(citeNode)) { 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) { 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) { res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); } } } // delete any empty cites bool bEmptyCite = false; if (leftCite) { res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false); if (NS_SUCCEEDED(res) && bEmptyCite) res = mHTMLEditor->DeleteNode(leftCite); NS_ENSURE_SUCCESS(res, res); } if (rightCite) { res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false); if (NS_SUCCEEDED(res) && bEmptyCite) 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 param *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; } bool bCollapsed = aSelection->Collapsed(), join = false; // 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 origCollapsed = bCollapsed; nsCOMPtr startNode, selNode; int32_t startOffset, selOffset; // first check for table selection mode. If so, // hand off to table editor. nsCOMPtr cell; nsresult res = mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); if (NS_SUCCEEDED(res) && cell) { res = mHTMLEditor->DeleteTableCellContents(); *aHandled = true; return res; } cell = NULL; res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); if (bCollapsed) { // if we are inside an empty block, delete it. nsCOMPtr hostContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr hostNode = do_QueryInterface(hostContent); NS_ENSURE_TRUE(hostNode, NS_ERROR_FAILURE); res = CheckForEmptyBlock(startNode, hostNode, 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, startNode, startOffset, aAction, aCancel); NS_ENSURE_SUCCESS(res, res); if (*aCancel) return NS_OK; 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 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); bCollapsed = aSelection->Collapsed(); } if (bCollapsed) { // what's in the direction we are deleting? 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; } else if (wsType == WSType::text) { // found normal text to delete. int32_t so = visOffset; int32_t eo = visOffset+1; if (aAction == nsIEditor::ePrevious) { if (so == 0) return NS_ERROR_UNEXPECTED; so--; eo--; } else { nsCOMPtr range; res = aSelection->GetRangeAt(0, getter_AddRefs(range)); NS_ENSURE_SUCCESS(res, res); #ifdef DEBUG nsIDOMNode *container; res = range->GetStartContainer(&container); NS_ENSURE_SUCCESS(res, res); NS_ASSERTION(container == visNode, "selection start not in visNode"); res = range->GetEndContainer(&container); NS_ENSURE_SUCCESS(res, res); NS_ASSERTION(container == visNode, "selection end not in visNode"); #endif res = range->GetStartOffset(&so); NS_ENSURE_SUCCESS(res, res); res = range->GetEndOffset(&eo); NS_ENSURE_SUCCESS(res, res); } res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo); NS_ENSURE_SUCCESS(res, res); nsCOMPtr nodeAsText(do_QueryInterface(visNode)); res = mHTMLEditor->DeleteText(nodeAsText, NS_MIN(so, eo), NS_ABS(eo - so)); *aHandled = true; NS_ENSURE_SUCCESS(res, res); res = InsertBRIfNeeded(aSelection); return res; } else if (wsType == WSType::special || wsType == WSType::br || nsHTMLEditUtils::IsHR(visNode)) { // short circuit for invisible breaks. delete them and recurse. if (nsTextEditUtils::IsBreak(visNode) && !mHTMLEditor->IsVisBreak(visNode)) { 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 && nsHTMLEditUtils::IsHR(visNode)) { /* 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 = nsEditor::GetNodeLocation(visNode, &selOffset); 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; res = 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
res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->DeleteNode(otherNode); NS_ENSURE_SUCCESS(res, res); } return NS_OK; } // else continue with normal delete code } // found break or image, or hr. res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode); NS_ENSURE_SUCCESS(res, res); // remember sibling to visnode, if any nsCOMPtr sibling, stepbrother; mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling)); // delete the node, and join like nodes if appropriate res = mHTMLEditor->DeleteNode(visNode); NS_ENSURE_SUCCESS(res, res); // we did something, so lets say so. *aHandled = true; // is there a prior node and are they siblings? if (sibling) mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother)); if (startNode == stepbrother) { // are they both text nodes? if (mHTMLEditor->IsTextNode(startNode) && mHTMLEditor->IsTextNode(sibling)) { // if so, join them! res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); // fix up selection res = aSelection->Collapse(selNode, selOffset); } } NS_ENSURE_SUCCESS(res, res); res = InsertBRIfNeeded(aSelection); return res; } else 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, leftNode, rightNode, leftParent, rightParent; if (aAction == nsIEditor::ePrevious) { res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode)); NS_ENSURE_SUCCESS(res, res); leftNode = leafNode; rightNode = startNode; } else { res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode)); NS_ENSURE_SUCCESS(res, res); leftNode = startNode; rightNode = leafNode; } if (nsTextEditUtils::IsBreak(otherNode)) { 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. nsCOMPtr newSelNode; int32_t newSelOffset; res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset); NS_ENSURE_SUCCESS(res, res); aSelection->Collapse(newSelNode, newSelOffset); return res; } // else we are joining content to block // find the relavent blocks if (IsBlockNode(leftNode)) leftParent = leftNode; else if (leftNode) leftParent = mHTMLEditor->GetBlockNodeParent(leftNode); if (IsBlockNode(rightNode)) rightParent = rightNode; else rightParent = mHTMLEditor->GetBlockNodeParent(rightNode); // sanity checks NS_ENSURE_TRUE(leftParent && rightParent, NS_ERROR_NULL_POINTER); if (leftParent == rightParent) return NS_ERROR_UNEXPECTED; // now join them nsCOMPtr selPointNode = startNode; int32_t selPointOffset = startOffset; { nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel); *aHandled = true; } aSelection->Collapse(selPointNode, selPointOffset); return res; } else 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 relavent nodes nsCOMPtr leftNode, rightNode, leftParent, rightParent; if (aAction == nsIEditor::ePrevious) { res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode)); NS_ENSURE_SUCCESS(res, res); rightNode = startNode; } else { res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode)); NS_ENSURE_SUCCESS(res, res); 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; } // find the relavent blocks if (IsBlockNode(leftNode)) leftParent = leftNode; else leftParent = mHTMLEditor->GetBlockNodeParent(leftNode); if (IsBlockNode(rightNode)) rightParent = rightNode; else rightParent = mHTMLEditor->GetBlockNodeParent(rightNode); // sanity checks NS_ENSURE_TRUE(leftParent && rightParent, NS_ERROR_NULL_POINTER); if (leftParent == rightParent) return NS_ERROR_UNEXPECTED; // now join them nsCOMPtr selPointNode = startNode; int32_t selPointOffset = startOffset; { nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); res = JoinBlocks(address_of(leftParent), address_of(rightParent), aCancel); *aHandled = true; } aSelection->Collapse(selPointNode, selPointOffset); return res; } } // 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 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); nsCOMPtr endNode; int32_t endOffset; res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(endNode), &endOffset); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); // figure out if the endpoints are in nodes that can be merged // adjust surrounding whitespace in preperation to delete selection if (!IsPlaintextEditor()) { 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 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) { res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); } else { // figure out mailcite ancestors nsCOMPtr endCiteNode, startCiteNode; res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode), IsPlaintextEditor()); NS_ENSURE_SUCCESS(res, res); res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode), IsPlaintextEditor()); NS_ENSURE_SUCCESS(res, res); // 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 nsCOMPtr leftParent; nsCOMPtr rightParent; if (IsBlockNode(startNode)) leftParent = startNode; else leftParent = mHTMLEditor->GetBlockNodeParent(startNode); if (IsBlockNode(endNode)) rightParent = endNode; else rightParent = mHTMLEditor->GetBlockNodeParent(endNode); // are endpoint block parents the same? use default deletion if (leftParent == rightParent) { 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; nsCOMPtr rightBlockParent; leftParent->GetParentNode(getter_AddRefs(leftBlockParent)); rightParent->GetParentNode(getter_AddRefs(rightBlockParent)); // MOOSE: this could conceivably screw up a table.. fix me. if ( (leftBlockParent == rightBlockParent) && (mHTMLEditor->NodesSameType(leftParent, rightParent)) ) { if (nsHTMLEditUtils::IsParagraph(leftParent)) { // first delete the selection res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); // then join para's, insert break res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); NS_ENSURE_SUCCESS(res, res); // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } if (nsHTMLEditUtils::IsListItem(leftParent) || nsHTMLEditUtils::IsHeader(leftParent)) { // first delete the selection res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); NS_ENSURE_SUCCESS(res, res); // join blocks res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); NS_ENSURE_SUCCESS(res, res); // fix up selection res = aSelection->Collapse(selNode,selOffset); return res; } } // else blocks not same type, or not siblings. Delete everything except // table elements. nsCOMPtr enumerator; res = aSelection->GetEnumerator(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED); join = true; for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next()) { nsCOMPtr currentItem; res = enumerator->CurrentItem(getter_AddRefs(currentItem)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED); // build a list of nodes in the range nsCOMPtr range( do_QueryInterface(currentItem) ); nsCOMArray arrayOfNodes; nsTrivialFunctor functor; nsDOMSubtreeIterator iter; res = iter.Init(range); NS_ENSURE_SUCCESS(res, res); res = iter.AppendList(functor, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // now that we have the list, delete non table elements int32_t listCount = arrayOfNodes.Count(); for (int32_t j = 0; j < listCount; j++) { nsCOMPtr somenode = do_QueryInterface(arrayOfNodes[0]); NS_ENSURE_STATE(somenode); DeleteNonTableElements(somenode); arrayOfNodes.RemoveObjectAt(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) { mHTMLEditor->IsVisTextNode(content, &join, true); } else { join = content->IsHTML(nsGkAtoms::br) && !mHTMLEditor->IsVisBreak(somenode->AsDOMNode()); } } } } // check endopints 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 ( mHTMLEditor->IsTextNode(startNode) ) { // delete to last character nsCOMPtrnodeAsText; uint32_t len; nodeAsText = do_QueryInterface(startNode); nodeAsText->GetLength(&len); if (len > (uint32_t)startOffset) { res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset); NS_ENSURE_SUCCESS(res, res); } } if ( mHTMLEditor->IsTextNode(endNode) ) { // delete to first character nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(endNode); if (endOffset) { res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset); NS_ENSURE_SUCCESS(res, res); } } if (join) { res = JoinBlocks(address_of(leftParent), address_of(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 (join ? aAction == nsIEditor::eNext : aAction == nsIEditor::ePrevious) { res = aSelection->Collapse(endNode,endOffset); } else { res = aSelection->Collapse(startNode,startOffset); } return res; } /***************************************************************************************************** * 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. * nsISelection *aSelection the collapsed selection */ nsresult nsHTMLEditRules::InsertBRIfNeeded(nsISelection *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(node)) return res; // examine selection 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 if (mHTMLEditor->CanContainTag(node, nsGkAtoms::br)) { nsCOMPtr brNode; res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious); } } return res; } /***************************************************************************************************** * GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were * trying to have a caret next to it. * nsIDOMNode *aNode the node * nsIEditor::EDirection aAction which edge to find: eNext indicates beginning, ePrevious ending * nsCOMPtr *outSelNode desired sel node * int32_t *outSelOffset desired sel offset */ nsresult nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction, nsCOMPtr *outSelNode, int32_t *outSelOffset) { NS_ENSURE_TRUE(aNode && outSelNode && outSelOffset, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; // default values *outSelNode = aNode; *outSelOffset = 0; if (mHTMLEditor->IsTextNode(aNode) || mHTMLEditor->IsContainer(aNode)) { if (aAction == nsIEditor::ePrevious) { uint32_t len; res = mHTMLEditor->GetLengthOfDOMNode(aNode, len); *outSelOffset = int32_t(len); NS_ENSURE_SUCCESS(res, res); } } else { *outSelNode = nsEditor::GetNodeLocation(aNode, outSelOffset); if (!nsTextEditUtils::IsBreak(aNode) || mHTMLEditor->IsVisBreak(aNode)) { if (aAction == nsIEditor::ePrevious) (*outSelOffset)++; } } return res; } /***************************************************************************************************** * 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(nsCOMPtr *aLeftBlock, nsCOMPtr *aRightBlock, bool *aCanceled) { NS_ENSURE_TRUE(aLeftBlock && aRightBlock && *aLeftBlock && *aRightBlock, NS_ERROR_NULL_POINTER); 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)) { nsCOMPtr realLeft = mHTMLEditor->GetBlockNodeParent(*aLeftBlock); *aLeftBlock = realLeft; } if (nsHTMLEditUtils::IsHR(*aRightBlock)) { nsCOMPtr realRight = mHTMLEditor->GetBlockNodeParent(*aRightBlock); *aRightBlock = realRight; } // 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; existingList = mHTMLEditor->GetTag(leftList); } } } 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++; res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBlockEnd); NS_ENSURE_SUCCESS(res, res); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kAfterBlock, &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 childToMove; nsCOMPtr parent(do_QueryInterface(rightList)); NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); nsIContent *child = parent->GetChildAt(theOffset); while (child) { childToMove = do_QueryInterface(child); res = mHTMLEditor->MoveNode(childToMove, leftList, -1); NS_ENSURE_SUCCESS(res, res); child = parent->GetChildAt(rightOffset); } } else { res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset); } 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. res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aRightBlock, nsWSRunObject::kBlockStart); NS_ENSURE_SUCCESS(res, res); res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, aLeftBlock, nsWSRunObject::kBeforeBlock, &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 { res = MoveBlock(*aLeftBlock, *aRightBlock, leftOffset, rightOffset); } 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 res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, *aLeftBlock, *aRightBlock); NS_ENSURE_SUCCESS(res, res); // Do br adjustment. nsCOMPtr brNode; res = CheckForInvisibleBR(*aLeftBlock, kBlockEnd, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); if (bMergeLists || mHTMLEditor->NodesSameType(*aLeftBlock, *aRightBlock)) { // nodes are same type. merge them. nsCOMPtr parent; int32_t offset; res = JoinNodesSmart(*aLeftBlock, *aRightBlock, address_of(parent), &offset); if (NS_SUCCEEDED(res) && 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) { 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) { nsCOMArray arrayOfNodes; nsCOMPtr isupports; // GetNodesFromPoint is the workhorse that figures out what we wnat to move. nsresult res = GetNodesFromPoint(DOMPoint(aRightBlock,aRightOffset), EditAction::makeList, arrayOfNodes, true); NS_ENSURE_SUCCESS(res, res); int32_t listCount = arrayOfNodes.Count(); int32_t i; for (i=0; iDeleteNode(curNode); } else { // otherwise move the content as is, checking against the dtd. res = MoveNodeSmart(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) { NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); nsresult res; // check if this node can go into the destination node if (mHTMLEditor->CanContain(aDest, aSource)) { // if it can, move it there res = mHTMLEditor->MoveNode(aSource, aDest, *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); 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_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)) { 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(nsISelection *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; res = GetTopEnclosingMailCite(startNode, address_of(citeNode), IsPlaintextEditor()); NS_ENSURE_SUCCESS(res, res); if (citeNode) { nsCOMPtr cite = do_QueryInterface(citeNode); bool isEmpty = true, seenBR = false; mHTMLEditor->IsEmptyNodeImpl(cite, &isEmpty, true, true, false, &seenBR); if (isEmpty) { nsCOMPtr brNode; int32_t offset; nsCOMPtr parent = nsEditor::GetNodeLocation(citeNode, &offset); res = mHTMLEditor->DeleteNode(citeNode); NS_ENSURE_SUCCESS(res, res); if (parent && seenBR) { res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); 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; } nsCOMPtr listTypeAtom = do_GetAtom(*aListType); NS_ENSURE_TRUE(listTypeAtom, NS_ERROR_OUT_OF_MEMORY); 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 (listTypeAtom == 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); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsCOMArray arrayOfNodes; res = GetListActionNodes(arrayOfNodes, aEntireList); NS_ENSURE_SUCCESS(res, res); int32_t listCount = arrayOfNodes.Count(); // check if all our nodes are
s, or empty inlines bool bOnlyBreaks = true; for (int32_t j = 0; j < listCount; j++) { nsIDOMNode* curNode = arrayOfNodes[j]; // if curNode is not a Break or empty inline, we're done if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) { bOnlyBreaks = false; break; } } // if no nodes, we make empty list. Ditto if the user tried to make a list // of some # of breaks. if (!listCount || bOnlyBreaks) { nsCOMPtr parent, theList, theListItem; int32_t offset; // if only breaks, delete them if (bOnlyBreaks) { for (int32_t j = 0; j < (int32_t)listCount; j++) { res = mHTMLEditor->DeleteNode(arrayOfNodes[j]); NS_ENSURE_SUCCESS(res, res); } } // get selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); // make sure we can put a list here if (!mHTMLEditor->CanContainTag(parent, listTypeAtom)) { *aCancel = true; return NS_OK; } res = SplitAsNeeded(aListType, address_of(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(*aListType, parent, offset, getter_AddRefs(theList)); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(nsDependentAtomString(itemType), theList, 0, getter_AddRefs(theListItem)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = 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. res = LookInsideDivBQandList(arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // Ok, now go through all the nodes and put then in the list, // or whatever is approriate. Wohoo! listCount = arrayOfNodes.Count(); nsCOMPtr curParent; nsCOMPtr curList; nsCOMPtr prevListItem; for (int32_t i = 0; i < listCount; i++) { // here's where we actually figure out what to do nsCOMPtr newBlock; nsCOMPtr curNode = arrayOfNodes[i]; 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)) { res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); prevListItem = 0; continue; } else if (IsEmptyInline(curNode)) { // if curNode is an empty inline container, delete it 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 res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, itemType); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->RemoveBlockContainer(newBlock); NS_ENSURE_SUCCESS(res, res); } else { // replace list with new list type res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, itemType); NS_ENSURE_SUCCESS(res, res); curList = newBlock; } prevListItem = 0; continue; } if (nsHTMLEditUtils::IsListItem(curNode)) { if (mHTMLEditor->GetTag(curParent) != listTypeAtom) { // 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)) { res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock)); NS_ENSURE_SUCCESS(res, res); int32_t offset; nsCOMPtr parent = nsEditor::GetNodeLocation(curParent, &offset); res = mHTMLEditor->CreateNode(*aListType, parent, offset, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); } // move list item to new list res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); // convert list item type if needed if (!mHTMLEditor->NodeIsType(curNode, itemType)) { res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), nsDependentAtomString(itemType)); NS_ENSURE_SUCCESS(res, res); } } 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; } else if (curParent != curList) { // move list item to new list res = mHTMLEditor->MoveNode(curNode, curList, -1); NS_ENSURE_SUCCESS(res, res); } if (!mHTMLEditor->NodeIsType(curNode, itemType)) { res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), nsDependentAtomString(itemType)); NS_ENSURE_SUCCESS(res, res); } } nsCOMPtr curElement = do_QueryInterface(curNode); NS_NAMED_LITERAL_STRING(typestr, "type"); if (aBulletType && !aBulletType->IsEmpty()) { res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType); } else { 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 (nsHTMLEditUtils::IsDiv(curNode)) { prevListItem = nullptr; int32_t j = i + 1; res = GetInnerContent(curNode, arrayOfNodes, &j); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->RemoveContainer(curNode); NS_ENSURE_SUCCESS(res, res); listCount = arrayOfNodes.Count(); continue; } // need to make a list to put things in if we haven't already, if (!curList) { res = SplitAsNeeded(aListType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(*aListType, curParent, offset, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = 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(curNode) && prevListItem) { // this is a continuation of some inline nodes that belong together in // the same list item. use prevListItem 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 (nsHTMLEditUtils::IsParagraph(curNode)) { res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), nsDependentAtomString(itemType)); } else { res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), nsDependentAtomString(itemType)); } NS_ENSURE_SUCCESS(res, res); if (IsInlineNode(curNode)) { prevListItem = listItem; } else { prevListItem = nullptr; } } } else { listItem = curNode; } if (listItem) { // if we made a new list item, deal with it: tuck the listItem into the // end of the active list 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); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsCOMArray arrayOfRanges; res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::makeList); NS_ENSURE_SUCCESS(res, res); // use these ranges to contruct a list of nodes to act on. nsCOMArray arrayOfNodes; res = GetListActionNodes(arrayOfNodes, false); NS_ENSURE_SUCCESS(res, res); // Remove all non-editable nodes. Leave them be. int32_t listCount = arrayOfNodes.Count(); int32_t i; for (i=listCount-1; i>=0; i--) { nsIDOMNode* testNode = arrayOfNodes[i]; if (!mHTMLEditor->IsEditable(testNode)) { arrayOfNodes.RemoveObjectAt(i); } } // reset list count listCount = arrayOfNodes.Count(); // Only act on lists or list items in the array nsCOMPtr curParent; for (i=0; i arrayOfNodes; res = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // Remove all non-editable nodes. Leave them be. int32_t listCount = arrayOfNodes.Count(); int32_t i; for (i=listCount-1; i>=0; i--) { if (!mHTMLEditor->IsEditable(arrayOfNodes[i])) { arrayOfNodes.RemoveObjectAt(i); } } // reset list count listCount = arrayOfNodes.Count(); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { nsCOMPtr parent, theBlock; int32_t offset; // get selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); if (tString.EqualsLiteral("normal") || tString.IsEmpty() ) // we are removing blocks (going to "body text") { nsCOMPtr curBlock = parent; if (!IsBlockNode(curBlock)) curBlock = mHTMLEditor->GetBlockNodeParent(parent); nsCOMPtr curBlockPar; NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER); curBlock->GetParentNode(getter_AddRefs(curBlockPar)); 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; res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); if (brNode && nsTextEditUtils::IsBreak(brNode)) { res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); } // do the splits! res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, true); NS_ENSURE_SUCCESS(res, res); // put a br at the split point 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 nsCOMPtr brNode; res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), true); NS_ENSURE_SUCCESS(res, res); if (brNode && nsTextEditUtils::IsBreak(brNode)) { res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); // we don't need to act on this node any more arrayOfNodes.RemoveObject(brNode); } // make sure we can put a block here res = SplitAsNeeded(aBlockType, address_of(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = theBlock; // delete anything that was in the list of nodes for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) { nsCOMPtr curNode = arrayOfNodes[0]; res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveObjectAt(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, aBlockType); return res; } return res; } nsresult nsHTMLEditRules::DidMakeBasicBlock(nsISelection *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; 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); nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); nsCOMArray arrayOfRanges; nsCOMArray arrayOfNodes; // short circuit: detect case of collapsed selection inside an
  • . // just sublist that
  • . This prevents bug 97797. nsCOMPtr liNode; if (aSelection->Collapsed()) { nsCOMPtr node, block; int32_t offset; nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); NS_ENSURE_SUCCESS(res, res); if (IsBlockNode(node)) block = node; else block = mHTMLEditor->GetBlockNodeParent(node); if (block && nsHTMLEditUtils::IsListItem(block)) liNode = block; } if (liNode) { arrayOfNodes.AppendObject(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); } NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { nsCOMPtr parent, theBlock; int32_t offset; nsAutoString quoteType(NS_LITERAL_STRING("div")); // get selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); // make sure we can put a block here res = SplitAsNeeded("eType, address_of(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = theBlock; RelativeChangeIndentationOfElementNode(theBlock, +1); // delete anything that was in the list of nodes for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) { nsCOMPtr curNode = arrayOfNodes[0]; res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveObjectAt(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 curQuote; nsCOMPtr curList; nsCOMPtr sibling; int32_t listCount = arrayOfNodes.Count(); for (i=0; i curNode = arrayOfNodes[i]; // Ignore all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(curNode)) continue; int32_t offset; curParent = nsEditor::GetNodeLocation(curNode, &offset); // 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. mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); if (sibling && nsHTMLEditUtils::IsList(sibling)) { nsAutoString curListTag, siblingListTag; nsEditor::GetTagString(curParent, curListTag); nsEditor::GetTagString(sibling, siblingListTag); if (curListTag == siblingListTag) { 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. mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); if (sibling && nsHTMLEditUtils::IsList(sibling)) { nsAutoString curListTag, siblingListTag; nsEditor::GetTagString(curParent, curListTag); nsEditor::GetTagString(sibling, siblingListTag); if (curListTag == siblingListTag) { 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) mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); if (!curList || (sibling && sibling != curList)) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); ToLowerCase(listTag); // create a new nested list of correct type res = SplitAsNeeded(&listTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); // curList is now the correct thing to put curNode in // remember our new block for postprocessing mNewBlock = curList; } // tuck the node into the end of the active list uint32_t listLen; res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->MoveNode(curNode, curList, listLen); NS_ENSURE_SUCCESS(res, res); } else // not a list item { if (IsBlockNode(curNode)) { RelativeChangeIndentationOfElementNode(curNode, +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 } NS_NAMED_LITERAL_STRING(divquoteType, "div"); res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote)); NS_ENSURE_SUCCESS(res, res); RelativeChangeIndentationOfElementNode(curQuote, +1); // remember our new block for postprocessing mNewBlock = curQuote; // curQuote is now the correct thing to put curNode in } // tuck the node into the end of the active blockquote uint32_t quoteLen; res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen); NS_ENSURE_SUCCESS(res, res); 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); 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 nsCOMArray arrayOfRanges; res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::indent); NS_ENSURE_SUCCESS(res, res); // use these ranges to contruct a list of nodes to act on. nsCOMArray arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent); NS_ENSURE_SUCCESS(res, res); NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { nsCOMPtr parent, theBlock; int32_t offset; // get selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); // make sure we can put a block here res = SplitAsNeeded("eType, address_of(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = theBlock; // delete anything that was in the list of nodes for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) { nsCOMPtr curNode = arrayOfNodes[0]; res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveObjectAt(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, curQuote, curList, indentedLI, sibling; int32_t listCount = arrayOfNodes.Count(); for (i=0; i curNode = arrayOfNodes[i]; // Ignore all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(curNode)) continue; int32_t offset; curParent = nsEditor::GetNodeLocation(curNode, &offset); // 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. mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); if (sibling && nsHTMLEditUtils::IsList(sibling)) { nsAutoString curListTag, siblingListTag; nsEditor::GetTagString(curParent, curListTag); nsEditor::GetTagString(sibling, siblingListTag); if (curListTag == siblingListTag) { 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. mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); if (sibling && nsHTMLEditUtils::IsList(sibling)) { nsAutoString curListTag, siblingListTag; nsEditor::GetTagString(curParent, curListTag); nsEditor::GetTagString(sibling, siblingListTag); if (curListTag == siblingListTag) { 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) mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); if (!curList || (sibling && sibling != curList) ) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); ToLowerCase(listTag); // create a new nested list of correct type res = SplitAsNeeded(&listTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); // 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 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) continue; // already indented this list item curParent = nsEditor::GetNodeLocation(listitem, &offset); // 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; mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); } if (!curList || (sibling && sibling != curList) ) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); ToLowerCase(listTag); // create a new nested list of correct type res = SplitAsNeeded(&listTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); } 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("eType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = curQuote; // curQuote is now the correct thing to put curNode in } // tuck the node into the end of the active blockquote 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; bool useCSS = mHTMLEditor->IsCSSEnabled(); res = NormalizeSelection(aSelection); NS_ENSURE_SUCCESS(res, res); // some scoping for selection resetting - we may need to tweak it { 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 nsCOMArray 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; int32_t listCount = arrayOfNodes.Count(); int32_t i; nsCOMPtr curParent; for (i=0; i curNode = arrayOfNodes[i]; int32_t offset; curParent = nsEditor::GetNodeLocation(curNode, &offset); // is it a blockquote? if (nsHTMLEditUtils::IsBlockquote(curNode)) { // 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; } res = mHTMLEditor->RemoveBlockContainer(curNode); NS_ENSURE_SUCCESS(res, res); continue; } // is it a block with a 'margin' property? if (useCSS && IsBlockNode(curNode)) { nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); nsAutoString value; mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); float f; nsCOMPtr unit; mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (f > 0) { RelativeChangeIndentationOfElementNode(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(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(curNode, curBlockQuote)) { lastBQChild = 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; nsCOMPtr tmp; 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 (!nsTextEditUtils::IsBody(n) && mHTMLEditor->IsDescendantOfEditorRoot(n) && (nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n))) { n->GetParentNode(getter_AddRefs(tmp)); if (!tmp) { break; } n = tmp; if (nsHTMLEditUtils::IsBlockquote(n)) { // if so, remember it, and remember first node we are taking out of it. curBlockQuote = n; firstBQChild = curNode; lastBQChild = curNode; break; } else if (useCSS) { nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); nsAutoString value; mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value); float f; nsCOMPtr unit; mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (f > 0 && !(nsHTMLEditUtils::IsList(curParent) && nsHTMLEditUtils::IsList(curNode))) { curBlockQuote = n; firstBQChild = curNode; lastBQChild = 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 { res = mHTMLEditor->RemoveBlockContainer(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(getter_AddRefs(child)); while (child) { if (nsHTMLEditUtils::IsListItem(child)) { bool bOutOfList; res = PopListItem(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. res = mHTMLEditor->MoveNode(child, curParent, offset + 1); NS_ENSURE_SUCCESS(res, res); } else { // delete any non- list items for now res = mHTMLEditor->DeleteNode(child); NS_ENSURE_SUCCESS(res, res); } curNode->GetLastChild(getter_AddRefs(child)); } // delete the now-empty list res = mHTMLEditor->RemoveBlockContainer(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; 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 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. return left side of block (if any) in // aLeftNode. return right side of block (if any) in // aRightNode. // nsresult nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock, nsIDOMNode *aStartChild, nsIDOMNode *aEndChild, nsCOMPtr *aLeftNode, nsCOMPtr *aRightNode) { nsCOMPtr middleNode; nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, aLeftNode, aRightNode, address_of(middleNode)); NS_ENSURE_SUCCESS(res, res); // get rid of part of blockquote we are outdenting return mHTMLEditor->RemoveBlockContainer(aBlock); } 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, offset; nsresult res; // get split point location nsCOMPtr startParent = nsEditor::GetNodeLocation(aStartChild, &startOffset); // do the splits! res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset, true, address_of(leftNode), address_of(rightNode)); NS_ENSURE_SUCCESS(res, res); if (rightNode) aBlock = rightNode; // remember left portion of block if caller requested if (aLeftNode) *aLeftNode = leftNode; // get split point location nsCOMPtr endParent = nsEditor::GetNodeLocation(aEndChild, &endOffset); endOffset++; // want to be after lastBQChild // do the splits! res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset, true, address_of(leftNode), address_of(rightNode)); NS_ENSURE_SUCCESS(res, res); if (leftNode) aBlock = leftNode; // remember right portion of block if caller requested if (aRightNode) *aRightNode = 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 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(nsINode* 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->IsHTML(aItemType)) { nsCOMPtr temp; nsresult rv = mHTMLEditor->ReplaceContainer(child, getter_AddRefs(temp), nsDependentAtomString(aItemType)); NS_ENSURE_SUCCESS(rv, rv); child = temp.forget(); } else if (nsHTMLEditUtils::IsList(element) && !element->IsHTML(aListType)) { nsCOMPtr temp; nsresult rv = ConvertListType(child, getter_AddRefs(temp), aListType, aItemType); NS_ENSURE_SUCCESS(rv, rv); child = temp.forget(); } } child = child->GetNextSibling(); } if (aList->IsElement() && aList->AsElement()->IsHTML(aListType)) { nsCOMPtr list = aList->AsElement(); list.forget(aOutList); return NS_OK; } return mHTMLEditor->ReplaceContainer(aList, aOutList, nsDependentAtomString(aListType)); } /////////////////////////////////////////////////////////////////////////// // CreateStyleForInsertText: take care of clearing and setting appropriate // style nodes for text insertion. // // nsresult nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocument *aDoc) { MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState); bool weDidSomething = false; nsCOMPtr node, tmp; int32_t offset; 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; 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) { 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 res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset); NS_ENSURE_SUCCESS(res, res); 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); 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 < abs(relFontSize); j++) { res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, 0, -1); NS_ENSURE_SUCCESS(res, res); } } while (item) { res = mHTMLEditor->SetInlinePropertyOnNode(node, 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; nsCOMArray arrayOfNodes; res = GetNodesFromSelection(aSelection, EditAction::align, arrayOfNodes); 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 = arrayOfNodes.Count(); if (!listCount) emptyDiv = true; if (listCount == 1) { nsCOMPtr theNode = arrayOfNodes[0]; if (nsHTMLEditUtils::SupportsAlignAttr(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 arrayOfNodes, as // the paragraph above points out. Do we rely on the selection // start node because of the fact that arrayOfNodes can be empty? // We should probably revisit this issue. - kin nsCOMPtr parent; int32_t offset; res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) emptyDiv = true; } } if (emptyDiv) { int32_t offset; nsCOMPtr brNode, parent, theDiv, sib; NS_NAMED_LITERAL_STRING(divType, "div"); res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = SplitAsNeeded(&divType, address_of(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. res = mHTMLEditor->GetNextHTMLNode(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. res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib)); NS_ENSURE_SUCCESS(res, res); if (!IsBlockNode(sib)) { res = mHTMLEditor->DeleteNode(brNode); NS_ENSURE_SUCCESS(res, res); } } res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = 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(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; res = MakeTransitionList(arrayOfNodes, transitionList); NS_ENSURE_SUCCESS(res, res); // 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 = arrayOfNodes[i]; // Ignore all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(curNode)) continue; int32_t offset; curParent = nsEditor::GetNodeLocation(curNode, &offset); // 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! if (nsEditor::IsTextNode(curNode) && ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) || nsHTMLEditUtils::IsList(curParent))) 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) { nsCOMPtr curElem = do_QueryInterface(curNode); 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(curNode, 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. NS_NAMED_LITERAL_STRING(divType, "div"); if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) { return NS_OK; // cancelled } res = SplitAsNeeded(&divType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = 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 res = mHTMLEditor->MoveNode(curNode, curDiv, -1); NS_ENSURE_SUCCESS(res, res); } return res; } /////////////////////////////////////////////////////////////////////////// // AlignInnerBlocks: align inside table cells or list items // nsresult nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType) { NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); nsresult res; // gather list of table cells or list items nsCOMArray arrayOfNodes; nsTableCellAndListItemFunctor functor; nsDOMIterator iter; res = iter.Init(aNode); NS_ENSURE_SUCCESS(res, res); res = iter.AppendList(functor, arrayOfNodes); NS_ENSURE_SUCCESS(res, res); // now that we have the list, align their contents as requested int32_t listCount = arrayOfNodes.Count(); int32_t j; for (j = 0; j < listCount; j++) { nsIDOMNode* node = arrayOfNodes[0]; res = AlignBlockContents(node, alignType); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveObjectAt(0); } return res; } /////////////////////////////////////////////////////////////////////////// // AlignBlockContents: align contents of a block element // nsresult nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType) { NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); nsresult res; nsCOMPtr firstChild, lastChild, divNode; bool useCSS = mHTMLEditor->IsCSSEnabled(); res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild)); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); NS_ENSURE_SUCCESS(res, res); NS_NAMED_LITERAL_STRING(attr, "align"); if (!firstChild) { // this cell has no content, nothing to align } else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild)) { // the cell already has a div containing all of its content: just // act on this div. nsCOMPtr divElem = do_QueryInterface(firstChild); if (useCSS) { res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); } else { 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 res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode)); NS_ENSURE_SUCCESS(res, res); // set up the alignment on the div nsCOMPtr divElem = do_QueryInterface(divNode); if (useCSS) { res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); } else { 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)) { res = mHTMLEditor->MoveNode(lastChild, divNode, 0); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); NS_ENSURE_SUCCESS(res, res); } } return res; } /////////////////////////////////////////////////////////////////////////// // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle // case of deleting from inside an empty block. // nsresult nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode, nsIDOMNode *aBodyNode, nsISelection *aSelection, bool *aHandled) { // If the editing host is an inline element, bail out early. if (IsInlineNode(aBodyNode)) { return NS_OK; } // if we are inside an empty block, delete it. // Note: do NOT delete table elements this way. nsresult res = NS_OK; nsCOMPtr block, emptyBlock; if (IsBlockNode(aStartNode)) block = aStartNode; else block = mHTMLEditor->GetBlockNodeParent(aStartNode); bool bIsEmptyNode; if (block != aBodyNode) // efficiency hack. avoiding IsEmptyNode() call when in body { res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); NS_ENSURE_SUCCESS(res, res); while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode)) { emptyBlock = block; block = mHTMLEditor->GetBlockNodeParent(emptyBlock); res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); NS_ENSURE_SUCCESS(res, res); } } nsCOMPtr emptyContent = do_QueryInterface(emptyBlock); if (emptyBlock && emptyContent->IsEditable()) { int32_t offset; nsCOMPtr blockParent = nsEditor::GetNodeLocation(emptyBlock, &offset); NS_ENSURE_TRUE(blockParent && offset >= 0, NS_ERROR_FAILURE); if (nsHTMLEditUtils::IsListItem(emptyBlock)) { // are we the first list item in the list? bool bIsFirst; res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst); NS_ENSURE_SUCCESS(res, res); if (bIsFirst) { int32_t listOffset; nsCOMPtr listParent = nsEditor::GetNodeLocation(blockParent, &listOffset); NS_ENSURE_TRUE(listParent && listOffset >= 0, NS_ERROR_FAILURE); // if we are a sublist, skip the br creation if (!nsHTMLEditUtils::IsList(listParent)) { // create a br before list nsCOMPtr brNode; res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); // adjust selection to be right before it res = aSelection->Collapse(listParent, listOffset); NS_ENSURE_SUCCESS(res, res); } // else just let selection perculate 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); } res = mHTMLEditor->DeleteNode(emptyBlock); *aHandled = true; } return res; } nsresult nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock, BRLocation aWhere, nsCOMPtr *outBRNode, int32_t aOffset) { NS_ENSURE_TRUE(aBlock && outBRNode, NS_ERROR_NULL_POINTER); *outBRNode = nullptr; nsCOMPtr testNode; int32_t testOffset = 0; bool runTest = false; if (aWhere == kBlockEnd) { nsCOMPtr rightmostNode = mHTMLEditor->GetRightmostChild(aBlock, true); // no block crossing 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 = wsTester.mStartReasonNode; } } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetInnerContent: aList and aTbl allow the caller to specify what kind // of content to "look inside". If aTbl is true, look inside // any table content, and insert the inner content into the // supplied issupportsarray at offset aIndex. // Similarly with aList and list content. // aIndex is updated to point past inserted elements. // nsresult nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray &outArrayOfNodes, int32_t *aIndex, bool aList, bool aTbl) { NS_ENSURE_TRUE(aNode && aIndex, NS_ERROR_NULL_POINTER); nsCOMPtr node; nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node)); while (NS_SUCCEEDED(res) && node) { if ( ( aList && (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsListItem(node) ) ) || ( aTbl && nsHTMLEditUtils::IsTableElement(node) ) ) { res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl); NS_ENSURE_SUCCESS(res, res); } else { outArrayOfNodes.InsertObjectAt(node, *aIndex); (*aIndex)++; } nsCOMPtr tmp; res = node->GetNextSibling(getter_AddRefs(tmp)); node = tmp; } return res; } /////////////////////////////////////////////////////////////////////////// // ExpandSelectionForDeletion: this promotes our selection to include blocks // that have all their children selected. // nsresult nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *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 nsCOMPtr range; res = aSelection->GetRangeAt(0, getter_AddRefs(range)); NS_ENSURE_SUCCESS(res, res); 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); // set up for loops and cache our root element bool stillLooking = true; nsCOMPtr visNode, firstBRParent; 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); wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode), &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 == wsObj.mStartReasonNode) || (rootElement == wsObj.mStartReasonNode) ) { stillLooking = false; } else { selStartNode = nsEditor::GetNodeLocation(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); wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode), &visOffset, &wsType); if (wsType == WSType::br) { if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode)) { stillLooking = false; } else { if (!firstBRParent) { firstBRParent = selEndNode; firstBROffset = selEndOffset; } selEndNode = nsEditor::GetNodeLocation(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 == wsObj.mEndReasonNode) || (rootElement == wsObj.mEndReasonNode) ) { stillLooking = false; } else { selEndNode = nsEditor::GetNodeLocation(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 nsRefPtr range = new nsRange(); 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); 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(nsISelection *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; nsCOMPtr range; res = inSelection->GetRangeAt(0, getter_AddRefs(range)); NS_ENSURE_SUCCESS(res, res); 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 someNode; 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 wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode), &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 = 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; 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(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 wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode), &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 = 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; 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(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) { mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset, &isSpace, &isNBSP, getter_AddRefs(temp), &offset); } else { 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->GetNodeParent()) { // Okay, can't promote any further return; } offset = node->GetNodeParent()->IndexOf(node); node = node->GetNodeParent(); } // look back through any further inline nodes that aren't across a
    // from us, and that are enclosed in the same block. nsCOMPtr priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); while (priorNode && priorNode->GetNodeParent() && !mHTMLEditor->IsVisBreak(priorNode->AsDOMNode()) && !IsBlockNode(priorNode->AsDOMNode())) { offset = priorNode->GetNodeParent()->IndexOf(priorNode); node = priorNode->GetNodeParent(); 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. nsCOMPtr nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); while (!nearNode && node->Tag() != nsGkAtoms::body && node->GetNodeParent()) { // 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->Tag() == nsGkAtoms::blockquote) { break; } int32_t parentOffset = node->GetNodeParent()->IndexOf(node); nsCOMPtr parent = node->GetNodeParent(); // 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; if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) && (blockLevelAction || !mHTMLEditor->IsDescendantOfEditorRoot(node))) { break; } node = parent; offset = parentOffset; 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->GetNodeParent()) { // Okay, can't promote any further return; } // want to be after the text node offset = 1 + node->GetNodeParent()->IndexOf(node); node = node->GetNodeParent(); } // look ahead through any further inline nodes that aren't across a
    from // us, and that are enclosed in the same block. nsCOMPtr nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); while (nextNode && !IsBlockNode(nextNode->AsDOMNode()) && nextNode->GetNodeParent()) { offset = 1 + nextNode->GetNodeParent()->IndexOf(nextNode); node = nextNode->GetNodeParent(); if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) { break; } 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. nsCOMPtr nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); while (!nearNode && node->Tag() != nsGkAtoms::body && node->GetNodeParent()) { int32_t parentOffset = node->GetNodeParent()->IndexOf(node); nsCOMPtr parent = node->GetNodeParent(); // 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->IsDescendantOfEditorRoot(node) && !mHTMLEditor->IsDescendantOfEditorRoot(parent)) { break; } node = parent; // we want to be AFTER nearNode offset = parentOffset + 1; nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); } *outNode = node->AsDOMNode(); *outOffset = offset; } /////////////////////////////////////////////////////////////////////////// // GetPromotedRanges: run all the selection range endpoint through // GetPromotedPoint() // nsresult nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection, nsCOMArray &outArrayOfRanges, EditAction inOperationType) { NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER); int32_t rangeCount; nsresult res = inSelection->GetRangeCount(&rangeCount); NS_ENSURE_SUCCESS(res, res); int32_t i; nsCOMPtr selectionRange; nsCOMPtr opRange; for (i = 0; i < rangeCount; i++) { res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange)); NS_ENSURE_SUCCESS(res, res); // clone range so we don't muck with actual selection ranges res = selectionRange->CloneRange(getter_AddRefs(opRange)); NS_ENSURE_SUCCESS(res, res); // 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. res = PromoteRange(opRange, inOperationType); NS_ENSURE_SUCCESS(res, res); // stuff new opRange into array outArrayOfRanges.AppendObject(opRange); } return res; } /////////////////////////////////////////////////////////////////////////// // PromoteRange: expand a range to include any parents for which all // editable children are already in range. // nsresult nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, EditAction inOperationType) { NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); nsresult res; nsCOMPtr startNode, endNode; int32_t startOffset, endOffset; res = inRange->GetStartContainer(getter_AddRefs(startNode)); NS_ENSURE_SUCCESS(res, res); res = inRange->GetStartOffset(&startOffset); NS_ENSURE_SUCCESS(res, res); res = inRange->GetEndContainer(getter_AddRefs(endNode)); NS_ENSURE_SUCCESS(res, res); res = inRange->GetEndOffset(&endOffset); NS_ENSURE_SUCCESS(res, res); // 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; if (IsBlockNode(startNode)) block = startNode; else block = mHTMLEditor->GetBlockNodeParent(startNode); if (block) { bool bIsEmptyNode = false; // check for the editing host nsIContent *rootContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr rootNode = do_QueryInterface(rootContent); nsCOMPtr blockNode = do_QueryInterface(block); NS_ENSURE_TRUE(rootNode && blockNode, NS_ERROR_UNEXPECTED); // Make sure we don't go higher than our root element in the content tree if (!nsContentUtils::ContentIsDescendantOf(rootNode, blockNode)) { res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); } if (bIsEmptyNode) { uint32_t numChildren; nsEditor::GetLengthOfDOMNode(block, numChildren); startNode = block; endNode = block; startOffset = 0; endOffset = numChildren; } } } // 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; nsCOMPtr opRange; GetPromotedPoint(kStart, startNode, startOffset, inOperationType, address_of(opStartNode), &opStartOffset); GetPromotedPoint(kEnd, endNode, endOffset, inOperationType, address_of(opEndNode), &opEndOffset); // Make sure that the new range ends up to be in the editable section. if (!mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) || !mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) { return NS_OK; } res = inRange->SetStart(opStartNode, opStartOffset); NS_ENSURE_SUCCESS(res, res); res = inRange->SetEnd(opEndNode, opEndOffset); return res; } class nsUniqueFunctor : public nsBoolDomIterFunctor { public: nsUniqueFunctor(nsCOMArray &aArray) : mArray(aArray) { } virtual bool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers { return mArray.IndexOf(aNode) < 0; } private: nsCOMArray &mArray; }; /////////////////////////////////////////////////////////////////////////// // GetNodesForOperation: run through the ranges in the array and construct // a new array of nodes to be acted on. // nsresult nsHTMLEditRules::GetNodesForOperation(nsCOMArray& inArrayOfRanges, nsCOMArray& outArrayOfNodes, EditAction inOperationType, bool aDontTouchContent) { int32_t rangeCount = inArrayOfRanges.Count(); int32_t i; nsCOMPtr opRange; nsresult res = NS_OK; // bust up any inlines that cross our range endpoints, // but only if we are allowed to touch content. if (!aDontTouchContent) { nsTArray > rangeItemArray; if (!rangeItemArray.AppendElements(rangeCount)) { return NS_ERROR_OUT_OF_MEMORY; } NS_ASSERTION(static_cast(rangeCount) == rangeItemArray.Length(), "How did that happen?"); // first register ranges for special editor gravity for (i = 0; i < rangeCount; i++) { opRange = inArrayOfRanges[0]; rangeItemArray[i] = new nsRangeStore(); rangeItemArray[i]->StoreRange(opRange); mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]); inArrayOfRanges.RemoveObjectAt(0); } // now bust up inlines. Safe to start at rangeCount-1, since we // asserted we have enough items above. for (i = rangeCount-1; i >= 0 && NS_SUCCEEDED(res); i--) { res = BustUpInlinesAtRangeEndpoints(*rangeItemArray[i]); } // then unregister the ranges for (i = 0; i < rangeCount; i++) { nsRangeStore* item = rangeItemArray[i]; mHTMLEditor->mRangeUpdater.DropRangeItem(item); nsRefPtr range; nsresult res2 = item->GetRange(getter_AddRefs(range)); opRange = range; if (NS_FAILED(res2) && NS_SUCCEEDED(res)) { // Remember the failure, but keep going so we make sure to unregister // all our range items. res = res2; } inArrayOfRanges.AppendObject(opRange); } NS_ENSURE_SUCCESS(res, res); } // gather up a list of all the nodes for (i = 0; i < rangeCount; i++) { opRange = inArrayOfRanges[i]; nsDOMSubtreeIterator iter; res = iter.Init(opRange); NS_ENSURE_SUCCESS(res, res); if (outArrayOfNodes.Count() == 0) { nsTrivialFunctor functor; res = iter.AppendList(functor, outArrayOfNodes); NS_ENSURE_SUCCESS(res, res); } else { // We don't want duplicates in outArrayOfNodes, so we use an // iterator/functor that only return nodes that are not already in // outArrayOfNodes. nsCOMArray nodes; nsUniqueFunctor functor(outArrayOfNodes); res = iter.AppendList(functor, nodes); NS_ENSURE_SUCCESS(res, res); if (!outArrayOfNodes.AppendObjects(nodes)) return NS_ERROR_OUT_OF_MEMORY; } } // certain operations should not act on li's and td's, but rather inside // them. alter the list as needed if (inOperationType == EditAction::makeBasicBlock) { int32_t listCount = outArrayOfNodes.Count(); for (i=listCount-1; i>=0; i--) { nsCOMPtr node = outArrayOfNodes[i]; if (nsHTMLEditUtils::IsListItem(node)) { int32_t j=i; outArrayOfNodes.RemoveObjectAt(i); res = GetInnerContent(node, outArrayOfNodes, &j); NS_ENSURE_SUCCESS(res, res); } } } // 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 (inOperationType == EditAction::outdent || inOperationType == EditAction::indent || inOperationType == EditAction::setAbsolutePosition) { int32_t listCount = outArrayOfNodes.Count(); for (i=listCount-1; i>=0; i--) { nsCOMPtr node = outArrayOfNodes[i]; if (nsHTMLEditUtils::IsTableElementButNotTable(node)) { int32_t j=i; outArrayOfNodes.RemoveObjectAt(i); res = GetInnerContent(node, outArrayOfNodes, &j); NS_ENSURE_SUCCESS(res, res); } } } // outdent should look inside of divs. if (inOperationType == EditAction::outdent && !mHTMLEditor->IsCSSEnabled()) { int32_t listCount = outArrayOfNodes.Count(); for (i=listCount-1; i>=0; i--) { nsCOMPtr node = outArrayOfNodes[i]; if (nsHTMLEditUtils::IsDiv(node)) { int32_t j=i; outArrayOfNodes.RemoveObjectAt(i); res = GetInnerContent(node, outArrayOfNodes, &j, false, false); NS_ENSURE_SUCCESS(res, res); } } } // post process the list to break up inline containers that contain br's. // but only for operations that might care, like making lists or para's... if (inOperationType == EditAction::makeBasicBlock || inOperationType == EditAction::makeList || inOperationType == EditAction::align || inOperationType == EditAction::setAbsolutePosition || inOperationType == EditAction::indent || inOperationType == EditAction::outdent) { int32_t listCount = outArrayOfNodes.Count(); for (i=listCount-1; i>=0; i--) { nsCOMPtr node = outArrayOfNodes[i]; if (!aDontTouchContent && IsInlineNode(node) && mHTMLEditor->IsContainer(node) && !mHTMLEditor->IsTextNode(node)) { nsCOMArray arrayOfInlines; res = BustUpInlinesAtBRs(node, arrayOfInlines); NS_ENSURE_SUCCESS(res, res); // put these nodes in outArrayOfNodes, replacing the current node outArrayOfNodes.RemoveObjectAt(i); outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i); } } } return res; } /////////////////////////////////////////////////////////////////////////// // GetChildNodesForOperation: // nsresult nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, nsCOMArray& outArrayOfNodes) { nsCOMPtr node = do_QueryInterface(inNode); NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); for (nsIContent* child = node->GetFirstChild(); child; child = child->GetNextSibling()) { nsIDOMNode* childNode = child->AsDOMNode(); if (!outArrayOfNodes.AppendObject(childNode)) { return NS_ERROR_FAILURE; } } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetListActionNodes: // nsresult nsHTMLEditRules::GetListActionNodes(nsCOMArray &outArrayOfNodes, bool aEntireList, bool aDontTouchContent) { nsresult res = NS_OK; nsCOMPtrselection; res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); nsCOMPtr selPriv(do_QueryInterface(selection)); NS_ENSURE_TRUE(selPriv, 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) { nsCOMPtr enumerator; res = selPriv->GetEnumerator(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED); for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next()) { nsCOMPtr currentItem; res = enumerator->CurrentItem(getter_AddRefs(currentItem)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED); nsCOMPtr range( do_QueryInterface(currentItem) ); nsCOMPtr commonParent, parent, tmp; range->GetCommonAncestorContainer(getter_AddRefs(commonParent)); if (commonParent) { parent = commonParent; while (parent) { if (nsHTMLEditUtils::IsList(parent)) { outArrayOfNodes.AppendObject(parent); break; } parent->GetParentNode(getter_AddRefs(tmp)); parent = tmp; } } } // 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 (outArrayOfNodes.Count()) return NS_OK; } { // We don't like other people messing with our selection! nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); // contruct a list of nodes to act on. res = GetNodesFromSelection(selection, EditAction::makeList, outArrayOfNodes, aDontTouchContent); NS_ENSURE_SUCCESS(res, res); } // pre process our list of nodes... int32_t listCount = outArrayOfNodes.Count(); int32_t i; for (i=listCount-1; i>=0; i--) { nsCOMPtr testNode = outArrayOfNodes[i]; // Remove all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(testNode)) { outArrayOfNodes.RemoveObjectAt(i); } // 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; outArrayOfNodes.RemoveObjectAt(i); res = GetInnerContent(testNode, outArrayOfNodes, &j, false); NS_ENSURE_SUCCESS(res, 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. res = LookInsideDivBQandList(outArrayOfNodes); return res; } /////////////////////////////////////////////////////////////////////////// // LookInsideDivBQandList: // nsresult nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray& aNodeArray) { // 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.Count(); if (listCount != 1) { return NS_OK; } nsCOMPtr curNode = do_QueryInterface(aNodeArray[0]); NS_ENSURE_STATE(curNode); while (curNode->IsElement() && (curNode->AsElement()->IsHTML(nsGkAtoms::div) || nsHTMLEditUtils::IsList(curNode) || curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { // dive as long as there is only one child, and it is a list, div, blockquote uint32_t numChildren = mHTMLEditor->CountEditableChildren(curNode); if (numChildren != 1) { break; } // keep diving // XXX One would expect to dive into the one editable node. nsIContent* tmp = curNode->GetFirstChild(); if (!tmp->IsElement()) { break; } dom::Element* element = tmp->AsElement(); if (!element->IsHTML(nsGkAtoms::div) && !nsHTMLEditUtils::IsList(element) && !element->IsHTML(nsGkAtoms::blockquote)) { break; } // check editablility XXX floppy moose curNode = tmp; } // we've found innermost list/blockquote/div: // replace the one node in the array with these nodes aNodeArray.RemoveObjectAt(0); if (curNode->IsElement() && (curNode->AsElement()->IsHTML(nsGkAtoms::div) || curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { int32_t j = 0; return GetInnerContent(curNode->AsDOMNode(), aNodeArray, &j, false, false); } aNodeArray.AppendObject(curNode->AsDOMNode()); return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetDefinitionListItemTypes: // void nsHTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement, bool* aDT, bool* aDD) { MOZ_ASSERT(aElement); MOZ_ASSERT(aElement->IsHTML(nsGkAtoms::dl)); MOZ_ASSERT(aDT); MOZ_ASSERT(aDD); *aDT = *aDD = false; for (nsIContent* child = aElement->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->IsHTML(nsGkAtoms::dt)) { *aDT = true; } else if (child->IsHTML(nsGkAtoms::dd)) { *aDD = true; } } } /////////////////////////////////////////////////////////////////////////// // GetParagraphFormatNodes: // nsresult nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray& outArrayOfNodes, bool aDontTouchContent) { nsCOMPtrselection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); // contruct a list of nodes to act on. res = GetNodesFromSelection(selection, EditAction::makeBasicBlock, outArrayOfNodes, aDontTouchContent); NS_ENSURE_SUCCESS(res, res); // pre process our list of nodes... int32_t listCount = outArrayOfNodes.Count(); int32_t i; for (i=listCount-1; i>=0; i--) { nsCOMPtr testNode = outArrayOfNodes[i]; // Remove all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(testNode)) { outArrayOfNodes.RemoveObjectAt(i); } // 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.RemoveObjectAt(i); res = GetInnerContent(testNode, outArrayOfNodes, &j); NS_ENSURE_SUCCESS(res, res); } } return res; } /////////////////////////////////////////////////////////////////////////// // BustUpInlinesAtRangeEndpoints: // nsresult nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) { nsresult res = NS_OK; bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); nsCOMPtr endInline = GetHighestInlineParent(item.endNode); // if we have inline parents above range endpoints, split them if (endInline && !isCollapsed) { nsCOMPtr resultEndNode; int32_t resultEndOffset; endInline->GetParentNode(getter_AddRefs(resultEndNode)); res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset, &resultEndOffset, true); NS_ENSURE_SUCCESS(res, res); // reset range item.endNode = resultEndNode; item.endOffset = resultEndOffset; } nsCOMPtr startInline = GetHighestInlineParent(item.startNode); if (startInline) { nsCOMPtr resultStartNode; int32_t resultStartOffset; startInline->GetParentNode(getter_AddRefs(resultStartNode)); res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset, &resultStartOffset, true); NS_ENSURE_SUCCESS(res, res); // reset range item.startNode = resultStartNode; item.startOffset = resultStartOffset; } return res; } /////////////////////////////////////////////////////////////////////////// // BustUpInlinesAtBRs: // nsresult nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, nsCOMArray& outArrayOfNodes) { NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER); // first step is to build up a list of all the break nodes inside // the inline container. nsCOMArray arrayOfBreaks; nsBRNodeFunctor functor; nsDOMIterator iter; nsresult res = iter.Init(inNode); NS_ENSURE_SUCCESS(res, res); res = iter.AppendList(functor, arrayOfBreaks); NS_ENSURE_SUCCESS(res, res); // if there aren't any breaks, just put inNode itself in the array int32_t listCount = arrayOfBreaks.Count(); if (!listCount) { if (!outArrayOfNodes.AppendObject(inNode)) return NS_ERROR_FAILURE; } else { // else we need to bust up inNode along all the breaks nsCOMPtr breakNode; nsCOMPtr inlineParentNode; nsCOMPtr leftNode; nsCOMPtr rightNode; nsCOMPtr splitDeepNode = inNode; nsCOMPtr splitParentNode; int32_t splitOffset, resultOffset, i; inNode->GetParentNode(getter_AddRefs(inlineParentNode)); for (i=0; i< listCount; i++) { breakNode = arrayOfBreaks[i]; NS_ENSURE_TRUE(breakNode, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER); splitParentNode = nsEditor::GetNodeLocation(breakNode, &splitOffset); res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset, &resultOffset, false, address_of(leftNode), address_of(rightNode)); NS_ENSURE_SUCCESS(res, res); // 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 if (!outArrayOfNodes.AppendObject(leftNode)) return NS_ERROR_FAILURE; } // move break outside of container and also put in node list res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset); NS_ENSURE_SUCCESS(res, res); if (!outArrayOfNodes.AppendObject(breakNode)) return NS_ERROR_FAILURE; // now rightNode becomes the new node to split splitDeepNode = rightNode; } // now tack on remaining rightNode, if any, to the list if (rightNode) { if (!outArrayOfNodes.AppendObject(rightNode)) return NS_ERROR_FAILURE; } } return res; } 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 point, EditAction operation, nsCOMArray &arrayOfNodes, bool dontTouchContent) { nsresult res; // get our point nsCOMPtr node; int32_t offset; point.GetPoint(node, offset); // use it to make a range nsRefPtr range = new nsRange(); res = range->SetStart(node, offset); NS_ENSURE_SUCCESS(res, res); /* SetStart() will also set the end for this new range res = range->SetEnd(node, offset); NS_ENSURE_SUCCESS(res, res); */ // expand the range to include adjacent inlines res = PromoteRange(range, operation); NS_ENSURE_SUCCESS(res, res); // make array of ranges nsCOMArray arrayOfRanges; // stuff new opRange into array arrayOfRanges.AppendObject(range); // use these ranges to contruct a list of nodes to act on. res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); return res; } /////////////////////////////////////////////////////////////////////////// // GetNodesFromSelection: given a particular operation, construct a list // of nodes from the selection that will be operated on. // nsresult nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection, EditAction operation, nsCOMArray& arrayOfNodes, bool dontTouchContent) { NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); nsresult res; // promote selection ranges nsCOMArray arrayOfRanges; res = GetPromotedRanges(selection, arrayOfRanges, operation); NS_ENSURE_SUCCESS(res, res); // use these ranges to contruct a list of nodes to act on. res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); return res; } /////////////////////////////////////////////////////////////////////////// // MakeTransitionList: detect all the transitions in the array, where a // transition means that adjacent nodes in the array // don't have the same parent. // nsresult nsHTMLEditRules::MakeTransitionList(nsCOMArray& inArrayOfNodes, nsTArray &inTransitionArray) { uint32_t listCount = inArrayOfNodes.Count(); inTransitionArray.EnsureLengthAtLeast(listCount); uint32_t i; nsCOMPtr prevElementParent; nsCOMPtr curElementParent; for (i=0; iGetParentNode(getter_AddRefs(curElementParent)); if (curElementParent != prevElementParent) { // different parents, or separated by
    : transition point inTransitionArray[i] = true; } else { // same parents: these nodes grew up together inTransitionArray[i] = false; } prevElementParent = curElementParent; } return NS_OK; } /******************************************************** * 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(); } nsINode* nsHTMLEditRules::IsInListItem(nsINode* aNode) { NS_ENSURE_TRUE(aNode, nullptr); if (nsHTMLEditUtils::IsListItem(aNode)) { return aNode; } nsINode* parent = aNode->GetNodeParent(); while (parent && mHTMLEditor->IsDescendantOfEditorRoot(parent) && !nsHTMLEditUtils::IsTableElement(parent)) { if (nsHTMLEditUtils::IsListItem(parent)) { return parent; } parent = parent->GetNodeParent(); } return nullptr; } /////////////////////////////////////////////////////////////////////////// // ReturnInHeader: do the right thing for returns pressed in headers // nsresult nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aNode, int32_t aOffset) { NS_ENSURE_TRUE(aSelection && aHeader && aNode, 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 nsCOMPtr selNode = aNode; nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); NS_ENSURE_SUCCESS(res, res); // split the header int32_t newOffset; res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset); NS_ENSURE_SUCCESS(res, res); // if the leftand heading is empty, put a mozbr in it nsCOMPtr prevItem; mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsHeader(prevItem)) { bool bIsEmptyNode; 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) { 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; res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling)); NS_ENSURE_SUCCESS(res, res); if (!sibling || !nsTextEditUtils::IsBreak(sibling)) { // create a paragraph NS_NAMED_LITERAL_STRING(pType, "p"); nsCOMPtr pNode; res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode)); NS_ENSURE_SUCCESS(res, res); // append a
    to it nsCOMPtr brNode; 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(nsISelection* 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); bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(); bool newBRneeded = false; nsCOMPtr sibling; 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? mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); if (!sibling || !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling)) { newBRneeded = true; } } else if (aOffset == (int32_t)strLength) { // we're at the end of text node... // is there a BR after to it? res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); if (!sibling || !mHTMLEditor->IsVisBreak(sibling) || nsTextEditUtils::HasMozAttr(sibling)) { 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; res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || nsTextEditUtils::HasMozAttr(nearNode)) { // is there a BR after it? res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); 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; 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, nsISelection *aSelection, nsCOMPtr *aSelNode, int32_t *aOffset) { NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; // split para int32_t newOffset; // get ws code to adjust any ws nsCOMPtr leftPara, rightPara; res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset); NS_ENSURE_SUCCESS(res, res); // split the paragraph res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, false, address_of(leftPara), address_of(rightPara)); NS_ENSURE_SUCCESS(res, res); // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p) if (mHTMLEditor->IsVisBreak(aBRNode)) { res = mHTMLEditor->DeleteNode(aBRNode); NS_ENSURE_SUCCESS(res, res); } // remove ID attribute on the paragraph we just created nsCOMPtr rightElt = do_QueryInterface(rightPara); 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(leftPara); NS_ENSURE_SUCCESS(res, res); res = InsertMozBRIfNeeded(rightPara); NS_ENSURE_SUCCESS(res, res); // selection to beginning of right hand para; // look inside any containers that are up front. nsCOMPtr child = mHTMLEditor->GetLeftmostChild(rightPara, 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(nsISelection *aSelection, nsIDOMNode *aListItem, nsIDOMNode *aNode, int32_t aOffset) { NS_ENSURE_TRUE(aSelection && aListItem && aNode, NS_ERROR_NULL_POINTER); nsCOMPtr selection(aSelection); nsCOMPtr selPriv(do_QueryInterface(selection)); 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. nsIContent* rootContent = mHTMLEditor->GetActiveEditingHost(); nsCOMPtr rootNode = do_QueryInterface(rootContent); int32_t itemOffset; nsCOMPtr list = nsEditor::GetNodeLocation(aListItem, &itemOffset); // 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 != list) && mReturnInEmptyLIKillsList) { // get the list offset now -- before we might eventually split the list int32_t offset; nsCOMPtr listparent = nsEditor::GetNodeLocation(list, &offset); // are we the last list item in the list? bool bIsLast; res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast); NS_ENSURE_SUCCESS(res, res); if (!bIsLast) { // we need to split the list! nsCOMPtr tempNode; res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode)); NS_ENSURE_SUCCESS(res, res); } // are we in a sublist? if (nsHTMLEditUtils::IsList(listparent)) //in a sublist { // if so, move this list item out of this list and into the grandparent list res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1); NS_ENSURE_SUCCESS(res, res); res = aSelection->Collapse(aListItem,0); } else { // otherwise kill this listitem res = mHTMLEditor->DeleteNode(aListItem); NS_ENSURE_SUCCESS(res, res); // time to insert a paragraph NS_NAMED_LITERAL_STRING(pType, "p"); nsCOMPtr pNode; res = mHTMLEditor->CreateNode(pType, listparent, offset+1, getter_AddRefs(pNode)); NS_ENSURE_SUCCESS(res, res); // append a
    to it nsCOMPtr brNode; 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 = aNode; res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); NS_ENSURE_SUCCESS(res, res); // now split list item int32_t newOffset; res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, false); NS_ENSURE_SUCCESS(res, res); // 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; mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem)); if (prevItem && nsHTMLEditUtils::IsListItem(prevItem)) { bool bIsEmptyNode; res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { res = CreateMozBR(prevItem, 0); NS_ENSURE_SUCCESS(res, res); } else { res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, true); NS_ENSURE_SUCCESS(res, res); if (bIsEmptyNode) { nsCOMPtr nodeAtom = nsEditor::GetTag(aListItem); if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt) { int32_t itemOffset; nsCOMPtr list = nsEditor::GetNodeLocation(aListItem, &itemOffset); nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt")); nsCOMPtr newListItem; 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; 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 { nsWSRunObject wsObj(mHTMLEditor, aListItem, 0); nsCOMPtr visNode; int32_t visOffset = 0; WSType wsType; wsObj.NextVisibleNode(aListItem, 0, address_of(visNode), &visOffset, &wsType); 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(nsCOMArray& arrayOfNodes) { // 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 = NS_OK; nsCOMPtr curNode, curParent, curBlock, newBlock; int32_t offset; int32_t listCount = arrayOfNodes.Count(); nsCOMPtr prevParent; int32_t i; for (i=0; i childArray; res = GetChildNodesForOperation(curNode, childArray); NS_ENSURE_SUCCESS(res, res); 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) { nsCOMPtr temp; curNode->GetParentNode(getter_AddRefs(temp)); if (temp != prevParent) { curBlock = 0; // forget any previous blockquote node we were using prevParent = temp; } } else { curNode->GetParentNode(getter_AddRefs(prevParent)); } // if no curBlock, make one if (!curBlock) { NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); res = SplitAsNeeded("eType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = curBlock; // note: doesn't matter if we set mNewBlock multiple times. } res = mHTMLEditor->MoveNode(curNode, curBlock, -1); NS_ENSURE_SUCCESS(res, res); } return res; } /////////////////////////////////////////////////////////////////////////// // RemoveBlockStyle: make the nodes have no special block type. // nsresult nsHTMLEditRules::RemoveBlockStyle(nsCOMArray& arrayOfNodes) { // 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 = NS_OK; nsCOMPtr curBlock, firstNode, lastNode; int32_t listCount = arrayOfNodes.Count(); for (int32_t i = 0; i < listCount; ++i) { // get the node to act on, and its location nsCOMPtr curNode = arrayOfNodes[i]; nsCOMPtr curElement = do_QueryInterface(curNode); // if curNode is a address, p, header, address, or pre, remove it if (curElement && nsHTMLEditUtils::IsFormatNode(curElement)) { // process any partial progress saved if (curBlock) { res = RemovePartOfBlock(curBlock, firstNode, lastNode); NS_ENSURE_SUCCESS(res, res); curBlock = 0; firstNode = 0; lastNode = 0; } // remove curent block res = mHTMLEditor->RemoveBlockContainer(curNode); NS_ENSURE_SUCCESS(res, res); } else if (curElement && (curElement->IsHTML(nsGkAtoms::table) || curElement->IsHTML(nsGkAtoms::tr) || curElement->IsHTML(nsGkAtoms::tbody) || curElement->IsHTML(nsGkAtoms::td) || nsHTMLEditUtils::IsList(curElement) || curElement->IsHTML(nsGkAtoms::li) || curElement->IsHTML(nsGkAtoms::blockquote) || curElement->IsHTML(nsGkAtoms::div))) { // process any partial progress saved if (curBlock) { res = RemovePartOfBlock(curBlock, firstNode, lastNode); NS_ENSURE_SUCCESS(res, res); curBlock = 0; firstNode = 0; lastNode = 0; } // recursion time nsCOMArray childArray; res = GetChildNodesForOperation(curNode, childArray); NS_ENSURE_SUCCESS(res, res); res = RemoveBlockStyle(childArray); NS_ENSURE_SUCCESS(res, res); } else if (IsInlineNode(curNode)) { if (curBlock) { // if so, is this node a descendant? if (nsEditorUtils::IsDescendantOf(curNode, curBlock)) { lastNode = curNode; continue; // then we don't need to do anything different for this node } else { // otherwise, we have progressed beyond end of curBlock, // so lets 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); curBlock = 0; firstNode = 0; lastNode = 0; // fall out and handle curNode } } curBlock = mHTMLEditor->GetBlockNodeParent(curNode); if (nsHTMLEditUtils::IsFormatNode(curBlock)) { firstNode = curNode; lastNode = curNode; } else curBlock = 0; // not a block kind that we care about. } else { // some node that is already sans block style. skip over it and // process any partial progress saved if (curBlock) { res = RemovePartOfBlock(curBlock, firstNode, lastNode); NS_ENSURE_SUCCESS(res, res); curBlock = 0; firstNode = 0; lastNode = 0; } } } // process any partial progress saved if (curBlock) { res = RemovePartOfBlock(curBlock, firstNode, lastNode); NS_ENSURE_SUCCESS(res, res); curBlock = 0; firstNode = 0; lastNode = 0; } return res; } /////////////////////////////////////////////////////////////////////////// // ApplyBlockStyle: do whatever it takes to make the list of nodes into // one or more blocks of type blockTag. // nsresult nsHTMLEditRules::ApplyBlockStyle(nsCOMArray& arrayOfNodes, const nsAString *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_TRUE(aBlockTag, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; nsCOMPtr curNode, curParent, curBlock, newBlock; int32_t offset; int32_t listCount = arrayOfNodes.Count(); nsString tString(*aBlockTag);////MJUDGE SCC NEED HELP // Remove all non-editable nodes. Leave them be. int32_t j; for (j=listCount-1; j>=0; j--) { if (!mHTMLEditor->IsEditable(arrayOfNodes[j])) { arrayOfNodes.RemoveObjectAt(j); } } // reset list count listCount = arrayOfNodes.Count(); int32_t i; for (i=0; iReplaceContainer(curNode, address_of(newBlock), *aBlockTag, nullptr, nullptr, true); NS_ENSURE_SUCCESS(res, res); } else if (nsHTMLEditUtils::IsTable(curNode) || (curNodeTag.EqualsLiteral("tbody")) || (curNodeTag.EqualsLiteral("tr")) || (curNodeTag.EqualsLiteral("td")) || nsHTMLEditUtils::IsList(curNode) || (curNodeTag.EqualsLiteral("li")) || nsHTMLEditUtils::IsBlockquote(curNode) || nsHTMLEditUtils::IsDiv(curNode)) { curBlock = 0; // forget any previous block used for previous inline nodes // recursion time nsCOMArray childArray; res = GetChildNodesForOperation(curNode, childArray); NS_ENSURE_SUCCESS(res, res); int32_t childCount = childArray.Count(); if (childCount) { res = ApplyBlockStyle(childArray, aBlockTag); NS_ENSURE_SUCCESS(res, res); } else { // make sure we can put a block here res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); nsCOMPtr theBlock; res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = theBlock; } } // if the node is a break, we honor it by putting further nodes in a new parent else if (curNodeTag.EqualsLiteral("br")) { if (curBlock) { curBlock = 0; // forget any previous block used for previous inline nodes res = mHTMLEditor->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, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = curBlock; // note: doesn't matter if we set mNewBlock multiple times. res = mHTMLEditor->MoveNode(curNode, curBlock, -1); NS_ENSURE_SUCCESS(res, res); } } // if curNode is inline, pull it into curBlock // note: it's assumed that consecutive inline nodes in the // arrayOfNodes are actually members of the same block parent. // this happens to be true now as a side effect of how // arrayOfNodes is contructed, but some additional logic should // be added here if that should change else if (IsInlineNode(curNode)) { // if curNode is a non editable, drop it if we are going to
          if (tString.LowerCaseEqualsLiteral("pre") 
            && (!mHTMLEditor->IsEditable(curNode)))
            continue; // do nothing to this block
          
          // if no curBlock, make one
          if (!curBlock)
          {
            res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
            NS_ENSURE_SUCCESS(res, res);
            res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
            NS_ENSURE_SUCCESS(res, res);
            // remember our new block for postprocessing
            mNewBlock = curBlock;
            // note: doesn't matter if we set mNewBlock multiple times.
          }
          
          // if curNode is a Break, replace it with a return if we are going to 
          // xxx floppy moose
     
          // this is a continuation of some inline nodes that belong together in
          // the same block item.  use curBlock
          res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
          NS_ENSURE_SUCCESS(res, res);
        }
      }
      return res;
    }
    
    
    ///////////////////////////////////////////////////////////////////////////
    // SplitAsNeeded:  given a tag name, split inOutParent up to the point   
    //                 where we can insert the tag.  Adjust inOutParent and
    //                 inOutOffset to pint to new location for tag.
    nsresult 
    nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag, 
                                   nsCOMPtr *inOutParent,
                                   int32_t *inOutOffset)
    {
      NS_ENSURE_TRUE(aTag && inOutParent && inOutOffset, NS_ERROR_NULL_POINTER);
      NS_ENSURE_TRUE(*inOutParent, NS_ERROR_NULL_POINTER);
      nsCOMPtr tagParent, temp, splitNode, parent = *inOutParent;
      nsresult res = NS_OK;
      nsCOMPtr tagAtom = do_GetAtom(*aTag);
       
      // check that we have a place that can legally contain the tag
      while (!tagParent)
      {
        // sniffing up the parent tree until we find 
        // a legal place for the block
        if (!parent) break;
        // Don't leave the active editing host
        if (!mHTMLEditor->IsDescendantOfEditorRoot(parent)) {
          nsCOMPtr parentContent = do_QueryInterface(parent);
          if (parentContent != mHTMLEditor->GetActiveEditingHost()) {
            break;
          }
        }
        if (mHTMLEditor->CanContainTag(parent, tagAtom)) {
          tagParent = parent;
          break;
        }
        splitNode = parent;
        parent->GetParentNode(getter_AddRefs(temp));
        parent = temp;
      }
      if (!tagParent)
      {
        // could not find a place to build tag!
        return NS_ERROR_FAILURE;
      }
      if (splitNode)
      {
        // we found a place for block, but above inOutParent.  We need to split nodes.
        res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset);
        NS_ENSURE_SUCCESS(res, res);
        *inOutParent = tagParent;
      }
      return res;
    }      
    
    ///////////////////////////////////////////////////////////////////////////
    // 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.
    nsresult 
    nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, 
                                     nsIDOMNode *aNodeRight, 
                                     nsCOMPtr *aOutMergeParent, 
                                     int32_t *aOutMergeOffset)
    {
      // check parms
      NS_ENSURE_TRUE(aNodeLeft &&  
          aNodeRight && 
          aOutMergeParent &&
          aOutMergeOffset, NS_ERROR_NULL_POINTER);
      
      nsresult res = NS_OK;
      // caller responsible for:
      //   left & right node are same type
      int32_t parOffset;
      nsCOMPtr rightParent;
      nsCOMPtr parent = nsEditor::GetNodeLocation(aNodeLeft, &parOffset);
      aNodeRight->GetParentNode(getter_AddRefs(rightParent));
    
      // if they don't have the same parent, first move the 'right' node 
      // to after the 'left' one
      if (parent != rightParent)
      {
        res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset);
        NS_ENSURE_SUCCESS(res, res);
      }
      
      // defaults for outParams
      *aOutMergeParent = aNodeRight;
      res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((uint32_t*)aOutMergeOffset));
      NS_ENSURE_SUCCESS(res, res);
    
      // separate join rules for differing blocks
      if (nsHTMLEditUtils::IsList(aNodeLeft) ||
          mHTMLEditor->IsTextNode(aNodeLeft))
      {
        // for list's, merge shallow (wouldn't want to combine list items)
        res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
        NS_ENSURE_SUCCESS(res, res);
        return res;
      }
      else
      {
        // remember the last left child, and firt right child
        nsCOMPtr lastLeft, firstRight;
        res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft));
        NS_ENSURE_SUCCESS(res, res);
        res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight));
        NS_ENSURE_SUCCESS(res, res);
    
        // for list items, divs, etc, merge smart
        res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
        NS_ENSURE_SUCCESS(res, res);
    
        if (lastLeft && firstRight &&
            mHTMLEditor->NodesSameType(lastLeft, firstRight) &&
            (nsEditor::IsTextNode(lastLeft) ||
             mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft, firstRight)))
          return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
      }
      return res;
    }
    
    
    nsresult 
    nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, 
                                             nsCOMPtr *aOutCiteNode,
                                             bool aPlainText)
    {
      // check parms
      NS_ENSURE_TRUE(aNode && aOutCiteNode, NS_ERROR_NULL_POINTER);
      
      nsresult res = NS_OK;
      nsCOMPtr node, parentNode;
      node = do_QueryInterface(aNode);
      
      while (node)
      {
        if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) ||
             nsHTMLEditUtils::IsMailCite(node) )
          *aOutCiteNode = node;
        if (nsTextEditUtils::IsBody(node)) break;
        
        res = node->GetParentNode(getter_AddRefs(parentNode));
        NS_ENSURE_SUCCESS(res, res);
        node = parentNode;
      }
    
      return res;
    }
    
    
    nsresult 
    nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode)
    {
      NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
    
      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"))) {
          mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag,
                                                  &(mCachedStyles[j].attr), nullptr,
                                                  isSet, &outValue);
        }
        else
        {
          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
      bool useCSS = mHTMLEditor->IsCSSEnabled();
    
      // get selection point; if it doesn't exist, we have nothing to do
      nsRefPtr selection = mHTMLEditor->GetSelection();
      MOZ_ASSERT(selection);
      if (!selection->GetRangeCount()) {
        // 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
            bAny = mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
              selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue,
              nsHTMLCSSUtils::eComputed);
          }
          if (!bAny) {
            // then check typeinstate and html style
            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)) {
            mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
                                               mCachedStyles[i].attr,
                                               mCachedStyles[i].value);
          }
        }
      }
    
      return NS_OK;
    }
    
    
    nsresult
    nsHTMLEditRules::ClearCachedStyles()
    {
      // clear the mPresent bits in mCachedStyles array
      
      int32_t j;
      for (j=0; j arrayOfNodes;
      nsCOMPtr isupports;
      int32_t nodeCount,j;
      
      // gather list of empty nodes
      nsEmptyEditableFunctor functor(mHTMLEditor);
      nsDOMIterator iter;
      nsresult res = iter.Init(mDocChangeRange);
      NS_ENSURE_SUCCESS(res, res);
      res = iter.AppendList(functor, arrayOfNodes);
      NS_ENSURE_SUCCESS(res, res);
    
      // put moz-br's into these empty li's and td's
      nodeCount = arrayOfNodes.Count();
      for (j = 0; j < nodeCount; j++)
      {
        // 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.
        uint32_t len;
        nsCOMPtr theNode = arrayOfNodes[0];
        arrayOfNodes.RemoveObjectAt(0);
        res = nsEditor::GetLengthOfDOMNode(theNode, len);
        NS_ENSURE_SUCCESS(res, res);
        res = CreateMozBR(theNode, (int32_t)len);
        NS_ENSURE_SUCCESS(res, res);
      }
      
      return res;
    }
    
    nsresult 
    nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
    {
      // get selection point
      nsCOMPtr selNode;
      int32_t selOffset;
      nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
      NS_ENSURE_SUCCESS(res, res);
      
      // ask whitespace object to tweak nbsp's
      return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
    }
    
    nsresult 
    nsHTMLEditRules::PinSelectionToNewBlock(nsISelection *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;
      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
      nsRefPtr range = new nsRange();
      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;
        mHTMLEditor->GetLastEditableChild(mNewBlock, address_of(tmp));
        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;
        mHTMLEditor->GetFirstEditableChild(mNewBlock, address_of(tmp));
        int32_t offset;
        if (!(mHTMLEditor->IsTextNode(tmp) || mHTMLEditor->IsContainer(tmp)))
        {
          tmp = nsEditor::GetNodeLocation(tmp, &offset);
        }
        return aSelection->Collapse(tmp, 0);
      }
    }
    
    nsresult 
    nsHTMLEditRules::CheckInterlinePosition(nsISelection *aSelection)
    {
      NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
      nsCOMPtr selection(aSelection);
      nsCOMPtr selPriv(do_QueryInterface(selection));
    
      // 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;
      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. mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), true); if (node && nsTextEditUtils::IsBreak(node)) { selPriv->SetInterlinePosition(true); return NS_OK; } // are we after a block? If so try set caret to following content mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node)); if (node && IsBlockNode(node)) { selPriv->SetInterlinePosition(true); return NS_OK; } // are we before a block? If so try set caret to prior content mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node)); if (node && IsBlockNode(node)) selPriv->SetInterlinePosition(false); return NS_OK; } nsresult nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction) { NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); nsCOMPtr selection(aSelection); nsCOMPtr selPriv(do_QueryInterface(selection)); // 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; nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); temp = selNode; // are we in an editable node? 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; } // 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. nsCOMPtr theblock; if (IsBlockNode(selNode)) theblock = selNode; else theblock = mHTMLEditor->GetBlockNodeParent(selNode); if (theblock && mHTMLEditor->IsEditable(theblock)) { bool bIsEmptyNode; res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false); NS_ENSURE_SUCCESS(res, res); // check if br can go into the destination node if (bIsEmptyNode && mHTMLEditor->CanContainTag(selNode, nsGkAtoms::br)) { nsCOMPtr rootNode = do_QueryInterface(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(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 nsCOMPtr nearNode; res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); if (nearNode) { // is nearNode also a descendant of same block? nsCOMPtr block, nearBlock; if (IsBlockNode(selNode)) block = selNode; else block = mHTMLEditor->GetBlockNodeParent(selNode); nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode); if (block == nearBlock) { if (nearNode && nsTextEditUtils::IsBreak(nearNode) ) { 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(selNode, selOffset, getter_AddRefs(brNode)); NS_ENSURE_SUCCESS(res, res); selNode = nsEditor::GetNodeLocation(brNode, &selOffset); // selection stays *before* moz-br, sticking to it selPriv->SetInterlinePosition(true); res = aSelection->Collapse(selNode,selOffset); NS_ENSURE_SUCCESS(res, res); } else { nsCOMPtr nextNode; mHTMLEditor->GetNextHTMLNode(nearNode, address_of(nextNode), true); if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) { // selection between br and mozbr. make it stick to mozbr // so that it will be on blank line. selPriv->SetInterlinePosition(true); } } } } } // we aren't in a textnode: are we adjacent to text or a break or an image? res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode), true); NS_ENSURE_SUCCESS(res, res); if (nearNode && (nsTextEditUtils::IsBreak(nearNode) || nsEditor::IsTextNode(nearNode) || nsHTMLEditUtils::IsImage(nearNode) || nsHTMLEditUtils::IsHR(nearNode))) return NS_OK; // this is a good place for the caret to be res = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, address_of(nearNode), true); NS_ENSURE_SUCCESS(res, res); if (nearNode && (nsTextEditUtils::IsBreak(nearNode) || nsEditor::IsTextNode(nearNode) || nsHTMLEditUtils::IsImage(nearNode) || nsHTMLEditUtils::IsHR(nearNode))) return NS_OK; // this is a good place for the caret to be // look for a nearby text node. // prefer the correct direction. res = FindNearSelectableNode(selNode, selOffset, aAction, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); 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) res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); else 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) res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); else 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. while (nearNode && !(mHTMLEditor->IsTextNode(nearNode) || nsTextEditUtils::IsBreak(nearNode) || nsHTMLEditUtils::IsImage(nearNode))) { curNode = nearNode; if (aDirection == nsIEditor::ePrevious) res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode)); else res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode)); NS_ENSURE_SUCCESS(res, res); } 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->GetNodeParent(); } while (aNode2 && !nsHTMLEditUtils::IsTableElement(aNode2)) { aNode2 = aNode2->GetNodeParent(); } return aNode1 != aNode2; } nsresult nsHTMLEditRules::RemoveEmptyNodes() { // 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 becasue 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 interseting 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 = do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); nsresult res = iter->Init(mDocChangeRange); NS_ENSURE_SUCCESS(res, res); nsCOMArray arrayOfEmptyNodes, arrayOfEmptyCites; nsTArray > skipList; // check for empty nodes while (!iter->IsDone()) { nsINode* node = iter->GetCurrentNode(); NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); nsINode* parent = node->GetNodeParent(); uint32_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()) { dom::Element* element = node->AsElement(); if (element->IsHTML(nsGkAtoms::body)) { // don't delete the body } else if ((bIsMailCite = nsHTMLEditUtils::IsMailCite(element)) || element->IsHTML(nsGkAtoms::a) || nsHTMLEditUtils::IsInlineStyle(element) || nsHTMLEditUtils::IsList(element) || element->IsHTML(nsGkAtoms::div)) { // only consider certain nodes to be empty for purposes of removal bIsCandidate = true; } else if (nsHTMLEditUtils::IsFormatNode(element) || nsHTMLEditUtils::IsListItem(element) || element->IsHTML(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 = mHTMLEditor->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.AppendObject(node); } else { arrayOfEmptyNodes.AppendObject(node); } } } if (!bIsEmptyNode) { // put parent on skip list skipList.AppendElement(parent); } } iter->Next(); } // now delete the empty nodes int32_t nodeCount = arrayOfEmptyNodes.Count(); for (int32_t j = 0; j < nodeCount; j++) { nsCOMPtr delNode = arrayOfEmptyNodes[0]->AsDOMNode(); arrayOfEmptyNodes.RemoveObjectAt(0); if (mHTMLEditor->IsModifiableNode(delNode)) { res = mHTMLEditor->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. nodeCount = arrayOfEmptyCites.Count(); for (int32_t j = 0; j < nodeCount; j++) { nsCOMPtr delNode = arrayOfEmptyCites[0]->AsDOMNode(); arrayOfEmptyCites.RemoveObjectAt(0); bool bIsEmptyNode; res = mHTMLEditor->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, brNode; int32_t offset; parent = nsEditor::GetNodeLocation(delNode, &offset); res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); NS_ENSURE_SUCCESS(res, res); } res = mHTMLEditor->DeleteNode(delNode); NS_ENSURE_SUCCESS(res, res); } return res; } nsresult nsHTMLEditRules::SelectionEndpointInNode(nsINode* aNode, bool* aResult) { NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER); nsIDOMNode* node = aNode->AsDOMNode(); *aResult = false; nsCOMPtrselection; nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); nsCOMPtrselPriv(do_QueryInterface(selection)); nsCOMPtr enumerator; res = selPriv->GetEnumerator(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(enumerator, NS_ERROR_UNEXPECTED); for (enumerator->First(); NS_OK!=enumerator->IsDone(); enumerator->Next()) { nsCOMPtr currentItem; res = enumerator->CurrentItem(getter_AddRefs(currentItem)); NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(currentItem, NS_ERROR_UNEXPECTED); nsCOMPtr range( do_QueryInterface(currentItem) ); 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 res; } /////////////////////////////////////////////////////////////////////////// // IsEmptyInline: return true if aNode is an empty inline container // // bool nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode) { if (aNode && IsInlineNode(aNode) && mHTMLEditor->IsContainer(aNode)) { bool bEmpty; mHTMLEditor->IsEmptyNode(aNode, &bEmpty); return bEmpty; } return false; } bool nsHTMLEditRules::ListIsEmptyLine(nsCOMArray &arrayOfNodes) { // 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. int32_t listCount = arrayOfNodes.Count(); NS_ENSURE_TRUE(listCount, true); nsCOMPtr somenode; int32_t j, brCount=0; for (j = 0; j < listCount; j++) { somenode = arrayOfNodes[j]; if (somenode && mHTMLEditor->IsEditable(somenode)) { if (nsTextEditUtils::IsBreak(somenode)) { // first break doesn't count if (brCount) return false; brCount++; } else if (IsEmptyInline(somenode)) { // empty inline, keep looking } else return false; } } return true; } nsresult nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, bool *aOutOfList) { // check parms NS_ENSURE_TRUE(aListItem && aOutOfList, NS_ERROR_NULL_POINTER); // init out params *aOutOfList = false; nsCOMPtr curNode( do_QueryInterface(aListItem)); int32_t offset; nsCOMPtr curParent = nsEditor::GetNodeLocation(curNode, &offset); if (!nsHTMLEditUtils::IsListItem(curNode)) return NS_ERROR_FAILURE; // if it's first or last list item, don't need to split the list // otherwise we do. int32_t parOffset; nsCOMPtr curParPar = nsEditor::GetNodeLocation(curParent, &parOffset); bool bIsFirstListItem; nsresult res = mHTMLEditor->IsFirstEditableChild(curNode, &bIsFirstListItem); NS_ENSURE_SUCCESS(res, res); bool bIsLastListItem; res = mHTMLEditor->IsLastEditableChild(curNode, &bIsLastListItem); NS_ENSURE_SUCCESS(res, res); if (!bIsFirstListItem && !bIsLastListItem) { // split the list nsCOMPtr newBlock; res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock)); NS_ENSURE_SUCCESS(res, res); } if (!bIsFirstListItem) parOffset++; res = mHTMLEditor->MoveNode(curNode, 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(curNode)) { res = mHTMLEditor->RemoveBlockContainer(curNode); 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 res = mHTMLEditor->DeleteNode(child); NS_ENSURE_SUCCESS(res, res); } aList->GetFirstChild(getter_AddRefs(child)); } // delete the now-empty list res = mHTMLEditor->RemoveBlockContainer(aList); NS_ENSURE_SUCCESS(res, res); return res; } nsresult nsHTMLEditRules::ConfirmSelectionInBody() { nsresult res = NS_OK; // get the body nsCOMPtr rootElement = do_QueryInterface(mHTMLEditor->GetRoot()); NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED); // get the selection nsCOMPtrselection; res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); NS_ENSURE_SUCCESS(res, res); // get the selection start location nsCOMPtr selNode, temp, parent; int32_t selOffset; 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 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(nsIDOMRange *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); if (!mHTMLEditor->IsDescendantOfRoot(startNode)) { // just return - we don't need to adjust mDocChangeRange in this case return NS_OK; } if (!mDocChangeRange) { // clone aRange. nsCOMPtr range; res = aRange->CloneRange(getter_AddRefs(range)); mDocChangeRange = static_cast(range.get()); } 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; 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; } // get the (collapsed) selection location nsCOMPtr selNode; int32_t selOffset; nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); NS_ENSURE_SUCCESS(res, res); res = mUtilRange->SetStart(selNode, selOffset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->GetEndNodeAndOffset(aSelection, 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); 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)); } 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; res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock); NS_ENSURE_SUCCESS(res, res); if (nsEditor::NodeIsType(child, nsEditProperty::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 res = mHTMLEditor->RemoveContainer(child); 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 res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align")); NS_ENSURE_SUCCESS(res, res); } if (useCSS) { if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child)) { res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, false); } else { nsAutoString dummyCssValue; res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, 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) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); nsCOMPtr child; nsresult res; if (aStarts) { res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(child)); } else { res = mHTMLEditor->GetLastEditableChild(aNode, address_of(child)); } NS_ENSURE_SUCCESS(res, res); NS_ENSURE_TRUE(child, NS_OK); bool isChildBlock; 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) { res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); } else { res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); } NS_ENSURE_SUCCESS(res, res); if (sibling) { bool isBlock; 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; 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"); 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 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)) { 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; } nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, element); nsAutoString value; mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(aNode, marginProperty, value); float f; nsCOMPtr unit; mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); if (0 == f) { nsAutoString defaultLengthUnit; mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit); unit = do_GetAtom(defaultLengthUnit); } if (nsEditProperty::cssInUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange; else if (nsEditProperty::cssCmUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange; else if (nsEditProperty::cssMmUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange; else if (nsEditProperty::cssPtUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange; else if (nsEditProperty::cssPcUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange; else if (nsEditProperty::cssEmUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange; else if (nsEditProperty::cssExUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange; else if (nsEditProperty::cssPxUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange; else if (nsEditProperty::cssPercentUnit == unit) f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange; if (0 < f) { nsAutoString newValue; newValue.AppendFloat(f); newValue.Append(nsDependentAtomString(unit)); mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(element, marginProperty, newValue, false); return NS_OK; } mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(element, marginProperty, value, false); // remove unnecessary DIV blocks: // we could skip this section but that would cause a FAIL in // editor/libeditor/html/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->IsHTML(nsGkAtoms::div) || node == mHTMLEditor->GetActiveEditingHost() || !mHTMLEditor->IsDescendantOfEditorRoot(node) || nsHTMLEditor::HasAttributes(node)) { return NS_OK; } return mHTMLEditor->RemoveContainer(element); } // // 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; 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); 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 nsCOMArray arrayOfRanges; res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::setAbsolutePosition); NS_ENSURE_SUCCESS(res, res); // use these ranges to contruct a list of nodes to act on. nsCOMArray arrayOfNodes; res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::setAbsolutePosition); NS_ENSURE_SUCCESS(res, res); NS_NAMED_LITERAL_STRING(divType, "div"); // if nothing visible in list, make an empty block if (ListIsEmptyLine(arrayOfNodes)) { nsCOMPtr parent, thePositionedDiv; int32_t offset; // get selection location res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); NS_ENSURE_SUCCESS(res, res); // make sure we can put a block here res = SplitAsNeeded(&divType, address_of(parent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(thePositionedDiv)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = thePositionedDiv; // delete anything that was in the list of nodes for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) { nsCOMPtr curNode = arrayOfNodes[0]; res = mHTMLEditor->DeleteNode(curNode); NS_ENSURE_SUCCESS(res, res); arrayOfNodes.RemoveObjectAt(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! int32_t i; nsCOMPtr curParent, curPositionedDiv, curList, indentedLI, sibling; int32_t listCount = arrayOfNodes.Count(); for (i=0; i curNode = arrayOfNodes[i]; // Ignore all non-editable nodes. Leave them be. if (!mHTMLEditor->IsEditable(curNode)) continue; int32_t offset; curParent = nsEditor::GetNodeLocation(curNode, &offset); // 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) { sibling = nullptr; mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); } if (!curList || (sibling && sibling != curList) ) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); ToLowerCase(listTag); // create a new nested list of correct type res = SplitAsNeeded(&listTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); if (!curPositionedDiv) { int32_t parentOffset; nsCOMPtr curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); mNewBlock = curPositionedDiv; } res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); // 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 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 == listitem) continue; // already indented this list item curParent = nsEditor::GetNodeLocation(listitem, &offset); // 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; mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); } if (!curList || (sibling && sibling != curList) ) { nsAutoString listTag; nsEditor::GetTagString(curParent,listTag); ToLowerCase(listTag); // create a new nested list of correct type res = SplitAsNeeded(&listTag, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); if (!curPositionedDiv) { int32_t parentOffset; nsCOMPtr curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); mNewBlock = curPositionedDiv; } res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); NS_ENSURE_SUCCESS(res, res); } res = mHTMLEditor->MoveNode(listitem, curList, -1); NS_ENSURE_SUCCESS(res, res); // remember we indented this li indentedLI = listitem; } else { // need to make a div to put things in if we haven't already if (!curPositionedDiv) { if (nsHTMLEditUtils::IsDiv(curNode)) { curPositionedDiv = curNode; mNewBlock = curPositionedDiv; curList = nullptr; continue; } res = SplitAsNeeded(&divType, address_of(curParent), &offset); NS_ENSURE_SUCCESS(res, res); res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curPositionedDiv)); NS_ENSURE_SUCCESS(res, res); // remember our new block for postprocessing mNewBlock = curPositionedDiv; // curPositionedDiv is now the correct thing to put curNode in } // tuck the node into the end of the active blockquote res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1); NS_ENSURE_SUCCESS(res, res); // forget curList, if any curList = nullptr; } } } return res; } nsresult nsHTMLEditRules::DidAbsolutePosition() { 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; res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); NS_ENSURE_SUCCESS(res, res); nsAutoSelectionReset selectionResetter(aSelection, 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; res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); NS_ENSURE_SUCCESS(res, res); nsAutoSelectionReset selectionResetter(aSelection, 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; nsCOMPtr kungFuDeathGrip(mHTMLEditor); nsCOMPtr selection; nsresult rv = mHTMLEditor->GetSelection(getter_AddRefs(selection)); if (NS_FAILED(rv)) { 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); }