michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAlgorithm.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCRT.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditProperty.h" michael@0: #include "nsEditor.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLCSSUtils.h" michael@0: #include "nsHTMLEditRules.h" michael@0: #include "nsHTMLEditUtils.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsID.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIDOMText.h" michael@0: #include "nsIHTMLAbsPosEditor.h" michael@0: #include "nsIHTMLDocument.h" michael@0: #include "nsINode.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsPlaintextEditor.h" michael@0: #include "nsRange.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsTArray.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsWSRunObject.h" michael@0: #include michael@0: michael@0: class nsISupports; michael@0: class nsRulesInfo; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE"; michael@0: //const static char* kMOZEditorBogusNodeValue="TRUE"; michael@0: michael@0: enum michael@0: { michael@0: kLonely = 0, michael@0: kPrevSib = 1, michael@0: kNextSib = 2, michael@0: kBothSibs = 3 michael@0: }; michael@0: michael@0: /******************************************************** michael@0: * first some helpful functors we will use michael@0: ********************************************************/ michael@0: michael@0: static bool IsBlockNode(nsIDOMNode* node) michael@0: { michael@0: bool isBlock (false); michael@0: nsHTMLEditor::NodeIsBlockStatic(node, &isBlock); michael@0: return isBlock; michael@0: } michael@0: michael@0: static bool IsInlineNode(nsIDOMNode* node) michael@0: { michael@0: return !IsBlockNode(node); michael@0: } michael@0: michael@0: static bool michael@0: IsStyleCachePreservingAction(EditAction action) michael@0: { michael@0: return action == EditAction::deleteSelection || michael@0: action == EditAction::insertBreak || michael@0: action == EditAction::makeList || michael@0: action == EditAction::indent || michael@0: action == EditAction::outdent || michael@0: action == EditAction::align || michael@0: action == EditAction::makeBasicBlock || michael@0: action == EditAction::removeList || michael@0: action == EditAction::makeDefListItem || michael@0: action == EditAction::insertElement || michael@0: action == EditAction::insertQuotation; michael@0: } michael@0: michael@0: class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor michael@0: { michael@0: public: michael@0: virtual bool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers michael@0: { michael@0: if (nsHTMLEditUtils::IsTableCell(aNode)) return true; michael@0: if (nsHTMLEditUtils::IsListItem(aNode)) return true; michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: class nsBRNodeFunctor : public nsBoolDomIterFunctor michael@0: { michael@0: public: michael@0: virtual bool operator()(nsIDOMNode* aNode) michael@0: { michael@0: if (nsTextEditUtils::IsBreak(aNode)) return true; michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: class nsEmptyEditableFunctor : public nsBoolDomIterFunctor michael@0: { michael@0: public: michael@0: nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} michael@0: virtual bool operator()(nsIDOMNode* aNode) michael@0: { michael@0: if (mHTMLEditor->IsEditable(aNode) && michael@0: (nsHTMLEditUtils::IsListItem(aNode) || michael@0: nsHTMLEditUtils::IsTableCellOrCaption(aNode))) michael@0: { michael@0: bool bIsEmptyNode; michael@0: nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false); michael@0: NS_ENSURE_SUCCESS(res, false); michael@0: if (bIsEmptyNode) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: protected: michael@0: nsHTMLEditor* mHTMLEditor; michael@0: }; michael@0: michael@0: class nsEditableTextFunctor : public nsBoolDomIterFunctor michael@0: { michael@0: public: michael@0: nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {} michael@0: virtual bool operator()(nsIDOMNode* aNode) michael@0: { michael@0: if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode)) michael@0: { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: protected: michael@0: nsHTMLEditor* mHTMLEditor; michael@0: }; michael@0: michael@0: michael@0: /******************************************************** michael@0: * Constructor/Destructor michael@0: ********************************************************/ michael@0: michael@0: nsHTMLEditRules::nsHTMLEditRules() michael@0: { michael@0: InitFields(); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditRules::InitFields() michael@0: { michael@0: mHTMLEditor = nullptr; michael@0: mDocChangeRange = nullptr; michael@0: mListenerEnabled = true; michael@0: mReturnInEmptyLIKillsList = true; michael@0: mDidDeleteSelection = false; michael@0: mDidRangedDelete = false; michael@0: mRestoreContentEditableCount = false; michael@0: mUtilRange = nullptr; michael@0: mJoinOffset = 0; michael@0: mNewBlock = nullptr; michael@0: mRangeItem = new nsRangeStore(); michael@0: // populate mCachedStyles michael@0: mCachedStyles[0] = StyleCache(nsEditProperty::b, EmptyString(), EmptyString()); michael@0: mCachedStyles[1] = StyleCache(nsEditProperty::i, EmptyString(), EmptyString()); michael@0: mCachedStyles[2] = StyleCache(nsEditProperty::u, EmptyString(), EmptyString()); michael@0: mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), EmptyString()); michael@0: mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), EmptyString()); michael@0: mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), EmptyString()); michael@0: mCachedStyles[6] = StyleCache(nsEditProperty::tt, EmptyString(), EmptyString()); michael@0: mCachedStyles[7] = StyleCache(nsEditProperty::em, EmptyString(), EmptyString()); michael@0: mCachedStyles[8] = StyleCache(nsEditProperty::strong, EmptyString(), EmptyString()); michael@0: mCachedStyles[9] = StyleCache(nsEditProperty::dfn, EmptyString(), EmptyString()); michael@0: mCachedStyles[10] = StyleCache(nsEditProperty::code, EmptyString(), EmptyString()); michael@0: mCachedStyles[11] = StyleCache(nsEditProperty::samp, EmptyString(), EmptyString()); michael@0: mCachedStyles[12] = StyleCache(nsEditProperty::var, EmptyString(), EmptyString()); michael@0: mCachedStyles[13] = StyleCache(nsEditProperty::cite, EmptyString(), EmptyString()); michael@0: mCachedStyles[14] = StyleCache(nsEditProperty::abbr, EmptyString(), EmptyString()); michael@0: mCachedStyles[15] = StyleCache(nsEditProperty::acronym, EmptyString(), EmptyString()); michael@0: mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, EmptyString(), EmptyString()); michael@0: mCachedStyles[17] = StyleCache(nsEditProperty::sub, EmptyString(), EmptyString()); michael@0: mCachedStyles[18] = StyleCache(nsEditProperty::sup, EmptyString(), EmptyString()); michael@0: } michael@0: michael@0: nsHTMLEditRules::~nsHTMLEditRules() michael@0: { michael@0: // remove ourselves as a listener to edit actions michael@0: // In some cases, we have already been removed by michael@0: // ~nsHTMLEditor, in which case we will get a null pointer here michael@0: // which we ignore. But this allows us to add the ability to michael@0: // switch rule sets on the fly if we want. michael@0: if (mHTMLEditor) michael@0: mHTMLEditor->RemoveEditActionListener(this); michael@0: } michael@0: michael@0: /******************************************************** michael@0: * XPCOM Cruft michael@0: ********************************************************/ michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules) michael@0: NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules) michael@0: NS_IMPL_QUERY_INTERFACE_INHERITED(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener) michael@0: michael@0: michael@0: /******************************************************** michael@0: * Public methods michael@0: ********************************************************/ michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::Init(nsPlaintextEditor *aEditor) michael@0: { michael@0: InitFields(); michael@0: michael@0: mHTMLEditor = static_cast(aEditor); michael@0: nsresult res; michael@0: michael@0: // call through to base class Init michael@0: res = nsTextEditRules::Init(aEditor); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // cache any prefs we care about michael@0: static const char kPrefName[] = michael@0: "editor.html.typing.returnInEmptyListItemClosesList"; michael@0: nsAdoptingCString returnInEmptyLIKillsList = michael@0: Preferences::GetCString(kPrefName); michael@0: michael@0: // only when "false", becomes FALSE. Otherwise (including empty), TRUE. michael@0: // XXX Why was this pref designed as a string and not bool? michael@0: mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false"); michael@0: michael@0: // make a utility range for use by the listenter michael@0: nsCOMPtr node = mHTMLEditor->GetRoot(); michael@0: if (!node) { michael@0: node = mHTMLEditor->GetDocument(); michael@0: } michael@0: michael@0: NS_ENSURE_STATE(node); michael@0: michael@0: mUtilRange = new nsRange(node); michael@0: michael@0: // set up mDocChangeRange to be whole doc michael@0: // temporarily turn off rules sniffing michael@0: nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); michael@0: if (!mDocChangeRange) { michael@0: mDocChangeRange = new nsRange(node); michael@0: } michael@0: michael@0: if (node->IsElement()) { michael@0: ErrorResult rv; michael@0: mDocChangeRange->SelectNode(*node, rv); michael@0: res = AdjustSpecialBreaks(node); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // add ourselves as a listener to edit actions michael@0: res = mHTMLEditor->AddEditActionListener(this); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DetachEditor() michael@0: { michael@0: if (mHTMLEditor) { michael@0: mHTMLEditor->RemoveEditActionListener(this); michael@0: } michael@0: mHTMLEditor = nullptr; michael@0: return nsTextEditRules::DetachEditor(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::BeforeEdit(EditAction action, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: if (mLockRulesSniffing) return NS_OK; michael@0: michael@0: nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this); michael@0: mDidExplicitlySetInterline = false; michael@0: michael@0: if (!mActionNesting++) michael@0: { michael@0: // clear our flag about if just deleted a range michael@0: mDidRangedDelete = false; michael@0: michael@0: // remember where our selection was before edit action took place: michael@0: michael@0: // get selection michael@0: nsCOMPtr selection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // get the selection start location michael@0: nsCOMPtr selStartNode, selEndNode; michael@0: int32_t selOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: mRangeItem->startNode = selStartNode; michael@0: mRangeItem->startOffset = selOffset; michael@0: michael@0: // get the selection end location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: mRangeItem->endNode = selEndNode; michael@0: mRangeItem->endOffset = selOffset; michael@0: michael@0: // register this range with range updater to track this as we perturb the doc michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: (mHTMLEditor->mRangeUpdater).RegisterRangeItem(mRangeItem); michael@0: michael@0: // clear deletion state bool michael@0: mDidDeleteSelection = false; michael@0: michael@0: // clear out mDocChangeRange and mUtilRange michael@0: if(mDocChangeRange) michael@0: { michael@0: // clear out our accounting of what changed michael@0: mDocChangeRange->Reset(); michael@0: } michael@0: if(mUtilRange) michael@0: { michael@0: // ditto for mUtilRange. michael@0: mUtilRange->Reset(); michael@0: } michael@0: michael@0: // remember current inline styles for deletion and normal insertion operations michael@0: if (action == EditAction::insertText || michael@0: action == EditAction::insertIMEText || michael@0: action == EditAction::deleteSelection || michael@0: IsStyleCachePreservingAction(action)) { michael@0: nsCOMPtr selNode = selStartNode; michael@0: if (aDirection == nsIEditor::eNext) michael@0: selNode = selEndNode; michael@0: res = CacheInlineStyles(selNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // Stabilize the document against contenteditable count changes michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr htmlDoc = do_QueryInterface(doc); michael@0: NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE); michael@0: if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { michael@0: htmlDoc->ChangeContentEditableCount(nullptr, +1); michael@0: mRestoreContentEditableCount = true; michael@0: } michael@0: michael@0: // check that selection is in subtree defined by body node michael@0: ConfirmSelectionInBody(); michael@0: // let rules remember the top level action michael@0: mTheAction = action; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::AfterEdit(EditAction action, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: if (mLockRulesSniffing) return NS_OK; michael@0: michael@0: nsAutoLockRulesSniffing lockIt(this); michael@0: michael@0: NS_PRECONDITION(mActionNesting>0, "bad action nesting!"); michael@0: nsresult res = NS_OK; michael@0: if (!--mActionNesting) michael@0: { michael@0: // do all the tricky stuff michael@0: res = AfterEditInner(action, aDirection); michael@0: michael@0: // free up selectionState range item michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: (mHTMLEditor->mRangeUpdater).DropRangeItem(mRangeItem); michael@0: michael@0: // Reset the contenteditable count to its previous value michael@0: if (mRestoreContentEditableCount) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr htmlDoc = do_QueryInterface(doc); michael@0: NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE); michael@0: if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { michael@0: htmlDoc->ChangeContentEditableCount(nullptr, -1); michael@0: } michael@0: mRestoreContentEditableCount = false; michael@0: } michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::AfterEditInner(EditAction action, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: ConfirmSelectionInBody(); michael@0: if (action == EditAction::ignore) return NS_OK; michael@0: michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr rangeStartParent, rangeEndParent; michael@0: int32_t rangeStartOffset = 0, rangeEndOffset = 0; michael@0: // do we have a real range to act on? michael@0: bool bDamagedRange = false; michael@0: if (mDocChangeRange) michael@0: { michael@0: mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent)); michael@0: mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent)); michael@0: mDocChangeRange->GetStartOffset(&rangeStartOffset); michael@0: mDocChangeRange->GetEndOffset(&rangeEndOffset); michael@0: if (rangeStartParent && rangeEndParent) michael@0: bDamagedRange = true; michael@0: } michael@0: michael@0: if (bDamagedRange && !((action == EditAction::undo) || (action == EditAction::redo))) michael@0: { michael@0: // don't let any txns in here move the selection around behind our back. michael@0: // Note that this won't prevent explicit selection setting from working. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: michael@0: // expand the "changed doc range" as needed michael@0: res = PromoteRange(mDocChangeRange, action); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if we did a ranged deletion, make sure we have a place to put caret. michael@0: // Note we only want to do this if the overall operation was deletion, michael@0: // not if deletion was done along the way for EditAction::loadHTML, EditAction::insertText, etc. michael@0: // That's why this is here rather than DidDeleteSelection(). michael@0: if ((action == EditAction::deleteSelection) && mDidRangedDelete) michael@0: { michael@0: res = InsertBRIfNeeded(selection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // add in any needed
s, and remove any unneeded ones. michael@0: res = AdjustSpecialBreaks(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // merge any adjacent text nodes michael@0: if ( (action != EditAction::insertText && michael@0: action != EditAction::insertIMEText) ) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // clean up any empty nodes in the selection michael@0: res = RemoveEmptyNodes(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // attempt to transform any unneeded nbsp's into spaces after doing various operations michael@0: if ((action == EditAction::insertText) || michael@0: (action == EditAction::insertIMEText) || michael@0: (action == EditAction::deleteSelection) || michael@0: (action == EditAction::insertBreak) || michael@0: (action == EditAction::htmlPaste || michael@0: (action == EditAction::loadHTML))) michael@0: { michael@0: res = AdjustWhitespace(selection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // also do this for original selection endpoints. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject(mHTMLEditor, mRangeItem->startNode, michael@0: mRangeItem->startOffset).AdjustWhitespace(); michael@0: // we only need to handle old selection endpoint if it was different from start michael@0: if (mRangeItem->startNode != mRangeItem->endNode || michael@0: mRangeItem->startOffset != mRangeItem->endOffset) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject(mHTMLEditor, mRangeItem->endNode, michael@0: mRangeItem->endOffset).AdjustWhitespace(); michael@0: } michael@0: } michael@0: michael@0: // if we created a new block, make sure selection lands in it michael@0: if (mNewBlock) michael@0: { michael@0: res = PinSelectionToNewBlock(selection); michael@0: mNewBlock = 0; michael@0: } michael@0: michael@0: // adjust selection for insert text, html paste, and delete actions michael@0: if ((action == EditAction::insertText) || michael@0: (action == EditAction::insertIMEText) || michael@0: (action == EditAction::deleteSelection) || michael@0: (action == EditAction::insertBreak) || michael@0: (action == EditAction::htmlPaste || michael@0: (action == EditAction::loadHTML))) michael@0: { michael@0: res = AdjustSelection(selection, aDirection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // check for any styles which were removed inappropriately michael@0: if (action == EditAction::insertText || michael@0: action == EditAction::insertIMEText || michael@0: action == EditAction::deleteSelection || michael@0: IsStyleCachePreservingAction(action)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mTypeInState->UpdateSelState(selection); michael@0: res = ReapplyCachedStyles(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: ClearCachedStyles(); michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: michael@0: res = mHTMLEditor->HandleInlineSpellCheck(action, selection, michael@0: mRangeItem->startNode, michael@0: mRangeItem->startOffset, michael@0: rangeStartParent, rangeStartOffset, michael@0: rangeEndParent, rangeEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // detect empty doc michael@0: res = CreateBogusNodeIfNeeded(selection); michael@0: michael@0: // adjust selection HINT if needed michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!mDidExplicitlySetInterline) michael@0: { michael@0: res = CheckInterlinePosition(selection); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillDoAction(Selection* aSelection, michael@0: nsRulesInfo* aInfo, michael@0: bool* aCancel, michael@0: bool* aHandled) michael@0: { michael@0: MOZ_ASSERT(aInfo && aCancel && aHandled); michael@0: michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // my kingdom for dynamic cast michael@0: nsTextRulesInfo *info = static_cast(aInfo); michael@0: michael@0: // Deal with actions for which we don't need to check whether the selection is michael@0: // editable. michael@0: if (info->action == EditAction::outputText || michael@0: info->action == EditAction::undo || michael@0: info->action == EditAction::redo) { michael@0: return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); michael@0: } michael@0: michael@0: // Nothing to do if there's no selection to act on michael@0: if (!aSelection) { michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_OK); michael@0: michael@0: nsRefPtr range = aSelection->GetRangeAt(0); michael@0: nsCOMPtr selStartNode = range->GetStartParent(); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsModifiableNode(selStartNode)) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr selEndNode = range->GetEndParent(); michael@0: michael@0: if (selStartNode != selEndNode) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsModifiableNode(selEndNode)) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: switch (info->action) { michael@0: case EditAction::insertText: michael@0: case EditAction::insertIMEText: michael@0: return WillInsertText(info->action, aSelection, aCancel, aHandled, michael@0: info->inString, info->outString, info->maxLength); michael@0: case EditAction::loadHTML: michael@0: return WillLoadHTML(aSelection, aCancel); michael@0: case EditAction::insertBreak: michael@0: return WillInsertBreak(aSelection, aCancel, aHandled); michael@0: case EditAction::deleteSelection: michael@0: return WillDeleteSelection(aSelection, info->collapsedAction, michael@0: info->stripWrappers, aCancel, aHandled); michael@0: case EditAction::makeList: michael@0: return WillMakeList(aSelection, info->blockType, info->entireList, michael@0: info->bulletType, aCancel, aHandled); michael@0: case EditAction::indent: michael@0: return WillIndent(aSelection, aCancel, aHandled); michael@0: case EditAction::outdent: michael@0: return WillOutdent(aSelection, aCancel, aHandled); michael@0: case EditAction::setAbsolutePosition: michael@0: return WillAbsolutePosition(aSelection, aCancel, aHandled); michael@0: case EditAction::removeAbsolutePosition: michael@0: return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled); michael@0: case EditAction::align: michael@0: return WillAlign(aSelection, info->alignType, aCancel, aHandled); michael@0: case EditAction::makeBasicBlock: michael@0: return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled); michael@0: case EditAction::removeList: michael@0: return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled); michael@0: case EditAction::makeDefListItem: michael@0: return WillMakeDefListItem(aSelection, info->blockType, info->entireList, michael@0: aCancel, aHandled); michael@0: case EditAction::insertElement: michael@0: return WillInsert(aSelection, aCancel); michael@0: case EditAction::decreaseZIndex: michael@0: return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled); michael@0: case EditAction::increaseZIndex: michael@0: return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled); michael@0: default: michael@0: return nsTextEditRules::WillDoAction(aSelection, aInfo, michael@0: aCancel, aHandled); michael@0: } michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidDoAction(nsISelection *aSelection, michael@0: nsRulesInfo *aInfo, nsresult aResult) michael@0: { michael@0: nsTextRulesInfo *info = static_cast(aInfo); michael@0: switch (info->action) michael@0: { michael@0: case EditAction::insertBreak: michael@0: return DidInsertBreak(aSelection, aResult); michael@0: case EditAction::deleteSelection: michael@0: return DidDeleteSelection(aSelection, info->collapsedAction, aResult); michael@0: case EditAction::makeBasicBlock: michael@0: case EditAction::indent: michael@0: case EditAction::outdent: michael@0: case EditAction::align: michael@0: return DidMakeBasicBlock(aSelection, aInfo, aResult); michael@0: case EditAction::setAbsolutePosition: { michael@0: nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return DidAbsolutePosition(); michael@0: } michael@0: default: michael@0: // pass thru to nsTextEditRules michael@0: return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL) michael@0: { michael@0: NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER); michael@0: *aMixed = false; michael@0: *aOL = false; michael@0: *aUL = false; michael@0: *aDL = false; michael@0: bool bNonList = false; michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: nsresult res = GetListActionNodes(arrayOfNodes, false, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Examine list type for nodes in selection. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t i = listCount - 1; i >= 0; --i) { michael@0: nsIDOMNode* curDOMNode = arrayOfNodes[i]; michael@0: nsCOMPtr curElement = do_QueryInterface(curDOMNode); michael@0: michael@0: if (!curElement) { michael@0: bNonList = true; michael@0: } else if (curElement->IsHTML(nsGkAtoms::ul)) { michael@0: *aUL = true; michael@0: } else if (curElement->IsHTML(nsGkAtoms::ol)) { michael@0: *aOL = true; michael@0: } else if (curElement->IsHTML(nsGkAtoms::li)) { michael@0: if (dom::Element* parent = curElement->GetParentElement()) { michael@0: if (parent->IsHTML(nsGkAtoms::ul)) { michael@0: *aUL = true; michael@0: } else if (parent->IsHTML(nsGkAtoms::ol)) { michael@0: *aOL = true; michael@0: } michael@0: } michael@0: } else if (curElement->IsHTML(nsGkAtoms::dl) || michael@0: curElement->IsHTML(nsGkAtoms::dt) || michael@0: curElement->IsHTML(nsGkAtoms::dd)) { michael@0: *aDL = true; michael@0: } else { michael@0: bNonList = true; michael@0: } michael@0: } michael@0: michael@0: // hokey arithmetic with booleans michael@0: if ((*aUL + *aOL + *aDL + bNonList) > 1) { michael@0: *aMixed = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD) michael@0: { michael@0: NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER); michael@0: *aMixed = false; michael@0: *aLI = false; michael@0: *aDT = false; michael@0: *aDD = false; michael@0: bool bNonList = false; michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: nsresult res = GetListActionNodes(arrayOfNodes, false, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // examine list type for nodes in selection michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t i = listCount - 1; i >= 0; --i) { michael@0: nsIDOMNode* curNode = arrayOfNodes[i]; michael@0: nsCOMPtr element = do_QueryInterface(curNode); michael@0: if (!element) { michael@0: bNonList = true; michael@0: } else if (element->IsHTML(nsGkAtoms::ul) || michael@0: element->IsHTML(nsGkAtoms::ol) || michael@0: element->IsHTML(nsGkAtoms::li)) { michael@0: *aLI = true; michael@0: } else if (element->IsHTML(nsGkAtoms::dt)) { michael@0: *aDT = true; michael@0: } else if (element->IsHTML(nsGkAtoms::dd)) { michael@0: *aDD = true; michael@0: } else if (element->IsHTML(nsGkAtoms::dl)) { michael@0: // need to look inside dl and see which types of items it has michael@0: bool bDT, bDD; michael@0: GetDefinitionListItemTypes(element, &bDT, &bDD); michael@0: *aDT |= bDT; michael@0: *aDD |= bDD; michael@0: } else { michael@0: bNonList = true; michael@0: } michael@0: } michael@0: michael@0: // hokey arithmetic with booleans michael@0: if ( (*aDT + *aDD + bNonList) > 1) *aMixed = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign) michael@0: { michael@0: // for now, just return first alignment. we'll lie about michael@0: // if it's mixed. This is for efficiency michael@0: // given that our current ui doesn't care if it's mixed. michael@0: // cmanske: NOT TRUE! We would like to pay attention to mixed state michael@0: // in Format | Align submenu! michael@0: michael@0: // this routine assumes that alignment is done ONLY via divs michael@0: michael@0: // default alignment is left michael@0: NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER); michael@0: *aMixed = false; michael@0: *aAlign = nsIHTMLEditor::eLeft; michael@0: michael@0: // get selection michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE); michael@0: michael@0: int32_t offset, rootOffset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(rootElem, &rootOffset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // is the selection collapsed? michael@0: nsCOMPtr nodeToExamine; michael@0: if (selection->Collapsed()) { michael@0: // if it is, we want to look at 'parent' and its ancestors michael@0: // for divs with alignment on them michael@0: nodeToExamine = parent; michael@0: } michael@0: else if (!mHTMLEditor) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: else if (mHTMLEditor->IsTextNode(parent)) michael@0: { michael@0: // if we are in a text node, then that is the node of interest michael@0: nodeToExamine = parent; michael@0: } michael@0: else if (nsEditor::NodeIsType(parent, nsEditProperty::html) && michael@0: offset == rootOffset) michael@0: { michael@0: // if we have selected the body, let's look at the first editable node michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextNode(parent, offset, true, address_of(nodeToExamine)); michael@0: } michael@0: else michael@0: { michael@0: nsCOMArray arrayOfRanges; michael@0: res = GetPromotedRanges(selection, arrayOfRanges, EditAction::align); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // use these ranges to construct a list of nodes to act on. michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, michael@0: EditAction::align, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nodeToExamine = arrayOfNodes.SafeObjectAt(0); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); michael@0: michael@0: NS_NAMED_LITERAL_STRING(typeAttrName, "align"); michael@0: nsIAtom *dummyProperty = nullptr; michael@0: nsCOMPtr blockParent; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsBlockNode(nodeToExamine)) michael@0: blockParent = nodeToExamine; michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsCSSEnabled()) michael@0: { michael@0: nsCOMPtr blockParentContent = do_QueryInterface(blockParent); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (blockParentContent && michael@0: mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParentContent, dummyProperty, &typeAttrName)) michael@0: { michael@0: // we are in CSS mode and we know how to align this element with CSS michael@0: nsAutoString value; michael@0: // let's get the value(s) of text-align or margin-left/margin-right michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet( michael@0: blockParentContent, dummyProperty, &typeAttrName, value, michael@0: nsHTMLCSSUtils::eComputed); michael@0: if (value.EqualsLiteral("center") || michael@0: value.EqualsLiteral("-moz-center") || michael@0: value.EqualsLiteral("auto auto")) michael@0: { michael@0: *aAlign = nsIHTMLEditor::eCenter; michael@0: return NS_OK; michael@0: } michael@0: if (value.EqualsLiteral("right") || michael@0: value.EqualsLiteral("-moz-right") || michael@0: value.EqualsLiteral("auto 0px")) michael@0: { michael@0: *aAlign = nsIHTMLEditor::eRight; michael@0: return NS_OK; michael@0: } michael@0: if (value.EqualsLiteral("justify")) michael@0: { michael@0: *aAlign = nsIHTMLEditor::eJustify; michael@0: return NS_OK; michael@0: } michael@0: *aAlign = nsIHTMLEditor::eLeft; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // check up the ladder for divs with alignment michael@0: nsCOMPtr temp = nodeToExamine; michael@0: bool isFirstNodeToExamine = true; michael@0: while (nodeToExamine) michael@0: { michael@0: if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine)) michael@0: { michael@0: // the node to examine is a table and this is not the first node michael@0: // we examine; let's break here to materialize the 'inline-block' michael@0: // behaviour of html tables regarding to text alignment michael@0: return NS_OK; michael@0: } michael@0: if (nsHTMLEditUtils::SupportsAlignAttr(nodeToExamine)) michael@0: { michael@0: // check for alignment michael@0: nsCOMPtr elem = do_QueryInterface(nodeToExamine); michael@0: if (elem) michael@0: { michael@0: nsAutoString typeAttrVal; michael@0: res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal); michael@0: ToLowerCase(typeAttrVal); michael@0: if (NS_SUCCEEDED(res) && typeAttrVal.Length()) michael@0: { michael@0: if (typeAttrVal.EqualsLiteral("center")) michael@0: *aAlign = nsIHTMLEditor::eCenter; michael@0: else if (typeAttrVal.EqualsLiteral("right")) michael@0: *aAlign = nsIHTMLEditor::eRight; michael@0: else if (typeAttrVal.EqualsLiteral("justify")) michael@0: *aAlign = nsIHTMLEditor::eJustify; michael@0: else michael@0: *aAlign = nsIHTMLEditor::eLeft; michael@0: return res; michael@0: } michael@0: } michael@0: } michael@0: isFirstNodeToExamine = false; michael@0: res = nodeToExamine->GetParentNode(getter_AddRefs(temp)); michael@0: if (NS_FAILED(res)) temp = nullptr; michael@0: nodeToExamine = temp; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) { michael@0: nsAutoString direction; michael@0: aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction); michael@0: return direction.EqualsLiteral("rtl") ? michael@0: nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetIndentState(bool *aCanIndent, bool *aCanOutdent) michael@0: { michael@0: NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE); michael@0: *aCanIndent = true; michael@0: *aCanOutdent = false; michael@0: michael@0: // get selection michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE); michael@0: michael@0: // contruct a list of nodes to act on. michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesFromSelection(selection, EditAction::indent, michael@0: arrayOfNodes, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // examine nodes in selection for blockquotes or list elements; michael@0: // these we can outdent. Note that we return true for canOutdent michael@0: // if *any* of the selection is outdentable, rather than all of it. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: bool useCSS = mHTMLEditor->IsCSSEnabled(); michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: michael@0: if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode)) michael@0: { michael@0: *aCanOutdent = true; michael@0: break; michael@0: } michael@0: else if (useCSS) { michael@0: // we are in CSS mode, indentation is done using the margin-left (or margin-right) property michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); michael@0: nsAutoString value; michael@0: // retrieve its specified value michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); michael@0: float f; michael@0: nsCOMPtr unit; michael@0: // get its number part and its unit michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); michael@0: // if the number part is strictly positive, outdent is possible michael@0: if (0 < f) { michael@0: *aCanOutdent = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!*aCanOutdent) michael@0: { michael@0: // if we haven't found something to outdent yet, also check the parents michael@0: // of selection endpoints. We might have a blockquote or list item michael@0: // in the parent hierarchy. michael@0: michael@0: // gather up info we need for test michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selection; michael@0: int32_t selOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // test start parent hierarchy michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: while (parent && (parent!=root)) michael@0: { michael@0: if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) michael@0: { michael@0: *aCanOutdent = true; michael@0: break; michael@0: } michael@0: tmp=parent; michael@0: tmp->GetParentNode(getter_AddRefs(parent)); michael@0: } michael@0: michael@0: // test end parent hierarchy michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: while (parent && (parent!=root)) michael@0: { michael@0: if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent)) michael@0: { michael@0: *aCanOutdent = true; michael@0: break; michael@0: } michael@0: tmp=parent; michael@0: tmp->GetParentNode(getter_AddRefs(parent)); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetParagraphState(bool *aMixed, nsAString &outFormat) michael@0: { michael@0: // This routine is *heavily* tied to our ui choices in the paragraph michael@0: // style popup. I can't see a way around that. michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); michael@0: *aMixed = true; michael@0: outFormat.Truncate(0); michael@0: michael@0: bool bMixed = false; michael@0: // using "x" as an uninitialized value, since "" is meaningful michael@0: nsAutoString formatStr(NS_LITERAL_STRING("x")); michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: nsresult res = GetParagraphFormatNodes(arrayOfNodes, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // post process list. We need to replace any block nodes that are not format michael@0: // nodes with their content. This is so we only have to look "up" the hierarchy michael@0: // to find format nodes, instead of both up and down. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: nsAutoString format; michael@0: // if it is a known format node we have it easy michael@0: if (IsBlockNode(curNode) && !nsHTMLEditUtils::IsFormatNode(curNode)) michael@0: { michael@0: // arrayOfNodes.RemoveObject(curNode); michael@0: res = AppendInnerFormatNodes(arrayOfNodes, curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // we might have an empty node list. if so, find selection parent michael@0: // and put that on the list michael@0: listCount = arrayOfNodes.Count(); michael@0: if (!listCount) michael@0: { michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER); michael@0: arrayOfNodes.AppendObject(selNode); michael@0: listCount = 1; michael@0: } michael@0: michael@0: // remember root node michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr rootElem = do_QueryInterface(mHTMLEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER); michael@0: michael@0: // loop through the nodes in selection and examine their paragraph format michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: nsAutoString format; michael@0: // if it is a known format node we have it easy michael@0: if (nsHTMLEditUtils::IsFormatNode(curNode)) michael@0: GetFormatString(curNode, format); michael@0: else if (IsBlockNode(curNode)) michael@0: { michael@0: // this is a div or some other non-format block. michael@0: // we should ignore it. Its children were appended to this list michael@0: // by AppendInnerFormatNodes() call above. We will get needed michael@0: // info when we examine them instead. michael@0: continue; michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr node, tmp = curNode; michael@0: tmp->GetParentNode(getter_AddRefs(node)); michael@0: while (node) michael@0: { michael@0: if (node == rootElem) michael@0: { michael@0: format.Truncate(0); michael@0: break; michael@0: } michael@0: else if (nsHTMLEditUtils::IsFormatNode(node)) michael@0: { michael@0: GetFormatString(node, format); michael@0: break; michael@0: } michael@0: // else keep looking up michael@0: tmp = node; michael@0: tmp->GetParentNode(getter_AddRefs(node)); michael@0: } michael@0: } michael@0: michael@0: // if this is the first node, we've found, remember it as the format michael@0: if (formatStr.EqualsLiteral("x")) michael@0: formatStr = format; michael@0: // else make sure it matches previously found format michael@0: else if (format != formatStr) michael@0: { michael@0: bMixed = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: *aMixed = bMixed; michael@0: outFormat = formatStr; michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray& aArray, michael@0: nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: return AppendInnerFormatNodes(aArray, node); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray& aArray, michael@0: nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: // we only need to place any one inline inside this node onto michael@0: // the list. They are all the same for purposes of determining michael@0: // paragraph style. We use foundInline to track this as we are michael@0: // going through the children in the loop below. michael@0: bool foundInline = false; michael@0: for (nsIContent* child = aNode->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: bool isBlock = IsBlockNode(child->AsDOMNode()); michael@0: bool isFormat = nsHTMLEditUtils::IsFormatNode(child); michael@0: if (isBlock && !isFormat) { michael@0: // if it's a div, etc, recurse michael@0: AppendInnerFormatNodes(aArray, child); michael@0: } else if (isFormat) { michael@0: aArray.AppendObject(child->AsDOMNode()); michael@0: } else if (!foundInline) { michael@0: // if this is the first inline we've found, use it michael@0: foundInline = true; michael@0: aArray.AppendObject(child->AsDOMNode()); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (nsHTMLEditUtils::IsFormatNode(aNode)) michael@0: { michael@0: nsCOMPtr atom = nsEditor::GetTag(aNode); michael@0: atom->ToString(outFormat); michael@0: } michael@0: else michael@0: outFormat.Truncate(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /******************************************************** michael@0: * Protected rules methods michael@0: ********************************************************/ michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillInsert(nsISelection *aSelection, bool *aCancel) michael@0: { michael@0: nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Adjust selection to prevent insertion after a moz-BR. michael@0: // this next only works for collapsed selections right now, michael@0: // because selection is a pain to work with when not collapsed. michael@0: // (no good way to extend start or end of selection), so we ignore michael@0: // those types of selections. michael@0: if (!aSelection->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if we are after a mozBR in the same block, then move selection michael@0: // to be before it michael@0: nsCOMPtr selNode, priorNode; michael@0: int32_t selOffset; michael@0: // get the (collapsed) selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), michael@0: &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // get prior node michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, michael@0: address_of(priorNode)); michael@0: if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode)) michael@0: { michael@0: nsCOMPtr block1, block2; michael@0: if (IsBlockNode(selNode)) { michael@0: block1 = selNode; michael@0: } michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: block1 = mHTMLEditor->GetBlockNodeParent(selNode); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: block2 = mHTMLEditor->GetBlockNodeParent(priorNode); michael@0: michael@0: if (block1 == block2) michael@0: { michael@0: // if we are here then the selection is right after a mozBR michael@0: // that is in the same block as the selection. We need to move michael@0: // the selection start to be before the mozBR. michael@0: selNode = nsEditor::GetNodeLocation(priorNode, &selOffset); michael@0: res = aSelection->Collapse(selNode,selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: if (mDidDeleteSelection && michael@0: (mTheAction == EditAction::insertText || michael@0: mTheAction == EditAction::insertIMEText || michael@0: mTheAction == EditAction::deleteSelection)) { michael@0: res = ReapplyCachedStyles(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // For most actions we want to clear the cached styles, but there are michael@0: // exceptions michael@0: if (!IsStyleCachePreservingAction(mTheAction)) { michael@0: ClearCachedStyles(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillInsertText(EditAction aAction, michael@0: Selection* aSelection, michael@0: bool *aCancel, michael@0: bool *aHandled, michael@0: const nsAString *inString, michael@0: nsAString *outString, michael@0: int32_t aMaxLength) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: if (inString->IsEmpty() && aAction != EditAction::insertIMEText) { michael@0: // HACK: this is a fix for bug 19395 michael@0: // I can't outlaw all empty insertions michael@0: // because IME transaction depend on them michael@0: // There is more work to do to make the michael@0: // world safe for IME. michael@0: *aCancel = true; michael@0: *aHandled = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: nsresult res; michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: michael@0: // If the selection isn't collapsed, delete it. Don't delete existing inline michael@0: // tags, because we're hopefully going to insert text (bug 787432). michael@0: if (!aSelection->Collapsed()) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: michael@0: // we need to get the doc michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr doc = mHTMLEditor->GetDOMDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // for every property that is set, insert a new inline style node michael@0: res = CreateStyleForInsertText(aSelection, doc); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // get the (collapsed) selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // dont put text in places that can't have it michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsTextNode(selNode) && michael@0: (!mHTMLEditor || michael@0: !mHTMLEditor->CanContainTag(selNode, nsGkAtoms::textTagName))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (aAction == EditAction::insertIMEText) { michael@0: // Right now the nsWSRunObject code bails on empty strings, but IME needs michael@0: // the InsertTextImpl() call to still happen since empty strings are meaningful there. michael@0: if (inString->IsEmpty()) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc); michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); michael@0: res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else // aAction == kInsertText michael@0: { michael@0: // find where we are michael@0: nsCOMPtr curNode = selNode; michael@0: int32_t curOffset = selOffset; michael@0: michael@0: // is our text going to be PREformatted? michael@0: // We remember this so that we know how to handle tabs. michael@0: bool isPRE; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsPreformatted(selNode, &isPRE); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // turn off the edit listener: we know how to michael@0: // build the "doc changed range" ourselves, and it's michael@0: // must faster to do it once here than to track all michael@0: // the changes one at a time. michael@0: nsAutoLockListener lockit(&mListenerEnabled); michael@0: michael@0: // don't spaz my selection in subtransactions michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString tString(*inString); michael@0: const char16_t *unicodeBuf = tString.get(); michael@0: nsCOMPtr unused; michael@0: int32_t pos = 0; michael@0: NS_NAMED_LITERAL_STRING(newlineStr, LFSTR); michael@0: michael@0: // for efficiency, break out the pre case separately. This is because michael@0: // its a lot cheaper to search the input string for only newlines than michael@0: // it is to search for both tabs and newlines. michael@0: if (isPRE || IsPlaintextEditor()) michael@0: { michael@0: while (unicodeBuf && (pos != -1) && (pos < (int32_t)(*inString).Length())) michael@0: { michael@0: int32_t oldPos = pos; michael@0: int32_t subStrLen; michael@0: pos = tString.FindChar(nsCRT::LF, oldPos); michael@0: michael@0: if (pos != -1) michael@0: { michael@0: subStrLen = pos - oldPos; michael@0: // if first char is newline, then use just it michael@0: if (subStrLen == 0) michael@0: subStrLen = 1; michael@0: } michael@0: else michael@0: { michael@0: subStrLen = tString.Length() - oldPos; michael@0: pos = tString.Length(); michael@0: } michael@0: michael@0: nsDependentSubstring subStr(tString, oldPos, subStrLen); michael@0: michael@0: // is it a return? michael@0: if (subStr.Equals(newlineStr)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); michael@0: pos++; michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: NS_NAMED_LITERAL_STRING(tabStr, "\t"); michael@0: NS_NAMED_LITERAL_STRING(spacesStr, " "); michael@0: char specialChars[] = {TAB, nsCRT::LF, 0}; michael@0: while (unicodeBuf && (pos != -1) && (pos < (int32_t)inString->Length())) michael@0: { michael@0: int32_t oldPos = pos; michael@0: int32_t subStrLen; michael@0: pos = tString.FindCharInSet(specialChars, oldPos); michael@0: michael@0: if (pos != -1) michael@0: { michael@0: subStrLen = pos - oldPos; michael@0: // if first char is newline, then use just it michael@0: if (subStrLen == 0) michael@0: subStrLen = 1; michael@0: } michael@0: else michael@0: { michael@0: subStrLen = tString.Length() - oldPos; michael@0: pos = tString.Length(); michael@0: } michael@0: michael@0: nsDependentSubstring subStr(tString, oldPos, subStrLen); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset); michael@0: michael@0: // is it a tab? michael@0: if (subStr.Equals(tabStr)) michael@0: { michael@0: res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: pos++; michael@0: } michael@0: // is it a return? michael@0: else if (subStr.Equals(newlineStr)) michael@0: { michael@0: res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: pos++; michael@0: } michael@0: else michael@0: { michael@0: res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: nsCOMPtr selection(aSelection); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: selPriv->SetInterlinePosition(false); michael@0: if (curNode) aSelection->Collapse(curNode, curOffset); michael@0: // manually update the doc changed range so that AfterEdit will clean up michael@0: // the correct portion of the document. michael@0: if (!mDocChangeRange) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(selNode); michael@0: NS_ENSURE_STATE(node); michael@0: mDocChangeRange = new nsRange(node); michael@0: } michael@0: res = mDocChangeRange->SetStart(selNode, selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (curNode) michael@0: res = mDocChangeRange->SetEnd(curNode, curOffset); michael@0: else michael@0: res = mDocChangeRange->SetEnd(selNode, selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillLoadHTML(nsISelection *aSelection, bool *aCancel) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aCancel = false; michael@0: michael@0: // Delete mBogusNode if it exists. If we really need one, michael@0: // it will be added during post-processing in AfterEditInner(). michael@0: michael@0: if (mBogusNode) michael@0: { michael@0: mEditor->DeleteNode(mBogusNode); michael@0: mBogusNode = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillInsertBreak(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: // initialize out params michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // if the selection isn't collapsed, delete it. michael@0: nsresult res = NS_OK; michael@0: if (!aSelection->Collapsed()) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: michael@0: // split any mailcites in the way. michael@0: // should we abort this if we encounter table cell boundaries? michael@0: if (IsMailEditor()) { michael@0: res = SplitMailCites(aSelection, IsPlaintextEditor(), aHandled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (*aHandled) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // smart splitting rules michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), michael@0: &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: // do nothing if the node is read-only michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsModifiableNode(node)) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // identify the block michael@0: nsCOMPtr blockParent; michael@0: if (IsBlockNode(node)) { michael@0: blockParent = node; michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: blockParent = mHTMLEditor->GetBlockNodeParent(node); michael@0: } michael@0: NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE); michael@0: michael@0: // if the active editing host is an inline element, or if the active editing michael@0: // host is the block parent itself, just append a br. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr hostContent = mHTMLEditor->GetActiveEditingHost(); michael@0: nsCOMPtr hostNode = do_QueryInterface(hostContent); michael@0: if (!nsEditorUtils::IsDescendantOf(blockParent, hostNode)) { michael@0: res = StandardBreakImpl(node, offset, aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *aHandled = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if block is empty, populate with br. (for example, imagine a div that michael@0: // contains the word "text". the user selects "text" and types return. michael@0: // "text" is deleted leaving an empty block. we want to put in one br to michael@0: // make block have a line. then code further below will put in a second br.) michael@0: bool isEmpty; michael@0: IsEmptyBlock(blockParent, &isEmpty); michael@0: if (isEmpty) { michael@0: uint32_t blockLen; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: nsCOMPtr listItem = IsInListItem(blockParent); michael@0: if (listItem && listItem != hostNode) { michael@0: ReturnInListItem(aSelection, listItem, node, offset); michael@0: *aHandled = true; michael@0: return NS_OK; michael@0: } else if (nsHTMLEditUtils::IsHeader(blockParent)) { michael@0: // headers: close (or split) header michael@0: ReturnInHeader(aSelection, blockParent, node, offset); michael@0: *aHandled = true; michael@0: return NS_OK; michael@0: } else if (nsHTMLEditUtils::IsParagraph(blockParent)) { michael@0: // paragraphs: special rules to look for
s michael@0: res = ReturnInParagraph(aSelection, blockParent, node, offset, michael@0: aCancel, aHandled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // fall through, we may not have handled it in ReturnInParagraph() michael@0: } michael@0: michael@0: // if not already handled then do the standard thing michael@0: if (!(*aHandled)) { michael@0: *aHandled = true; michael@0: return StandardBreakImpl(node, offset, aSelection); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::StandardBreakImpl(nsIDOMNode* aNode, int32_t aOffset, michael@0: nsISelection* aSelection) michael@0: { michael@0: nsCOMPtr brNode; michael@0: bool bAfterBlock = false; michael@0: bool bBeforeBlock = false; michael@0: nsresult res = NS_OK; michael@0: nsCOMPtr node(aNode); michael@0: nsCOMPtr selPriv(do_QueryInterface(aSelection)); michael@0: michael@0: if (IsPlaintextEditor()) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode)); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, node, aOffset); michael@0: nsCOMPtr visNode, linkNode; michael@0: int32_t visOffset = 0, newOffset; michael@0: WSType wsType; michael@0: wsObj.PriorVisibleNode(node, aOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType & WSType::block) { michael@0: bAfterBlock = true; michael@0: } michael@0: wsObj.NextVisibleNode(node, aOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType & WSType::block) { michael@0: bBeforeBlock = true; michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsInLink(node, address_of(linkNode))) { michael@0: // split the link michael@0: nsCOMPtr linkParent; michael@0: res = linkNode->GetParentNode(getter_AddRefs(linkParent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset, michael@0: &newOffset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // reset {node,aOffset} to the point where link was split michael@0: node = linkParent; michael@0: aOffset = newOffset; michael@0: } michael@0: res = wsObj.InsertBreak(address_of(node), &aOffset, michael@0: address_of(brNode), nsIEditor::eNone); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = nsEditor::GetNodeLocation(brNode, &aOffset); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: if (bAfterBlock && bBeforeBlock) { michael@0: // we just placed a br between block boundaries. This is the one case michael@0: // where we want the selection to be before the br we just placed, as the michael@0: // br will be on a new line, rather than at end of prior line. michael@0: selPriv->SetInterlinePosition(true); michael@0: res = aSelection->Collapse(node, aOffset); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1); michael@0: nsCOMPtr secondBR; michael@0: int32_t visOffset = 0; michael@0: WSType wsType; michael@0: wsObj.NextVisibleNode(node, aOffset+1, address_of(secondBR), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::br) { michael@0: // the next thing after the break we inserted is another break. Move michael@0: // the 2nd break to be the first breaks sibling. This will prevent them michael@0: // from being in different inline nodes, which would break michael@0: // SetInterlinePosition(). It will also assure that if the user clicks michael@0: // away and then clicks back on their new blank line, they will still michael@0: // get the style from the line above. michael@0: int32_t brOffset; michael@0: nsCOMPtr brParent = nsEditor::GetNodeLocation(secondBR, &brOffset); michael@0: if (brParent != node || brOffset != aOffset + 1) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(secondBR, node, aOffset+1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: // SetInterlinePosition(true) means we want the caret to stick to the michael@0: // content on the "right". We want the caret to stick to whatever is past michael@0: // the break. This is because the break is on the same line we were on, michael@0: // but the next content will be on the following line. michael@0: michael@0: // An exception to this is if the break has a next sibling that is a block michael@0: // node. Then we stick to the left to avoid an uber caret. michael@0: nsCOMPtr siblingNode; michael@0: brNode->GetNextSibling(getter_AddRefs(siblingNode)); michael@0: if (siblingNode && IsBlockNode(siblingNode)) { michael@0: selPriv->SetInterlinePosition(false); michael@0: } else { michael@0: selPriv->SetInterlinePosition(true); michael@0: } michael@0: res = aSelection->Collapse(node, aOffset+1); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool *aHandled) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selPriv(do_QueryInterface(aSelection)); michael@0: nsCOMPtr citeNode, selNode, leftCite, rightCite; michael@0: int32_t selOffset, newOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (citeNode) michael@0: { michael@0: // If our selection is just before a break, nudge it to be michael@0: // just after it. This does two things for us. It saves us the trouble of having to add michael@0: // a break here ourselves to preserve the "blockness" of the inline span mailquote michael@0: // (in the inline case), and : michael@0: // it means the break won't end up making an empty line that happens to be inside a michael@0: // mailquote (in either inline or block case). michael@0: // The latter can confuse a user if they click there and start typing, michael@0: // because being in the mailquote may affect wrapping behavior, or font color, etc. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset=0; michael@0: WSType wsType; michael@0: wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::br) { michael@0: // ok, we are just before a break. is it inside the mailquote? michael@0: int32_t unused; michael@0: if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused)) michael@0: { michael@0: // it is. so lets reset our selection to be just after it. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: selNode = mHTMLEditor->GetNodeLocation(visNode, &selOffset); michael@0: ++selOffset; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset, michael@0: true, address_of(leftCite), address_of(rightCite)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = citeNode->GetParentNode(getter_AddRefs(selNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // want selection before the break, and on same line michael@0: selPriv->SetInterlinePosition(true); michael@0: res = aSelection->Collapse(selNode, newOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // if citeNode wasn't a block, we might also want another break before it. michael@0: // We need to examine the content both before the br we just added and also michael@0: // just after it. If we don't have another br or block boundary adjacent, michael@0: // then we will need a 2nd br added to achieve blank line that user expects. michael@0: if (IsInlineNode(citeNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset=0; michael@0: WSType wsType; michael@0: wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::normalWS || wsType == WSType::text || michael@0: wsType == WSType::special) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1); michael@0: wsObjAfterBR.NextVisibleNode(selNode, newOffset+1, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::normalWS || wsType == WSType::text || michael@0: wsType == WSType::special) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: // delete any empty cites michael@0: bool bEmptyCite = false; michael@0: if (leftCite) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false); michael@0: if (NS_SUCCEEDED(res) && bEmptyCite) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(leftCite); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: if (rightCite) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false); michael@0: if (NS_SUCCEEDED(res) && bEmptyCite) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(rightCite); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: *aHandled = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillDeleteSelection(Selection* aSelection, michael@0: nsIEditor::EDirection aAction, michael@0: nsIEditor::EStripWrappers aStripWrappers, michael@0: bool* aCancel, michael@0: bool* aHandled) michael@0: { michael@0: MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip || michael@0: aStripWrappers == nsIEditor::eNoStrip); michael@0: michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // remember that we did a selection deletion. Used by CreateStyleForInsertText() michael@0: mDidDeleteSelection = true; michael@0: michael@0: // if there is only bogus content, cancel the operation michael@0: if (mBogusNode) michael@0: { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool bCollapsed = aSelection->Collapsed(), join = false; michael@0: michael@0: // origCollapsed is used later to determine whether we should join michael@0: // blocks. We don't really care about bCollapsed because it will be michael@0: // modified by ExtendSelectionForDelete later. JoinBlocks should michael@0: // happen if the original selection is collapsed and the cursor is michael@0: // at the end of a block element, in which case ExtendSelectionForDelete michael@0: // would always make the selection not collapsed. michael@0: bool origCollapsed = bCollapsed; michael@0: nsCOMPtr startNode, selNode; michael@0: int32_t startOffset, selOffset; michael@0: michael@0: // first check for table selection mode. If so, michael@0: // hand off to table editor. michael@0: nsCOMPtr cell; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); michael@0: if (NS_SUCCEEDED(res) && cell) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteTableCellContents(); michael@0: *aHandled = true; michael@0: return res; michael@0: } michael@0: cell = nullptr; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: michael@0: if (bCollapsed) michael@0: { michael@0: // if we are inside an empty block, delete it. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr hostContent = mHTMLEditor->GetActiveEditingHost(); michael@0: nsCOMPtr hostNode = do_QueryInterface(hostContent); michael@0: NS_ENSURE_TRUE(hostNode, NS_ERROR_FAILURE); michael@0: res = CheckForEmptyBlock(startNode, hostNode, aSelection, aHandled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (*aHandled) return NS_OK; michael@0: michael@0: // Test for distance between caret and text that will be deleted michael@0: res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aAction, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (*aCancel) return NS_OK; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // We should delete nothing. michael@0: if (aAction == nsIEditor::eNone) michael@0: return NS_OK; michael@0: michael@0: // ExtendSelectionForDelete() may have changed the selection, update it michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: michael@0: bCollapsed = aSelection->Collapsed(); michael@0: } michael@0: michael@0: if (bCollapsed) michael@0: { michael@0: // what's in the direction we are deleting? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset; michael@0: WSType wsType; michael@0: michael@0: // find next visible node michael@0: if (aAction == nsIEditor::eNext) michael@0: wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: else michael@0: wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: michael@0: if (!visNode) // can't find anything to delete! michael@0: { michael@0: *aCancel = true; michael@0: return res; michael@0: } michael@0: michael@0: if (wsType == WSType::normalWS) { michael@0: // we found some visible ws to delete. Let ws code handle it. michael@0: if (aAction == nsIEditor::eNext) michael@0: res = wsObj.DeleteWSForward(); michael@0: else michael@0: res = wsObj.DeleteWSBackward(); michael@0: *aHandled = true; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertBRIfNeeded(aSelection); michael@0: return res; michael@0: } else if (wsType == WSType::text) { michael@0: // found normal text to delete. michael@0: int32_t so = visOffset; michael@0: int32_t eo = visOffset+1; michael@0: if (aAction == nsIEditor::ePrevious) michael@0: { michael@0: if (so == 0) return NS_ERROR_UNEXPECTED; michael@0: so--; michael@0: eo--; michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr range; michael@0: res = aSelection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: #ifdef DEBUG michael@0: nsIDOMNode *container; michael@0: michael@0: res = range->GetStartContainer(&container); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ASSERTION(container == visNode, "selection start not in visNode"); michael@0: michael@0: res = range->GetEndContainer(&container); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ASSERTION(container == visNode, "selection end not in visNode"); michael@0: #endif michael@0: michael@0: res = range->GetStartOffset(&so); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&eo); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr nodeAsText(do_QueryInterface(visNode)); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so)); michael@0: *aHandled = true; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertBRIfNeeded(aSelection); michael@0: return res; michael@0: } else if (wsType == WSType::special || wsType == WSType::br || michael@0: nsHTMLEditUtils::IsHR(visNode)) { michael@0: // short circuit for invisible breaks. delete them and recurse. michael@0: if (nsTextEditUtils::IsBreak(visNode) && michael@0: (!mHTMLEditor || !mHTMLEditor->IsVisBreak(visNode))) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(visNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return WillDeleteSelection(aSelection, aAction, aStripWrappers, michael@0: aCancel, aHandled); michael@0: } michael@0: michael@0: // special handling for backspace when positioned after
michael@0: if (aAction == nsIEditor::ePrevious && nsHTMLEditUtils::IsHR(visNode)) michael@0: { michael@0: /* michael@0: Only if the caret is positioned at the end-of-hr-line position, michael@0: we want to delete the
. michael@0: michael@0: In other words, we only want to delete, if michael@0: our selection position (indicated by startNode and startOffset) michael@0: is the position directly after the
, michael@0: on the same line as the
. michael@0: michael@0: To detect this case we check: michael@0: startNode == parentOfVisNode michael@0: and michael@0: startOffset -1 == visNodeOffsetToVisNodeParent michael@0: and michael@0: interline position is false (left) michael@0: michael@0: In any other case we set the position to michael@0: startnode -1 and interlineposition to false, michael@0: only moving the caret to the end-of-hr-line position. michael@0: */ michael@0: michael@0: bool moveOnly = true; michael@0: michael@0: selNode = nsEditor::GetNodeLocation(visNode, &selOffset); michael@0: michael@0: bool interLineIsRight; michael@0: res = aSelection->GetInterlinePosition(&interLineIsRight); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (startNode == selNode && michael@0: startOffset -1 == selOffset && michael@0: !interLineIsRight) michael@0: { michael@0: moveOnly = false; michael@0: } michael@0: michael@0: if (moveOnly) michael@0: { michael@0: // Go to the position after the
, but to the end of the
line michael@0: // by setting the interline position to left. michael@0: ++selOffset; michael@0: res = aSelection->Collapse(selNode, selOffset); michael@0: aSelection->SetInterlinePosition(false); michael@0: mDidExplicitlySetInterline = true; michael@0: *aHandled = true; michael@0: michael@0: // There is one exception to the move only case. michael@0: // If the
is followed by a
we want to delete the
. michael@0: michael@0: WSType otherWSType; michael@0: nsCOMPtr otherNode; michael@0: int32_t otherOffset; michael@0: michael@0: wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), michael@0: &otherOffset, &otherWSType); michael@0: michael@0: if (otherWSType == WSType::br) { michael@0: // Delete the
michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(otherNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: // else continue with normal delete code michael@0: } michael@0: michael@0: // found break or image, or hr. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember sibling to visnode, if any michael@0: nsCOMPtr sibling, stepbrother; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling)); michael@0: // delete the node, and join like nodes if appropriate michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(visNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // we did something, so lets say so. michael@0: *aHandled = true; michael@0: // is there a prior node and are they siblings? michael@0: if (sibling) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother)); michael@0: } michael@0: if (startNode == stepbrother) michael@0: { michael@0: // are they both text nodes? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsTextNode(startNode) && michael@0: (!mHTMLEditor || mHTMLEditor->IsTextNode(sibling))) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: // if so, join them! michael@0: res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // fix up selection michael@0: res = aSelection->Collapse(selNode, selOffset); michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertBRIfNeeded(aSelection); michael@0: return res; michael@0: } else if (wsType == WSType::otherBlock) { michael@0: // make sure it's not a table element. If so, cancel the operation michael@0: // (translation: users cannot backspace or delete across table cells) michael@0: if (nsHTMLEditUtils::IsTableElement(visNode)) michael@0: { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // next to a block. See if we are between a block and a br. If so, we really michael@0: // want to delete the br. Else join content at selection to the block. michael@0: michael@0: bool bDeletedBR = false; michael@0: WSType otherWSType; michael@0: nsCOMPtr otherNode; michael@0: int32_t otherOffset; michael@0: michael@0: // find node in other direction michael@0: if (aAction == nsIEditor::eNext) michael@0: wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode), michael@0: &otherOffset, &otherWSType); michael@0: else michael@0: wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode), michael@0: &otherOffset, &otherWSType); michael@0: michael@0: // first find the adjacent node in the block michael@0: nsCOMPtr leafNode, leftNode, rightNode; michael@0: if (aAction == nsIEditor::ePrevious) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: leftNode = leafNode; michael@0: rightNode = startNode; michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: leftNode = startNode; michael@0: rightNode = leafNode; michael@0: } michael@0: michael@0: if (nsTextEditUtils::IsBreak(otherNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(otherNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *aHandled = true; michael@0: bDeletedBR = true; michael@0: } michael@0: michael@0: // don't cross table boundaries michael@0: if (leftNode && rightNode && InDifferentTableElements(leftNode, rightNode)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (bDeletedBR) michael@0: { michael@0: // put selection at edge of block and we are done. michael@0: nsCOMPtr newSelNode; michael@0: int32_t newSelOffset; michael@0: res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: aSelection->Collapse(newSelNode, newSelOffset); michael@0: return res; michael@0: } michael@0: michael@0: // else we are joining content to block michael@0: michael@0: nsCOMPtr selPointNode = startNode; michael@0: int32_t selPointOffset = startOffset; michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); michael@0: res = JoinBlocks(leftNode, rightNode, aCancel); michael@0: *aHandled = true; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: aSelection->Collapse(selPointNode, selPointOffset); michael@0: return res; michael@0: } else if (wsType == WSType::thisBlock) { michael@0: // at edge of our block. Look beside it and see if we can join to an adjacent block michael@0: michael@0: // make sure it's not a table element. If so, cancel the operation michael@0: // (translation: users cannot backspace or delete across table cells) michael@0: if (nsHTMLEditUtils::IsTableElement(visNode)) michael@0: { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // first find the relavent nodes michael@0: nsCOMPtr leftNode, rightNode; michael@0: if (aAction == nsIEditor::ePrevious) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: rightNode = startNode; michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: leftNode = startNode; michael@0: } michael@0: michael@0: // nothing to join michael@0: if (!leftNode || !rightNode) michael@0: { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // don't cross table boundaries -- cancel it michael@0: if (InDifferentTableElements(leftNode, rightNode)) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr selPointNode = startNode; michael@0: int32_t selPointOffset = startOffset; michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset); michael@0: res = JoinBlocks(leftNode, rightNode, aCancel); michael@0: *aHandled = true; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: aSelection->Collapse(selPointNode, selPointOffset); michael@0: return res; michael@0: } michael@0: } michael@0: michael@0: michael@0: // else we have a non collapsed selection michael@0: // first adjust the selection michael@0: res = ExpandSelectionForDeletion(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // remember that we did a ranged delete for the benefit of AfterEditInner(). michael@0: mDidRangedDelete = true; michael@0: michael@0: // refresh start and end points michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: nsCOMPtr endNode; michael@0: int32_t endOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(endNode), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE); michael@0: michael@0: // figure out if the endpoints are in nodes that can be merged michael@0: // adjust surrounding whitespace in preperation to delete selection michael@0: if (!IsPlaintextEditor()) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, michael@0: address_of(startNode), &startOffset, michael@0: address_of(endNode), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: { michael@0: // track location of where we are deleting michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater, michael@0: address_of(startNode), &startOffset); michael@0: nsAutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater, michael@0: address_of(endNode), &endOffset); michael@0: // we are handling all ranged deletions directly now. michael@0: *aHandled = true; michael@0: michael@0: if (endNode == startNode) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // figure out mailcite ancestors michael@0: nsCOMPtr endCiteNode, startCiteNode; michael@0: res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode), michael@0: IsPlaintextEditor()); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode), michael@0: IsPlaintextEditor()); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if we only have a mailcite at one of the two endpoints, set the directionality michael@0: // of the deletion so that the selection will end up outside the mailcite. michael@0: if (startCiteNode && !endCiteNode) michael@0: { michael@0: aAction = nsIEditor::eNext; michael@0: } michael@0: else if (!startCiteNode && endCiteNode) michael@0: { michael@0: aAction = nsIEditor::ePrevious; michael@0: } michael@0: michael@0: // figure out block parents michael@0: nsCOMPtr leftParent; michael@0: nsCOMPtr rightParent; michael@0: if (IsBlockNode(startNode)) michael@0: leftParent = startNode; michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: leftParent = mHTMLEditor->GetBlockNodeParent(startNode); michael@0: } michael@0: michael@0: if (IsBlockNode(endNode)) michael@0: rightParent = endNode; michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: rightParent = mHTMLEditor->GetBlockNodeParent(endNode); michael@0: } michael@0: michael@0: // are endpoint block parents the same? use default deletion michael@0: if (leftParent == rightParent) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); michael@0: } michael@0: else michael@0: { michael@0: // deleting across blocks michael@0: // are the blocks of same type? michael@0: NS_ENSURE_STATE(leftParent && rightParent); michael@0: michael@0: // are the blocks siblings? michael@0: nsCOMPtr leftBlockParent; michael@0: nsCOMPtr rightBlockParent; michael@0: leftParent->GetParentNode(getter_AddRefs(leftBlockParent)); michael@0: rightParent->GetParentNode(getter_AddRefs(rightBlockParent)); michael@0: michael@0: // MOOSE: this could conceivably screw up a table.. fix me. michael@0: if ( (leftBlockParent == rightBlockParent) michael@0: && (!mHTMLEditor || mHTMLEditor->NodesSameType(leftParent, rightParent)) ) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (nsHTMLEditUtils::IsParagraph(leftParent)) michael@0: { michael@0: // first delete the selection michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // then join para's, insert break michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // fix up selection michael@0: res = aSelection->Collapse(selNode,selOffset); michael@0: return res; michael@0: } michael@0: if (nsHTMLEditUtils::IsListItem(leftParent) michael@0: || nsHTMLEditUtils::IsHeader(leftParent)) michael@0: { michael@0: // first delete the selection michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // join blocks michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // fix up selection michael@0: res = aSelection->Collapse(selNode,selOffset); michael@0: return res; michael@0: } michael@0: } michael@0: michael@0: // else blocks not same type, or not siblings. Delete everything except michael@0: // table elements. michael@0: join = true; michael@0: michael@0: uint32_t rangeCount = aSelection->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = aSelection->GetRangeAt(rangeIdx); michael@0: michael@0: // build a list of nodes in the range michael@0: nsCOMArray arrayOfNodes; michael@0: nsTrivialFunctor functor; michael@0: nsDOMSubtreeIterator iter; michael@0: res = iter.Init(range); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = iter.AppendList(functor, arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // now that we have the list, delete non table elements michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t j = 0; j < listCount; j++) { michael@0: nsCOMPtr somenode = do_QueryInterface(arrayOfNodes[0]); michael@0: NS_ENSURE_STATE(somenode); michael@0: DeleteNonTableElements(somenode); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: // If something visible is deleted, no need to join. michael@0: // Visible means all nodes except non-visible textnodes and breaks. michael@0: if (join && origCollapsed) { michael@0: if (!somenode->IsContent()) { michael@0: join = false; michael@0: continue; michael@0: } michael@0: nsCOMPtr content = somenode->AsContent(); michael@0: if (content->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->IsVisTextNode(content, &join, true); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: join = content->IsHTML(nsGkAtoms::br) && michael@0: !mHTMLEditor->IsVisBreak(somenode->AsDOMNode()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // check endopints for possible text deletion. michael@0: // we can assume that if text node is found, we can michael@0: // delete to end or to begining as appropriate, michael@0: // since the case where both sel endpoints in same michael@0: // text node was already handled (we wouldn't be here) michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if ( mHTMLEditor->IsTextNode(startNode) ) michael@0: { michael@0: // delete to last character michael@0: nsCOMPtrnodeAsText; michael@0: uint32_t len; michael@0: nodeAsText = do_QueryInterface(startNode); michael@0: nodeAsText->GetLength(&len); michael@0: if (len > (uint32_t)startOffset) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if ( mHTMLEditor->IsTextNode(endNode) ) michael@0: { michael@0: // delete to first character michael@0: nsCOMPtrnodeAsText; michael@0: nodeAsText = do_QueryInterface(endNode); michael@0: if (endOffset) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: if (join) { michael@0: res = JoinBlocks(leftParent, rightParent, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: //If we're joining blocks: if deleting forward the selection should be michael@0: //collapsed to the end of the selection, if deleting backward the selection michael@0: //should be collapsed to the beginning of the selection. But if we're not michael@0: //joining then the selection should collapse to the beginning of the michael@0: //selection if we'redeleting forward, because the end of the selection will michael@0: //still be in the next block. And same thing for deleting backwards michael@0: //(selection should collapse to the end, because the beginning will still michael@0: //be in the first block). See Bug 507936 michael@0: if (join ? aAction == nsIEditor::eNext : aAction == nsIEditor::ePrevious) michael@0: { michael@0: res = aSelection->Collapse(endNode,endOffset); michael@0: } michael@0: else michael@0: { michael@0: res = aSelection->Collapse(startNode,startOffset); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /***************************************************************************************************** michael@0: * InsertBRIfNeeded: determines if a br is needed for current selection to not be spastic. michael@0: * If so, it inserts one. Callers responsibility to only call with collapsed selection. michael@0: * nsISelection *aSelection the collapsed selection michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::InsertBRIfNeeded(nsISelection *aSelection) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // get selection michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: // inline elements don't need any br michael@0: if (!IsBlockNode(node)) michael@0: return res; michael@0: michael@0: // examine selection michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, node, offset); michael@0: if (((wsObj.mStartReason & WSType::block) || michael@0: (wsObj.mStartReason & WSType::br)) && michael@0: (wsObj.mEndReason & WSType::block)) { michael@0: // if we are tucked between block boundaries then insert a br michael@0: // first check that we are allowed to michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->CanContainTag(node, nsGkAtoms::br)) { michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /***************************************************************************************************** michael@0: * GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were michael@0: * trying to have a caret next to it. michael@0: * nsIDOMNode *aNode the node michael@0: * nsIEditor::EDirection aAction which edge to find: eNext indicates beginning, ePrevious ending michael@0: * nsCOMPtr *outSelNode desired sel node michael@0: * int32_t *outSelOffset desired sel offset michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction, michael@0: nsCOMPtr *outSelNode, int32_t *outSelOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && outSelNode && outSelOffset, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = NS_OK; michael@0: michael@0: // default values michael@0: *outSelNode = aNode; michael@0: *outSelOffset = 0; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsTextNode(aNode) || michael@0: !mHTMLEditor || mHTMLEditor->IsContainer(aNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (aAction == nsIEditor::ePrevious) michael@0: { michael@0: uint32_t len; michael@0: res = mHTMLEditor->GetLengthOfDOMNode(aNode, len); michael@0: *outSelOffset = int32_t(len); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: *outSelNode = nsEditor::GetNodeLocation(aNode, outSelOffset); michael@0: if (!nsTextEditUtils::IsBreak(aNode) || michael@0: !mHTMLEditor || mHTMLEditor->IsVisBreak(aNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (aAction == nsIEditor::ePrevious) michael@0: (*outSelOffset)++; michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /***************************************************************************************************** michael@0: * JoinBlocks: this method is used to join two block elements. The right element is always joined michael@0: * to the left element. If the elements are the same type and not nested within each other, michael@0: * JoinNodesSmart is called (example, joining two list items together into one). If the elements michael@0: * are not the same type, or one is a descendant of the other, we instead destroy the right block michael@0: * placing its children into leftblock. DTD containment rules are followed throughout. michael@0: * nsCOMPtr *aLeftBlock pointer to the left block michael@0: * nsCOMPtr *aRightBlock pointer to the right block; will have contents moved to left block michael@0: * bool *aCanceled return TRUE if we had to cancel operation michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::JoinBlocks(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: bool *aCanceled) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aLeftNode && aRightNode); michael@0: michael@0: nsCOMPtr aLeftBlock, aRightBlock; michael@0: michael@0: if (IsBlockNode(aLeftNode)) { michael@0: aLeftBlock = aLeftNode; michael@0: } else if (aLeftNode) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: aLeftBlock = mHTMLEditor->GetBlockNodeParent(aLeftNode); michael@0: } michael@0: michael@0: if (IsBlockNode(aRightNode)) { michael@0: aRightBlock = aRightNode; michael@0: } else if (aRightNode) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: aRightBlock = mHTMLEditor->GetBlockNodeParent(aRightNode); michael@0: } michael@0: michael@0: // sanity checks michael@0: NS_ENSURE_TRUE(aLeftBlock && aRightBlock, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_STATE(aLeftBlock != aRightBlock); michael@0: michael@0: if (nsHTMLEditUtils::IsTableElement(aLeftBlock) || michael@0: nsHTMLEditUtils::IsTableElement(aRightBlock)) { michael@0: // do not try to merge table elements michael@0: *aCanceled = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // make sure we don't try to move thing's into HR's, which look like blocks but aren't containers michael@0: if (nsHTMLEditUtils::IsHR(aLeftBlock)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr realLeft = mHTMLEditor->GetBlockNodeParent(aLeftBlock); michael@0: aLeftBlock = realLeft; michael@0: } michael@0: if (nsHTMLEditUtils::IsHR(aRightBlock)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr realRight = mHTMLEditor->GetBlockNodeParent(aRightBlock); michael@0: aRightBlock = realRight; michael@0: } michael@0: michael@0: // bail if both blocks the same michael@0: if (aLeftBlock == aRightBlock) { michael@0: *aCanceled = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Joining a list item to its parent is a NOP. michael@0: if (nsHTMLEditUtils::IsList(aLeftBlock) && michael@0: nsHTMLEditUtils::IsListItem(aRightBlock)) { michael@0: nsCOMPtr rightParent; michael@0: aRightBlock->GetParentNode(getter_AddRefs(rightParent)); michael@0: if (rightParent == aLeftBlock) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // special rule here: if we are trying to join list items, and they are in different lists, michael@0: // join the lists instead. michael@0: bool bMergeLists = false; michael@0: nsIAtom* existingList = nsGkAtoms::_empty; michael@0: int32_t theOffset; michael@0: nsCOMPtr leftList, rightList; michael@0: if (nsHTMLEditUtils::IsListItem(aLeftBlock) && michael@0: nsHTMLEditUtils::IsListItem(aRightBlock)) { michael@0: aLeftBlock->GetParentNode(getter_AddRefs(leftList)); michael@0: aRightBlock->GetParentNode(getter_AddRefs(rightList)); michael@0: if (leftList && rightList && (leftList!=rightList)) michael@0: { michael@0: // there are some special complications if the lists are descendants of michael@0: // the other lists' items. Note that it is ok for them to be descendants michael@0: // of the other lists themselves, which is the usual case for sublists michael@0: // in our impllementation. michael@0: if (!nsEditorUtils::IsDescendantOf(leftList, aRightBlock, &theOffset) && michael@0: !nsEditorUtils::IsDescendantOf(rightList, aLeftBlock, &theOffset)) michael@0: { michael@0: aLeftBlock = leftList; michael@0: aRightBlock = rightList; michael@0: bMergeLists = true; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: existingList = mHTMLEditor->GetTag(leftList); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: michael@0: nsresult res = NS_OK; michael@0: int32_t rightOffset = 0; michael@0: int32_t leftOffset = -1; michael@0: michael@0: // theOffset below is where you find yourself in aRightBlock when you traverse upwards michael@0: // from aLeftBlock michael@0: if (nsEditorUtils::IsDescendantOf(aLeftBlock, aRightBlock, &rightOffset)) { michael@0: // tricky case. left block is inside right block. michael@0: // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining. michael@0: rightOffset++; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, michael@0: address_of(aLeftBlock), michael@0: nsWSRunObject::kBlockEnd); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, michael@0: address_of(aRightBlock), michael@0: nsWSRunObject::kAfterBlock, michael@0: &rightOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Do br adjustment. michael@0: nsCOMPtr brNode; michael@0: res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bMergeLists) michael@0: { michael@0: // idea here is to take all children in rightList that are past michael@0: // theOffset, and pull them into leftlist. michael@0: nsCOMPtr childToMove; michael@0: nsCOMPtr parent(do_QueryInterface(rightList)); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsIContent *child = parent->GetChildAt(theOffset); michael@0: while (child) michael@0: { michael@0: childToMove = do_QueryInterface(child); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(childToMove, leftList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: child = parent->GetChildAt(rightOffset); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (brNode) mHTMLEditor->DeleteNode(brNode); michael@0: // theOffset below is where you find yourself in aLeftBlock when you traverse upwards michael@0: // from aRightBlock michael@0: } else if (nsEditorUtils::IsDescendantOf(aRightBlock, aLeftBlock, &leftOffset)) { michael@0: // tricky case. right block is inside left block. michael@0: // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, michael@0: address_of(aRightBlock), michael@0: nsWSRunObject::kBlockStart); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor, michael@0: address_of(aLeftBlock), michael@0: nsWSRunObject::kBeforeBlock, michael@0: &leftOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Do br adjustment. michael@0: nsCOMPtr brNode; michael@0: res = CheckForInvisibleBR(aLeftBlock, kBeforeBlock, address_of(brNode), michael@0: leftOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bMergeLists) michael@0: { michael@0: res = MoveContents(rightList, leftList, &leftOffset); michael@0: } michael@0: else michael@0: { michael@0: // Left block is a parent of right block, and the parent of the previous michael@0: // visible content. Right block is a child and contains the contents we michael@0: // want to move. michael@0: michael@0: int32_t previousContentOffset; michael@0: nsCOMPtr previousContentParent; michael@0: michael@0: if (aLeftNode == aLeftBlock) { michael@0: // We are working with valid HTML, aLeftNode is a block node, and is michael@0: // therefore allowed to contain aRightBlock. This is the simple case, michael@0: // we will simply move the content in aRightBlock out of its block. michael@0: previousContentParent = aLeftBlock; michael@0: previousContentOffset = leftOffset; michael@0: } else { michael@0: // We try to work as well as possible with HTML that's already invalid. michael@0: // Although "right block" is a block, and a block must not be contained michael@0: // in inline elements, reality is that broken documents do exist. The michael@0: // DIRECT parent of "left NODE" might be an inline element. Previous michael@0: // versions of this code skipped inline parents until the first block michael@0: // parent was found (and used "left block" as the destination). michael@0: // However, in some situations this strategy moves the content to an michael@0: // unexpected position. (see bug 200416) The new idea is to make the michael@0: // moving content a sibling, next to the previous visible content. michael@0: michael@0: previousContentParent = michael@0: nsEditor::GetNodeLocation(aLeftNode, &previousContentOffset); michael@0: michael@0: // We want to move our content just after the previous visible node. michael@0: previousContentOffset++; michael@0: } michael@0: michael@0: // Because we don't want the moving content to receive the style of the michael@0: // previous content, we split the previous content's style. michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr editorRoot = mHTMLEditor->GetEditorRoot(); michael@0: if (!editorRoot || aLeftNode != editorRoot->AsDOMNode()) { michael@0: nsCOMPtr splittedPreviousContent; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParent), michael@0: &previousContentOffset, michael@0: nullptr, nullptr, nullptr, michael@0: address_of(splittedPreviousContent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (splittedPreviousContent) { michael@0: previousContentParent = michael@0: nsEditor::GetNodeLocation(splittedPreviousContent, michael@0: &previousContentOffset); michael@0: } michael@0: } michael@0: michael@0: res = MoveBlock(previousContentParent, aRightBlock, michael@0: previousContentOffset, rightOffset); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (brNode) mHTMLEditor->DeleteNode(brNode); michael@0: } michael@0: else michael@0: { michael@0: // normal case. blocks are siblings, or at least close enough to siblings. An example michael@0: // of the latter is a

paragraph

  • one
  • two
  • three
. The first michael@0: // li and the p are not true siblings, but we still want to join them if you backspace michael@0: // from li into p. michael@0: michael@0: // adjust whitespace at block boundaries michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, aLeftBlock, aRightBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Do br adjustment. michael@0: nsCOMPtr brNode; michael@0: res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (bMergeLists || mHTMLEditor->NodesSameType(aLeftBlock, aRightBlock)) { michael@0: // nodes are same type. merge them. michael@0: nsCOMPtr parent; michael@0: int32_t offset; michael@0: res = JoinNodesSmart(aLeftBlock, aRightBlock, address_of(parent), &offset); michael@0: if (NS_SUCCEEDED(res) && bMergeLists) michael@0: { michael@0: nsCOMPtr newBlock; michael@0: res = ConvertListType(aRightBlock, address_of(newBlock), michael@0: existingList, nsGkAtoms::li); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // nodes are disimilar types. michael@0: res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset); michael@0: } michael@0: if (NS_SUCCEEDED(res) && brNode) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(brNode); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /***************************************************************************************************** michael@0: * MoveBlock: this method is used to move the content from rightBlock into leftBlock michael@0: * Note that the "block" might merely be inline nodes between
s, or between blocks, etc. michael@0: * DTD containment rules are followed throughout. michael@0: * nsIDOMNode *aLeftBlock parent to receive moved content michael@0: * nsIDOMNode *aRightBlock parent to provide moved content michael@0: * int32_t aLeftOffset offset in aLeftBlock to move content to michael@0: * int32_t aRightOffset offset in aRightBlock to move content from michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int32_t aLeftOffset, int32_t aRightOffset) michael@0: { michael@0: nsCOMArray arrayOfNodes; michael@0: nsCOMPtr isupports; michael@0: // GetNodesFromPoint is the workhorse that figures out what we wnat to move. michael@0: nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset), michael@0: EditAction::makeList, arrayOfNodes, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=0; iDeleteNode(curNode); michael@0: } michael@0: else michael@0: { michael@0: // otherwise move the content as is, checking against the dtd. michael@0: res = MoveNodeSmart(curNode, aLeftBlock, &aLeftOffset); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /***************************************************************************************************** michael@0: * MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset). michael@0: * DTD containment rules are followed throughout. aOffset is updated to point _after_ michael@0: * inserted content. michael@0: * nsIDOMNode *aSource the selection. michael@0: * nsIDOMNode *aDest parent to receive moved content michael@0: * int32_t *aOffset offset in aDest to move content to michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res; michael@0: // check if this node can go into the destination node michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->CanContain(aDest, aSource)) { michael@0: // if it can, move it there michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (*aOffset != -1) ++(*aOffset); michael@0: } michael@0: else michael@0: { michael@0: // if it can't, move its children, and then delete it. michael@0: res = MoveContents(aSource, aDest, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(aSource); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /***************************************************************************************************** michael@0: * MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset). michael@0: * DTD containment rules are followed throughout. aOffset is updated to point _after_ michael@0: * inserted content. aSource is deleted. michael@0: * nsIDOMNode *aSource the selection. michael@0: * nsIDOMNode *aDest parent to receive moved content michael@0: * int32_t *aOffset offset in aDest to move content to michael@0: */ michael@0: nsresult michael@0: nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER); michael@0: if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: NS_ASSERTION(!mHTMLEditor->IsTextNode(aSource), "#text does not have contents"); michael@0: michael@0: nsCOMPtr child; michael@0: nsAutoString tag; michael@0: nsresult res; michael@0: aSource->GetFirstChild(getter_AddRefs(child)); michael@0: while (child) michael@0: { michael@0: res = MoveNodeSmart(child, aDest, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: aSource->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::DeleteNonTableElements(nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: if (!nsHTMLEditUtils::IsTableElementButNotTable(aNode)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: return mHTMLEditor->DeleteNode(aNode->AsDOMNode()); michael@0: } michael@0: michael@0: for (int32_t i = aNode->GetChildCount() - 1; i >= 0; --i) { michael@0: nsresult rv = DeleteNonTableElements(aNode->GetChildAt(i)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection, michael@0: nsIEditor::EDirection aDir, michael@0: nsresult aResult) michael@0: { michael@0: if (!aSelection) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // find where we are michael@0: nsCOMPtr startNode; michael@0: int32_t startOffset; michael@0: nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); michael@0: michael@0: // find any enclosing mailcite michael@0: nsCOMPtr citeNode; michael@0: res = GetTopEnclosingMailCite(startNode, address_of(citeNode), michael@0: IsPlaintextEditor()); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (citeNode) { michael@0: nsCOMPtr cite = do_QueryInterface(citeNode); michael@0: bool isEmpty = true, seenBR = false; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->IsEmptyNodeImpl(cite, &isEmpty, true, true, false, &seenBR); michael@0: if (isEmpty) michael@0: { michael@0: nsCOMPtr brNode; michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(citeNode, &offset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(citeNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (parent && seenBR) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: aSelection->Collapse(parent, offset); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // call through to base class michael@0: return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillMakeList(Selection* aSelection, michael@0: const nsAString* aListType, michael@0: bool aEntireList, michael@0: const nsAString* aBulletType, michael@0: bool* aCancel, michael@0: bool* aHandled, michael@0: const nsAString* aItemType) michael@0: { michael@0: if (!aSelection || !aListType || !aCancel || !aHandled) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: nsCOMPtr listTypeAtom = do_GetAtom(*aListType); michael@0: NS_ENSURE_TRUE(listTypeAtom, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: // deduce what tag to use for list items michael@0: nsCOMPtr itemType; michael@0: if (aItemType) { michael@0: itemType = do_GetAtom(*aItemType); michael@0: NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY); michael@0: } else if (listTypeAtom == nsGkAtoms::dl) { michael@0: itemType = nsGkAtoms::dd; michael@0: } else { michael@0: itemType = nsGkAtoms::li; michael@0: } michael@0: michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: michael@0: *aHandled = true; michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetListActionNodes(arrayOfNodes, aEntireList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: michael@0: // check if all our nodes are
s, or empty inlines michael@0: bool bOnlyBreaks = true; michael@0: for (int32_t j = 0; j < listCount; j++) { michael@0: nsIDOMNode* curNode = arrayOfNodes[j]; michael@0: // if curNode is not a Break or empty inline, we're done michael@0: if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) { michael@0: bOnlyBreaks = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // if no nodes, we make empty list. Ditto if the user tried to make a list michael@0: // of some # of breaks. michael@0: if (!listCount || bOnlyBreaks) { michael@0: nsCOMPtr parent, theList, theListItem; michael@0: int32_t offset; michael@0: michael@0: // if only breaks, delete them michael@0: if (bOnlyBreaks) { michael@0: for (int32_t j = 0; j < (int32_t)listCount; j++) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(arrayOfNodes[j]); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, michael@0: getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // make sure we can put a list here michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->CanContainTag(parent, listTypeAtom)) { michael@0: *aCancel = true; michael@0: return NS_OK; michael@0: } michael@0: res = SplitAsNeeded(aListType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aListType, parent, offset, michael@0: getter_AddRefs(theList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(nsDependentAtomString(itemType), theList, 0, michael@0: getter_AddRefs(theListItem)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theListItem; michael@0: // put selection in new list item michael@0: res = aSelection->Collapse(theListItem, 0); michael@0: // to prevent selection resetter from overriding us michael@0: selectionResetter.Abort(); michael@0: *aHandled = true; michael@0: return res; michael@0: } michael@0: michael@0: // if there is only one node in the array, and it is a list, div, or michael@0: // blockquote, then look inside of it until we find inner list or content. michael@0: michael@0: res = LookInsideDivBQandList(arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Ok, now go through all the nodes and put then in the list, michael@0: // or whatever is approriate. Wohoo! michael@0: michael@0: listCount = arrayOfNodes.Count(); michael@0: nsCOMPtr curParent; michael@0: nsCOMPtr curList; michael@0: nsCOMPtr prevListItem; michael@0: michael@0: for (int32_t i = 0; i < listCount; i++) { michael@0: // here's where we actually figure out what to do michael@0: nsCOMPtr newBlock; michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // make sure we don't assemble content that is in different table cells michael@0: // into the same list. respect table cell boundaries when listifying. michael@0: if (curList && InDifferentTableElements(curList, curNode)) { michael@0: curList = nullptr; michael@0: } michael@0: michael@0: // if curNode is a Break, delete it, and quit remembering prev list item michael@0: if (nsTextEditUtils::IsBreak(curNode)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: prevListItem = 0; michael@0: continue; michael@0: } else if (IsEmptyInline(curNode)) { michael@0: // if curNode is an empty inline container, delete it michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: michael@0: if (nsHTMLEditUtils::IsList(curNode)) { michael@0: // do we have a curList already? michael@0: if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList)) { michael@0: // move all of our children into curList. cheezy way to do it: move michael@0: // whole list and then RemoveContainer() on the list. ConvertListType michael@0: // first: that routine handles converting the list item types, if michael@0: // needed michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, michael@0: itemType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(newBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: // replace list with new list type michael@0: res = ConvertListType(curNode, address_of(newBlock), listTypeAtom, michael@0: itemType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curList = newBlock; michael@0: } michael@0: prevListItem = 0; michael@0: continue; michael@0: } michael@0: michael@0: if (nsHTMLEditUtils::IsListItem(curNode)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->GetTag(curParent) != listTypeAtom) { michael@0: // list item is in wrong type of list. if we don't have a curList, michael@0: // split the old list and make a new list of correct type. michael@0: if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNode(curParent, offset, michael@0: getter_AddRefs(newBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(curParent, &offset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aListType, parent, offset, michael@0: getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // move list item to new list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // convert list item type if needed michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->NodeIsType(curNode, itemType)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), michael@0: nsDependentAtomString(itemType)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } else { michael@0: // item is in right type of list. But we might still have to move it. michael@0: // and we might need to convert list item types. michael@0: if (!curList) { michael@0: curList = curParent; michael@0: } else if (curParent != curList) { michael@0: // move list item to new list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->NodeIsType(curNode, itemType)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), michael@0: nsDependentAtomString(itemType)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: nsCOMPtr curElement = do_QueryInterface(curNode); michael@0: NS_NAMED_LITERAL_STRING(typestr, "type"); michael@0: if (aBulletType && !aBulletType->IsEmpty()) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveAttribute(curElement, typestr); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: michael@0: // if we hit a div clear our prevListItem, insert divs contents michael@0: // into our node array, and remove the div michael@0: if (nsHTMLEditUtils::IsDiv(curNode)) { michael@0: prevListItem = nullptr; michael@0: int32_t j = i + 1; michael@0: res = GetInnerContent(curNode, arrayOfNodes, &j); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: listCount = arrayOfNodes.Count(); michael@0: continue; michael@0: } michael@0: michael@0: // need to make a list to put things in if we haven't already, michael@0: if (!curList) { michael@0: res = SplitAsNeeded(aListType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aListType, curParent, offset, michael@0: getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curList; michael@0: // curList is now the correct thing to put curNode in michael@0: prevListItem = 0; michael@0: } michael@0: michael@0: // if curNode isn't a list item, we must wrap it in one michael@0: nsCOMPtr listItem; michael@0: if (!nsHTMLEditUtils::IsListItem(curNode)) { michael@0: if (IsInlineNode(curNode) && prevListItem) { michael@0: // this is a continuation of some inline nodes that belong together in michael@0: // the same list item. use prevListItem michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, prevListItem, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: // don't wrap li around a paragraph. instead replace paragraph with li michael@0: if (nsHTMLEditUtils::IsParagraph(curNode)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), michael@0: nsDependentAtomString(itemType)); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), michael@0: nsDependentAtomString(itemType)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (IsInlineNode(curNode)) { michael@0: prevListItem = listItem; michael@0: } else { michael@0: prevListItem = nullptr; michael@0: } michael@0: } michael@0: } else { michael@0: listItem = curNode; michael@0: } michael@0: michael@0: if (listItem) { michael@0: // if we made a new list item, deal with it: tuck the listItem into the michael@0: // end of the active list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(listItem, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillRemoveList(Selection* aSelection, michael@0: bool aOrdered, michael@0: bool *aCancel, michael@0: bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: nsresult res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: nsCOMArray arrayOfRanges; michael@0: res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::makeList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // use these ranges to contruct a list of nodes to act on. michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetListActionNodes(arrayOfNodes, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Remove all non-editable nodes. Leave them be. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsIDOMNode* testNode = arrayOfNodes[i]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(testNode)) michael@0: { michael@0: arrayOfNodes.RemoveObjectAt(i); michael@0: } michael@0: } michael@0: michael@0: // reset list count michael@0: listCount = arrayOfNodes.Count(); michael@0: michael@0: // Only act on lists or list items in the array michael@0: nsCOMPtr curParent; michael@0: for (i=0; i arrayOfNodes; michael@0: res = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock, michael@0: arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Remove all non-editable nodes. Leave them be. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(arrayOfNodes[i])) michael@0: { michael@0: arrayOfNodes.RemoveObjectAt(i); michael@0: } michael@0: } michael@0: michael@0: // reset list count michael@0: listCount = arrayOfNodes.Count(); michael@0: michael@0: // if nothing visible in list, make an empty block michael@0: if (ListIsEmptyLine(arrayOfNodes)) michael@0: { michael@0: nsCOMPtr parent, theBlock; michael@0: int32_t offset; michael@0: michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (tString.EqualsLiteral("normal") || michael@0: tString.IsEmpty() ) // we are removing blocks (going to "body text") michael@0: { michael@0: nsCOMPtr curBlock = parent; michael@0: if (!IsBlockNode(curBlock)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: curBlock = mHTMLEditor->GetBlockNodeParent(parent); michael@0: } michael@0: nsCOMPtr curBlockPar; michael@0: NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER); michael@0: curBlock->GetParentNode(getter_AddRefs(curBlockPar)); michael@0: if (nsHTMLEditUtils::IsFormatNode(curBlock)) michael@0: { michael@0: // if the first editable node after selection is a br, consume it. Otherwise michael@0: // it gets pushed into a following block after the split, which is visually bad. michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (brNode && nsTextEditUtils::IsBreak(brNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(brNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // do the splits! michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // put a br at the split point michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // put selection at the split point michael@0: res = aSelection->Collapse(curBlockPar, offset); michael@0: selectionResetter.Abort(); // to prevent selection reseter from overriding us. michael@0: *aHandled = true; michael@0: } michael@0: // else nothing to do! michael@0: } michael@0: else // we are making a block michael@0: { michael@0: // consume a br, if needed michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (brNode && nsTextEditUtils::IsBreak(brNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(brNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // we don't need to act on this node any more michael@0: arrayOfNodes.RemoveObject(brNode); michael@0: } michael@0: // make sure we can put a block here michael@0: res = SplitAsNeeded(aBlockType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theBlock; michael@0: // delete anything that was in the list of nodes michael@0: for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[0]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: } michael@0: // put selection in new block michael@0: res = aSelection->Collapse(theBlock,0); michael@0: selectionResetter.Abort(); // to prevent selection reseter from overriding us. michael@0: *aHandled = true; michael@0: } michael@0: return res; michael@0: } michael@0: else michael@0: { michael@0: // Ok, now go through all the nodes and make the right kind of blocks, michael@0: // or whatever is approriate. Wohoo! michael@0: // Note: blockquote is handled a little differently michael@0: if (tString.EqualsLiteral("blockquote")) michael@0: res = MakeBlockquote(arrayOfNodes); michael@0: else if (tString.EqualsLiteral("normal") || michael@0: tString.IsEmpty() ) michael@0: res = RemoveBlockStyle(arrayOfNodes); michael@0: else michael@0: res = ApplyBlockStyle(arrayOfNodes, aBlockType); michael@0: return res; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::DidMakeBasicBlock(nsISelection *aSelection, michael@0: nsRulesInfo *aInfo, nsresult aResult) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: // check for empty block. if so, put a moz br in it. michael@0: if (!aSelection->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr parent; michael@0: int32_t offset; michael@0: nsresult res = nsEditor::GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertMozBRIfNeeded(parent); michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillIndent(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: nsresult res; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsCSSEnabled()) { michael@0: res = WillCSSIndent(aSelection, aCancel, aHandled); michael@0: } michael@0: else { michael@0: res = WillHTMLIndent(aSelection, aCancel, aHandled); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillCSSIndent(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: nsCOMArray arrayOfRanges; michael@0: nsCOMArray arrayOfNodes; michael@0: michael@0: // short circuit: detect case of collapsed selection inside an
  • . michael@0: // just sublist that
  • . This prevents bug 97797. michael@0: michael@0: nsCOMPtr liNode; michael@0: if (aSelection->Collapsed()) { michael@0: nsCOMPtr node, block; michael@0: int32_t offset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (IsBlockNode(node)) { michael@0: block = node; michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: block = mHTMLEditor->GetBlockNodeParent(node); michael@0: } michael@0: if (block && nsHTMLEditUtils::IsListItem(block)) michael@0: liNode = block; michael@0: } michael@0: michael@0: if (liNode) michael@0: { michael@0: arrayOfNodes.AppendObject(liNode); michael@0: } michael@0: else michael@0: { michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: res = GetNodesFromSelection(aSelection, EditAction::indent, arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); michael@0: // if nothing visible in list, make an empty block michael@0: if (ListIsEmptyLine(arrayOfNodes)) michael@0: { michael@0: nsCOMPtr parent, theBlock; michael@0: int32_t offset; michael@0: nsAutoString quoteType(NS_LITERAL_STRING("div")); michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // make sure we can put a block here michael@0: res = SplitAsNeeded("eType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theBlock; michael@0: RelativeChangeIndentationOfElementNode(theBlock, +1); michael@0: // delete anything that was in the list of nodes michael@0: for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[0]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: } michael@0: // put selection in new block michael@0: res = aSelection->Collapse(theBlock,0); michael@0: selectionResetter.Abort(); // to prevent selection reseter from overriding us. michael@0: *aHandled = true; michael@0: return res; michael@0: } michael@0: michael@0: // Ok, now go through all the nodes and put them in a blockquote, michael@0: // or whatever is appropriate. Wohoo! michael@0: int32_t i; michael@0: nsCOMPtr curParent; michael@0: nsCOMPtr curQuote; michael@0: nsCOMPtr curList; michael@0: nsCOMPtr sibling; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (i=0; i curNode = arrayOfNodes[i]; michael@0: michael@0: // Ignore all non-editable nodes. Leave them be. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(curNode)) continue; michael@0: michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // some logic for putting list items into nested lists... michael@0: if (nsHTMLEditUtils::IsList(curParent)) michael@0: { michael@0: sibling = nullptr; michael@0: michael@0: // Check for whether we should join a list that follows curNode. michael@0: // We do this if the next element is a list, and the list is of the michael@0: // same type (li/ol) as curNode was a part it. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); michael@0: if (sibling && nsHTMLEditUtils::IsList(sibling)) michael@0: { michael@0: nsAutoString curListTag, siblingListTag; michael@0: nsEditor::GetTagString(curParent, curListTag); michael@0: nsEditor::GetTagString(sibling, siblingListTag); michael@0: if (curListTag == siblingListTag) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, sibling, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: } michael@0: // Check for whether we should join a list that preceeds curNode. michael@0: // We do this if the previous element is a list, and the list is of michael@0: // the same type (li/ol) as curNode was a part of. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: if (sibling && nsHTMLEditUtils::IsList(sibling)) michael@0: { michael@0: nsAutoString curListTag, siblingListTag; michael@0: nsEditor::GetTagString(curParent, curListTag); michael@0: nsEditor::GetTagString(sibling, siblingListTag); michael@0: if (curListTag == siblingListTag) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, sibling, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: } michael@0: sibling = nullptr; michael@0: michael@0: // check to see if curList is still appropriate. Which it is if michael@0: // curNode is still right after it in the same list. michael@0: if (curList) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: } michael@0: michael@0: if (!curList || (sibling && sibling != curList)) michael@0: { michael@0: nsAutoString listTag; michael@0: nsEditor::GetTagString(curParent,listTag); michael@0: ToLowerCase(listTag); michael@0: // create a new nested list of correct type michael@0: res = SplitAsNeeded(&listTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // curList is now the correct thing to put curNode in michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curList; michael@0: } michael@0: // tuck the node into the end of the active list michael@0: uint32_t listLen; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, listLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: else // not a list item michael@0: { michael@0: if (IsBlockNode(curNode)) { michael@0: RelativeChangeIndentationOfElementNode(curNode, +1); michael@0: curQuote = nullptr; michael@0: } michael@0: else { michael@0: if (!curQuote) michael@0: { michael@0: // First, check that our element can contain a div. michael@0: if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) { michael@0: return NS_OK; // cancelled michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(divquoteType, "div"); michael@0: res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: RelativeChangeIndentationOfElementNode(curQuote, +1); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curQuote; michael@0: // curQuote is now the correct thing to put curNode in michael@0: } michael@0: michael@0: // tuck the node into the end of the active blockquote michael@0: uint32_t quoteLen; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillHTMLIndent(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: michael@0: nsCOMArray arrayOfRanges; michael@0: res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::indent); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // use these ranges to contruct a list of nodes to act on. michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); michael@0: michael@0: // if nothing visible in list, make an empty block michael@0: if (ListIsEmptyLine(arrayOfNodes)) michael@0: { michael@0: nsCOMPtr parent, theBlock; michael@0: int32_t offset; michael@0: michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // make sure we can put a block here michael@0: res = SplitAsNeeded("eType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theBlock; michael@0: // delete anything that was in the list of nodes michael@0: for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[0]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: } michael@0: // put selection in new block michael@0: res = aSelection->Collapse(theBlock,0); michael@0: selectionResetter.Abort(); // to prevent selection reseter from overriding us. michael@0: *aHandled = true; michael@0: return res; michael@0: } michael@0: michael@0: // Ok, now go through all the nodes and put them in a blockquote, michael@0: // or whatever is appropriate. Wohoo! michael@0: int32_t i; michael@0: nsCOMPtr curParent, curQuote, curList, indentedLI, sibling; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (i=0; i curNode = arrayOfNodes[i]; michael@0: michael@0: // Ignore all non-editable nodes. Leave them be. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(curNode)) continue; michael@0: michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // some logic for putting list items into nested lists... michael@0: if (nsHTMLEditUtils::IsList(curParent)) michael@0: { michael@0: sibling = nullptr; michael@0: michael@0: // Check for whether we should join a list that follows curNode. michael@0: // We do this if the next element is a list, and the list is of the michael@0: // same type (li/ol) as curNode was a part it. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling)); michael@0: if (sibling && nsHTMLEditUtils::IsList(sibling)) michael@0: { michael@0: nsAutoString curListTag, siblingListTag; michael@0: nsEditor::GetTagString(curParent, curListTag); michael@0: nsEditor::GetTagString(sibling, siblingListTag); michael@0: if (curListTag == siblingListTag) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, sibling, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // Check for whether we should join a list that preceeds curNode. michael@0: // We do this if the previous element is a list, and the list is of michael@0: // the same type (li/ol) as curNode was a part of. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: if (sibling && nsHTMLEditUtils::IsList(sibling)) michael@0: { michael@0: nsAutoString curListTag, siblingListTag; michael@0: nsEditor::GetTagString(curParent, curListTag); michael@0: nsEditor::GetTagString(sibling, siblingListTag); michael@0: if (curListTag == siblingListTag) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, sibling, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: sibling = nullptr; michael@0: michael@0: // check to see if curList is still appropriate. Which it is if michael@0: // curNode is still right after it in the same list. michael@0: if (curList) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: } michael@0: michael@0: if (!curList || (sibling && sibling != curList) ) michael@0: { michael@0: nsAutoString listTag; michael@0: nsEditor::GetTagString(curParent,listTag); michael@0: ToLowerCase(listTag); michael@0: // create a new nested list of correct type michael@0: res = SplitAsNeeded(&listTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // curList is now the correct thing to put curNode in michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curList; michael@0: } michael@0: // tuck the node into the end of the active list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // forget curQuote, if any michael@0: curQuote = nullptr; michael@0: } michael@0: michael@0: else // not a list item, use blockquote? michael@0: { michael@0: // if we are inside a list item, we don't want to blockquote, we want michael@0: // to sublist the list item. We may have several nodes listed in the michael@0: // array of nodes to act on, that are in the same list item. Since michael@0: // we only want to indent that li once, we must keep track of the most michael@0: // recent indented list item, and not indent it if we find another node michael@0: // to act on that is still inside the same li. michael@0: nsCOMPtr listitem=IsInListItem(curNode); michael@0: if (listitem) michael@0: { michael@0: if (indentedLI == listitem) continue; // already indented this list item michael@0: curParent = nsEditor::GetNodeLocation(listitem, &offset); michael@0: // check to see if curList is still appropriate. Which it is if michael@0: // curNode is still right after it in the same list. michael@0: if (curList) michael@0: { michael@0: sibling = nullptr; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: } michael@0: michael@0: if (!curList || (sibling && sibling != curList) ) michael@0: { michael@0: nsAutoString listTag; michael@0: nsEditor::GetTagString(curParent,listTag); michael@0: ToLowerCase(listTag); michael@0: // create a new nested list of correct type michael@0: res = SplitAsNeeded(&listTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(listitem, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember we indented this li michael@0: indentedLI = listitem; michael@0: } michael@0: michael@0: else michael@0: { michael@0: // need to make a blockquote to put things in if we haven't already, michael@0: // or if this node doesn't go in blockquote we used earlier. michael@0: // One reason it might not go in prio blockquote is if we are now michael@0: // in a different table cell. michael@0: if (curQuote && InDifferentTableElements(curQuote, curNode)) { michael@0: curQuote = nullptr; michael@0: } michael@0: michael@0: if (!curQuote) michael@0: { michael@0: // First, check that our element can contain a blockquote. michael@0: if (!mEditor->CanContainTag(curParent, nsGkAtoms::blockquote)) { michael@0: return NS_OK; // cancelled michael@0: } michael@0: michael@0: res = SplitAsNeeded("eType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curQuote; michael@0: // curQuote is now the correct thing to put curNode in michael@0: } michael@0: michael@0: // tuck the node into the end of the active blockquote michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curQuote, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // forget curList, if any michael@0: curList = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillOutdent(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: // initialize out param michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: nsresult res = NS_OK; michael@0: nsCOMPtr rememberedLeftBQ, rememberedRightBQ; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: bool useCSS = mHTMLEditor->IsCSSEnabled(); michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // some scoping for selection resetting - we may need to tweak it michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesFromSelection(aSelection, EditAction::outdent, michael@0: arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Ok, now go through all the nodes and remove a level of blockquoting, michael@0: // or whatever is appropriate. Wohoo! michael@0: michael@0: nsCOMPtr curBlockQuote, firstBQChild, lastBQChild; michael@0: bool curBlockQuoteIsIndentedWithCSS = false; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t i; michael@0: nsCOMPtr curParent; michael@0: for (i=0; i curNode = arrayOfNodes[i]; michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // is it a blockquote? michael@0: if (nsHTMLEditUtils::IsBlockquote(curNode)) michael@0: { michael@0: // if it is a blockquote, remove it. michael@0: // So we need to finish up dealng with any curBlockQuote first. michael@0: if (curBlockQuote) michael@0: { michael@0: res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, michael@0: curBlockQuoteIsIndentedWithCSS, michael@0: address_of(rememberedLeftBQ), michael@0: address_of(rememberedRightBQ)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; michael@0: curBlockQuoteIsIndentedWithCSS = false; michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: // is it a block with a 'margin' property? michael@0: if (useCSS && IsBlockNode(curNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); michael@0: nsAutoString value; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value); michael@0: float f; michael@0: nsCOMPtr unit; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); michael@0: if (f > 0) michael@0: { michael@0: RelativeChangeIndentationOfElementNode(curNode, -1); michael@0: continue; michael@0: } michael@0: } michael@0: // is it a list item? michael@0: if (nsHTMLEditUtils::IsListItem(curNode)) michael@0: { michael@0: // if it is a list item, that means we are not outdenting whole list. michael@0: // So we need to finish up dealing with any curBlockQuote, and then michael@0: // pop this list item. michael@0: if (curBlockQuote) michael@0: { michael@0: res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, michael@0: curBlockQuoteIsIndentedWithCSS, michael@0: address_of(rememberedLeftBQ), michael@0: address_of(rememberedRightBQ)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; michael@0: curBlockQuoteIsIndentedWithCSS = false; michael@0: } michael@0: bool bOutOfList; michael@0: res = PopListItem(curNode, &bOutOfList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: continue; michael@0: } michael@0: // do we have a blockquote that we are already committed to removing? michael@0: if (curBlockQuote) michael@0: { michael@0: // if so, is this node a descendant? michael@0: if (nsEditorUtils::IsDescendantOf(curNode, curBlockQuote)) michael@0: { michael@0: lastBQChild = curNode; michael@0: continue; // then we don't need to do anything different for this node michael@0: } michael@0: else michael@0: { michael@0: // otherwise, we have progressed beyond end of curBlockQuote, michael@0: // so lets handle it now. We need to remove the portion of michael@0: // curBlockQuote that contains [firstBQChild - lastBQChild]. michael@0: res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, michael@0: curBlockQuoteIsIndentedWithCSS, michael@0: address_of(rememberedLeftBQ), michael@0: address_of(rememberedRightBQ)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0; michael@0: curBlockQuoteIsIndentedWithCSS = false; michael@0: // fall out and handle curNode michael@0: } michael@0: } michael@0: michael@0: // are we inside a blockquote? michael@0: nsCOMPtr n = curNode; michael@0: nsCOMPtr tmp; michael@0: curBlockQuoteIsIndentedWithCSS = false; michael@0: // keep looking up the hierarchy as long as we don't hit the body or the michael@0: // active editing host or a table element (other than an entire table) michael@0: while (!nsTextEditUtils::IsBody(n) && mHTMLEditor && michael@0: mHTMLEditor->IsDescendantOfEditorRoot(n) michael@0: && (nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n))) michael@0: { michael@0: n->GetParentNode(getter_AddRefs(tmp)); michael@0: if (!tmp) { michael@0: break; michael@0: } michael@0: n = tmp; michael@0: if (nsHTMLEditUtils::IsBlockquote(n)) michael@0: { michael@0: // if so, remember it, and remember first node we are taking out of it. michael@0: curBlockQuote = n; michael@0: firstBQChild = curNode; michael@0: lastBQChild = curNode; michael@0: break; michael@0: } michael@0: else if (useCSS) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode); michael@0: nsAutoString value; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value); michael@0: float f; michael@0: nsCOMPtr unit; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); michael@0: if (f > 0 && !(nsHTMLEditUtils::IsList(curParent) && nsHTMLEditUtils::IsList(curNode))) michael@0: { michael@0: curBlockQuote = n; michael@0: firstBQChild = curNode; michael@0: lastBQChild = curNode; michael@0: curBlockQuoteIsIndentedWithCSS = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!curBlockQuote) michael@0: { michael@0: // could not find an enclosing blockquote for this node. handle list cases. michael@0: if (nsHTMLEditUtils::IsList(curParent)) // move node out of list michael@0: { michael@0: if (nsHTMLEditUtils::IsList(curNode)) // just unwrap this sublist michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // handled list item case above michael@0: } michael@0: else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out michael@0: { michael@0: nsCOMPtr child; michael@0: curNode->GetLastChild(getter_AddRefs(child)); michael@0: while (child) michael@0: { michael@0: if (nsHTMLEditUtils::IsListItem(child)) michael@0: { michael@0: bool bOutOfList; michael@0: res = PopListItem(child, &bOutOfList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (nsHTMLEditUtils::IsList(child)) michael@0: { michael@0: // We have an embedded list, so move it out from under the michael@0: // parent list. Be sure to put it after the parent list michael@0: // because this loop iterates backwards through the parent's michael@0: // list of children. michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(child, curParent, offset + 1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // delete any non- list items for now michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: curNode->GetLastChild(getter_AddRefs(child)); michael@0: } michael@0: // delete the now-empty list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (useCSS) { michael@0: nsCOMPtr element; michael@0: nsCOMPtr textNode = do_QueryInterface(curNode); michael@0: if (textNode) { michael@0: // We want to outdent the parent of text nodes michael@0: nsCOMPtr parent; michael@0: textNode->GetParentNode(getter_AddRefs(parent)); michael@0: element = do_QueryInterface(parent); michael@0: } else { michael@0: element = do_QueryInterface(curNode); michael@0: } michael@0: if (element) { michael@0: RelativeChangeIndentationOfElementNode(element, -1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (curBlockQuote) michael@0: { michael@0: // we have a blockquote we haven't finished handling michael@0: res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild, michael@0: curBlockQuoteIsIndentedWithCSS, michael@0: address_of(rememberedLeftBQ), michael@0: address_of(rememberedRightBQ)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: // make sure selection didn't stick to last piece of content in old bq michael@0: // (only a problem for collapsed selections) michael@0: if (rememberedLeftBQ || rememberedRightBQ) { michael@0: if (aSelection->Collapsed()) { michael@0: // push selection past end of rememberedLeftBQ michael@0: nsCOMPtr sNode; michael@0: int32_t sOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); michael@0: if (rememberedLeftBQ && michael@0: ((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ))) michael@0: { michael@0: // selection is inside rememberedLeftBQ - push it past it. michael@0: sNode = nsEditor::GetNodeLocation(rememberedLeftBQ, &sOffset); michael@0: sOffset++; michael@0: aSelection->Collapse(sNode, sOffset); michael@0: } michael@0: // and pull selection before beginning of rememberedRightBQ michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset); michael@0: if (rememberedRightBQ && michael@0: ((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ))) michael@0: { michael@0: // selection is inside rememberedRightBQ - push it before it. michael@0: sNode = nsEditor::GetNodeLocation(rememberedRightBQ, &sOffset); michael@0: aSelection->Collapse(sNode, sOffset); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // RemovePartOfBlock: split aBlock and move aStartChild to aEndChild out michael@0: // of aBlock. return left side of block (if any) in michael@0: // aLeftNode. return right side of block (if any) in michael@0: // aRightNode. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock, michael@0: nsIDOMNode *aStartChild, michael@0: nsIDOMNode *aEndChild, michael@0: nsCOMPtr *aLeftNode, michael@0: nsCOMPtr *aRightNode) michael@0: { michael@0: nsCOMPtr middleNode; michael@0: nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, michael@0: aLeftNode, aRightNode, michael@0: address_of(middleNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // get rid of part of blockquote we are outdenting michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: return mHTMLEditor->RemoveBlockContainer(aBlock); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock, michael@0: nsIDOMNode *aStartChild, michael@0: nsIDOMNode *aEndChild, michael@0: nsCOMPtr *aLeftNode, michael@0: nsCOMPtr *aRightNode, michael@0: nsCOMPtr *aMiddleNode) michael@0: { michael@0: NS_ENSURE_TRUE(aBlock && aStartChild && aEndChild, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr leftNode, rightNode; michael@0: int32_t startOffset, endOffset, offset; michael@0: nsresult res; michael@0: michael@0: // get split point location michael@0: nsCOMPtr startParent = nsEditor::GetNodeLocation(aStartChild, &startOffset); michael@0: michael@0: // do the splits! michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset, michael@0: true, address_of(leftNode), address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (rightNode) aBlock = rightNode; michael@0: michael@0: // remember left portion of block if caller requested michael@0: if (aLeftNode) michael@0: *aLeftNode = leftNode; michael@0: michael@0: // get split point location michael@0: nsCOMPtr endParent = nsEditor::GetNodeLocation(aEndChild, &endOffset); michael@0: endOffset++; // want to be after lastBQChild michael@0: michael@0: // do the splits! michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset, michael@0: true, address_of(leftNode), address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (leftNode) aBlock = leftNode; michael@0: michael@0: // remember right portion of block if caller requested michael@0: if (aRightNode) michael@0: *aRightNode = rightNode; michael@0: michael@0: if (aMiddleNode) michael@0: *aMiddleNode = aBlock; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock, michael@0: nsIDOMNode *aStartChild, michael@0: nsIDOMNode *aEndChild, michael@0: bool aIsBlockIndentedWithCSS, michael@0: nsCOMPtr *aLeftNode, michael@0: nsCOMPtr *aRightNode) michael@0: { michael@0: nsCOMPtr middleNode; michael@0: nsresult res = SplitBlock(aBlock, aStartChild, aEndChild, michael@0: aLeftNode, michael@0: aRightNode, michael@0: address_of(middleNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (aIsBlockIndentedWithCSS) { michael@0: res = RelativeChangeIndentationOfElementNode(middleNode, -1); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(middleNode); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ConvertListType: convert list type and list item type. michael@0: // michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ConvertListType(nsIDOMNode* aList, michael@0: nsCOMPtr* outList, michael@0: nsIAtom* aListType, michael@0: nsIAtom* aItemType) michael@0: { michael@0: MOZ_ASSERT(aListType); michael@0: MOZ_ASSERT(aItemType); michael@0: michael@0: NS_ENSURE_TRUE(aList && outList, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr list = do_QueryInterface(aList); michael@0: NS_ENSURE_STATE(list); michael@0: michael@0: nsCOMPtr outNode; michael@0: nsresult rv = ConvertListType(list, getter_AddRefs(outNode), aListType, aItemType); michael@0: *outList = outNode ? outNode->AsDOMNode() : nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::ConvertListType(nsINode* aList, michael@0: dom::Element** aOutList, michael@0: nsIAtom* aListType, michael@0: nsIAtom* aItemType) michael@0: { michael@0: MOZ_ASSERT(aList); michael@0: MOZ_ASSERT(aOutList); michael@0: MOZ_ASSERT(aListType); michael@0: MOZ_ASSERT(aItemType); michael@0: michael@0: nsCOMPtr child = aList->GetFirstChild(); michael@0: while (child) michael@0: { michael@0: if (child->IsElement()) { michael@0: dom::Element* element = child->AsElement(); michael@0: if (nsHTMLEditUtils::IsListItem(element) && !element->IsHTML(aItemType)) { michael@0: nsCOMPtr temp; michael@0: nsresult rv = michael@0: mHTMLEditor->ReplaceContainer(child, getter_AddRefs(temp), michael@0: nsDependentAtomString(aItemType)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: child = temp.forget(); michael@0: } else if (nsHTMLEditUtils::IsList(element) && michael@0: !element->IsHTML(aListType)) { michael@0: nsCOMPtr temp; michael@0: nsresult rv = michael@0: ConvertListType(child, getter_AddRefs(temp), aListType, aItemType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: child = temp.forget(); michael@0: } michael@0: } michael@0: child = child->GetNextSibling(); michael@0: } michael@0: michael@0: if (aList->IsElement() && aList->AsElement()->IsHTML(aListType)) { michael@0: nsCOMPtr list = aList->AsElement(); michael@0: list.forget(aOutList); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mHTMLEditor->ReplaceContainer(aList, aOutList, michael@0: nsDependentAtomString(aListType)); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // CreateStyleForInsertText: take care of clearing and setting appropriate michael@0: // style nodes for text insertion. michael@0: // michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, michael@0: nsIDOMDocument *aDoc) michael@0: { michael@0: MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState); michael@0: michael@0: bool weDidSomething = false; michael@0: nsCOMPtr node, tmp; michael@0: int32_t offset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, michael@0: getter_AddRefs(node), michael@0: &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // next examine our present style and make sure default styles are either michael@0: // present or explicitly overridden. If neither, add the default style to michael@0: // the TypeInState michael@0: int32_t length = mHTMLEditor->mDefaultStyles.Length(); michael@0: for (int32_t j = 0; j < length; j++) { michael@0: PropItem* propItem = mHTMLEditor->mDefaultStyles[j]; michael@0: MOZ_ASSERT(propItem); michael@0: bool bFirst, bAny, bAll; michael@0: michael@0: // GetInlineProperty also examine TypeInState. The only gotcha here is michael@0: // that a cleared property looks like an unset property. For now I'm michael@0: // assuming that's not a problem: that default styles will always be michael@0: // multivalue styles (like font face or size) where clearing the style michael@0: // means we want to go back to the default. If we ever wanted a "toggle" michael@0: // style like bold for a default, though, I'll have to add code to detect michael@0: // the difference between unset and explicitly cleared, else user would michael@0: // never be able to unbold, for instance. michael@0: nsAutoString curValue; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &propItem->attr, michael@0: nullptr, &bFirst, &bAny, &bAll, michael@0: &curValue, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!bAny) { michael@0: // no style set for this prop/attr michael@0: mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, michael@0: propItem->value); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr rootElement; michael@0: res = aDoc->GetDocumentElement(getter_AddRefs(rootElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // process clearing any styles first michael@0: nsAutoPtr item(mHTMLEditor->mTypeInState->TakeClearProperty()); michael@0: while (item && node != rootElement) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->ClearStyle(address_of(node), &offset, michael@0: item->tag, &item->attr); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: item = mHTMLEditor->mTypeInState->TakeClearProperty(); michael@0: weDidSomething = true; michael@0: } michael@0: michael@0: // then process setting any styles michael@0: int32_t relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize(); michael@0: item = mHTMLEditor->mTypeInState->TakeSetProperty(); michael@0: michael@0: if (item || relFontSize) { michael@0: // we have at least one style to add; make a new text node to insert style michael@0: // nodes above. michael@0: if (mHTMLEditor->IsTextNode(node)) { michael@0: // if we are in a text node, split it michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node->GetParentNode(getter_AddRefs(tmp)); michael@0: node = tmp; michael@0: } michael@0: if (!mHTMLEditor->IsContainer(node)) { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr newNode; michael@0: nsCOMPtr nodeAsText; michael@0: res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER); michael@0: newNode = do_QueryInterface(nodeAsText); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->InsertNode(newNode, node, offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: node = newNode; michael@0: offset = 0; michael@0: weDidSomething = true; michael@0: michael@0: if (relFontSize) { michael@0: // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller michael@0: int32_t dir = relFontSize > 0 ? 1 : -1; michael@0: for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, michael@0: 0, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: while (item) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr, michael@0: &item->value); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: item = mHTMLEditor->mTypeInState->TakeSetProperty(); michael@0: } michael@0: } michael@0: if (weDidSomething) { michael@0: return aSelection->Collapse(node, offset); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsEmptyBlock: figure out if aNode is (or is inside) an empty block. michael@0: // A block can have children and still be considered empty, michael@0: // if the children are empty or non-editable. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode, michael@0: bool *outIsEmptyBlock, michael@0: bool aMozBRDoesntCount, michael@0: bool aListItemsNotEmpty) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && outIsEmptyBlock, NS_ERROR_NULL_POINTER); michael@0: *outIsEmptyBlock = true; michael@0: michael@0: // nsresult res = NS_OK; michael@0: nsCOMPtr nodeToTest; michael@0: if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode); michael@0: // else nsCOMPtr block; michael@0: // looks like I forgot to finish this. Wonder what I was going to do? michael@0: michael@0: NS_ENSURE_TRUE(nodeToTest, NS_ERROR_NULL_POINTER); michael@0: return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock, michael@0: aMozBRDoesntCount, aListItemsNotEmpty); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillAlign(Selection* aSelection, michael@0: const nsAString *alignType, michael@0: bool *aCancel, michael@0: bool *aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: *aHandled = true; michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesFromSelection(aSelection, EditAction::align, arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if we don't have any nodes, or we have only a single br, then we are michael@0: // creating an empty alignment div. We have to do some different things for these. michael@0: bool emptyDiv = false; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: if (!listCount) emptyDiv = true; michael@0: if (listCount == 1) michael@0: { michael@0: nsCOMPtr theNode = arrayOfNodes[0]; michael@0: michael@0: if (nsHTMLEditUtils::SupportsAlignAttr(theNode)) michael@0: { michael@0: // the node is a table element, an horiz rule, a paragraph, a div michael@0: // or a section header; in HTML 4, it can directly carry the ALIGN michael@0: // attribute and we don't need to make a div! If we are in CSS mode, michael@0: // all the work is done in AlignBlock michael@0: nsCOMPtr theElem = do_QueryInterface(theNode); michael@0: res = AlignBlock(theElem, alignType, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (nsTextEditUtils::IsBreak(theNode)) michael@0: { michael@0: // The special case emptyDiv code (below) that consumes BRs can michael@0: // cause tables to split if the start node of the selection is michael@0: // not in a table cell or caption, for example parent is a . michael@0: // Avoid this unnecessary splitting if possible by leaving emptyDiv michael@0: // FALSE so that we fall through to the normal case alignment code. michael@0: // michael@0: // XXX: It seems a little error prone for the emptyDiv special michael@0: // case code to assume that the start node of the selection michael@0: // is the parent of the single node in the arrayOfNodes, as michael@0: // the paragraph above points out. Do we rely on the selection michael@0: // start node because of the fact that arrayOfNodes can be empty? michael@0: // We should probably revisit this issue. - kin michael@0: michael@0: nsCOMPtr parent; michael@0: int32_t offset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: michael@0: if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent)) michael@0: emptyDiv = true; michael@0: } michael@0: } michael@0: if (emptyDiv) michael@0: { michael@0: int32_t offset; michael@0: nsCOMPtr brNode, parent, theDiv, sib; michael@0: NS_NAMED_LITERAL_STRING(divType, "div"); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = SplitAsNeeded(&divType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // consume a trailing br, if any. This is to keep an alignment from michael@0: // creating extra lines, if possible. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (brNode && nsTextEditUtils::IsBreak(brNode)) michael@0: { michael@0: // making use of html structure... if next node after where michael@0: // we are putting our div is not a block, then the br we michael@0: // found is in same block we are, so its safe to consume it. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!IsBlockNode(sib)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(brNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theDiv; michael@0: // set up the alignment on the div, using HTML or CSS michael@0: nsCOMPtr divElem = do_QueryInterface(theDiv); michael@0: res = AlignBlock(divElem, alignType, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *aHandled = true; michael@0: // put in a moz-br so that it won't get deleted michael@0: res = CreateMozBR(theDiv, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = aSelection->Collapse(theDiv, 0); michael@0: selectionResetter.Abort(); // don't reset our selection in this case. michael@0: return res; michael@0: } michael@0: michael@0: // Next we detect all the transitions in the array, where a transition michael@0: // means that adjacent nodes in the array don't have the same parent. michael@0: michael@0: nsTArray transitionList; michael@0: res = MakeTransitionList(arrayOfNodes, transitionList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Ok, now go through all the nodes and give them an align attrib or put them in a div, michael@0: // or whatever is appropriate. Wohoo! michael@0: michael@0: nsCOMPtr curParent; michael@0: nsCOMPtr curDiv; michael@0: bool useCSS = mHTMLEditor->IsCSSEnabled(); michael@0: for (int32_t i = 0; i < listCount; ++i) { michael@0: // here's where we actually figure out what to do michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: michael@0: // Ignore all non-editable nodes. Leave them be. michael@0: if (!mHTMLEditor->IsEditable(curNode)) continue; michael@0: michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // the node is a table element, an horiz rule, a paragraph, a div michael@0: // or a section header; in HTML 4, it can directly carry the ALIGN michael@0: // attribute and we don't need to nest it, just set the alignment. michael@0: // In CSS, assign the corresponding CSS styles in AlignBlock michael@0: if (nsHTMLEditUtils::SupportsAlignAttr(curNode)) michael@0: { michael@0: nsCOMPtr curElem = do_QueryInterface(curNode); michael@0: res = AlignBlock(curElem, alignType, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // clear out curDiv so that we don't put nodes after this one into it michael@0: curDiv = 0; michael@0: continue; michael@0: } michael@0: michael@0: // Skip insignificant formatting text nodes to prevent michael@0: // unnecessary structure splitting! michael@0: bool isEmptyTextNode = false; michael@0: if (nsEditor::IsTextNode(curNode) && michael@0: ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) || michael@0: nsHTMLEditUtils::IsList(curParent) || michael@0: (NS_SUCCEEDED(mHTMLEditor->IsEmptyNode(curNode, &isEmptyTextNode)) && isEmptyTextNode))) michael@0: continue; michael@0: michael@0: // if it's a list item, or a list michael@0: // inside a list, forget any "current" div, and instead put divs inside michael@0: // the appropriate block (td, li, etc) michael@0: if ( nsHTMLEditUtils::IsListItem(curNode) michael@0: || nsHTMLEditUtils::IsList(curNode)) michael@0: { michael@0: res = RemoveAlignment(curNode, *alignType, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (useCSS) { michael@0: nsCOMPtr curElem = do_QueryInterface(curNode); michael@0: NS_NAMED_LITERAL_STRING(attrName, "align"); michael@0: int32_t count; michael@0: mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nullptr, michael@0: &attrName, alignType, michael@0: &count, false); michael@0: curDiv = 0; michael@0: continue; michael@0: } michael@0: else if (nsHTMLEditUtils::IsList(curParent)) { michael@0: // if we don't use CSS, add a contraint to list element : they have michael@0: // to be inside another list, ie >= second level of nesting michael@0: res = AlignInnerBlocks(curNode, alignType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curDiv = 0; michael@0: continue; michael@0: } michael@0: // clear out curDiv so that we don't put nodes after this one into it michael@0: } michael@0: michael@0: // need to make a div to put things in if we haven't already, michael@0: // or if this node doesn't go in div we used earlier. michael@0: if (!curDiv || transitionList[i]) michael@0: { michael@0: // First, check that our element can contain a div. michael@0: NS_NAMED_LITERAL_STRING(divType, "div"); michael@0: if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) { michael@0: return NS_OK; // cancelled michael@0: } michael@0: michael@0: res = SplitAsNeeded(&divType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curDiv; michael@0: // set up the alignment on the div michael@0: nsCOMPtr divElem = do_QueryInterface(curDiv); michael@0: res = AlignBlock(divElem, alignType, true); michael@0: //nsAutoString attr(NS_LITERAL_STRING("align")); michael@0: //res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); michael@0: //NS_ENSURE_SUCCESS(res, res); michael@0: // curDiv is now the correct thing to put curNode in michael@0: } michael@0: michael@0: // tuck the node into the end of the active div michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curDiv, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // AlignInnerBlocks: align inside table cells or list items michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: michael@0: // gather list of table cells or list items michael@0: nsCOMArray arrayOfNodes; michael@0: nsTableCellAndListItemFunctor functor; michael@0: nsDOMIterator iter; michael@0: res = iter.Init(aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = iter.AppendList(functor, arrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // now that we have the list, align their contents as requested michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t j; michael@0: michael@0: for (j = 0; j < listCount; j++) michael@0: { michael@0: nsIDOMNode* node = arrayOfNodes[0]; michael@0: res = AlignBlockContents(node, alignType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // AlignBlockContents: align contents of a block element michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr firstChild, lastChild, divNode; michael@0: michael@0: bool useCSS = mHTMLEditor->IsCSSEnabled(); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_NAMED_LITERAL_STRING(attr, "align"); michael@0: if (!firstChild) michael@0: { michael@0: // this cell has no content, nothing to align michael@0: } michael@0: else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild)) michael@0: { michael@0: // the cell already has a div containing all of its content: just michael@0: // act on this div. michael@0: nsCOMPtr divElem = do_QueryInterface(firstChild); michael@0: if (useCSS) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); michael@0: } michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // else we need to put in a div, set the alignment, and toss in all the children michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // set up the alignment on the div michael@0: nsCOMPtr divElem = do_QueryInterface(divNode); michael@0: if (useCSS) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false); michael@0: } michael@0: else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttribute(divElem, attr, *alignType); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // tuck the children into the end of the active div michael@0: while (lastChild && (lastChild != divNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(lastChild, divNode, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle michael@0: // case of deleting from inside an empty block. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode, michael@0: nsIDOMNode *aBodyNode, michael@0: nsISelection *aSelection, michael@0: bool *aHandled) michael@0: { michael@0: // If the editing host is an inline element, bail out early. michael@0: if (IsInlineNode(aBodyNode)) { michael@0: return NS_OK; michael@0: } michael@0: // if we are inside an empty block, delete it. michael@0: // Note: do NOT delete table elements this way. michael@0: nsresult res = NS_OK; michael@0: nsCOMPtr block, emptyBlock; michael@0: if (IsBlockNode(aStartNode)) michael@0: block = aStartNode; michael@0: else michael@0: block = mHTMLEditor->GetBlockNodeParent(aStartNode); michael@0: bool bIsEmptyNode; michael@0: if (block != aBodyNode) // efficiency hack. avoiding IsEmptyNode() call when in body michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode)) michael@0: { michael@0: emptyBlock = block; michael@0: block = mHTMLEditor->GetBlockNodeParent(emptyBlock); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr emptyContent = do_QueryInterface(emptyBlock); michael@0: if (emptyBlock && emptyContent->IsEditable()) michael@0: { michael@0: int32_t offset; michael@0: nsCOMPtr blockParent = nsEditor::GetNodeLocation(emptyBlock, &offset); michael@0: NS_ENSURE_TRUE(blockParent && offset >= 0, NS_ERROR_FAILURE); michael@0: michael@0: if (nsHTMLEditUtils::IsListItem(emptyBlock)) michael@0: { michael@0: // are we the first list item in the list? michael@0: bool bIsFirst; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bIsFirst) michael@0: { michael@0: int32_t listOffset; michael@0: nsCOMPtr listParent = nsEditor::GetNodeLocation(blockParent, michael@0: &listOffset); michael@0: NS_ENSURE_TRUE(listParent && listOffset >= 0, NS_ERROR_FAILURE); michael@0: // if we are a sublist, skip the br creation michael@0: if (!nsHTMLEditUtils::IsList(listParent)) michael@0: { michael@0: // create a br before list michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // adjust selection to be right before it michael@0: res = aSelection->Collapse(listParent, listOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // else just let selection perculate up. We'll adjust it in AfterEdit() michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // adjust selection to be right after it michael@0: res = aSelection->Collapse(blockParent, offset+1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(emptyBlock); michael@0: *aHandled = true; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock, michael@0: BRLocation aWhere, michael@0: nsCOMPtr *outBRNode, michael@0: int32_t aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aBlock && outBRNode, NS_ERROR_NULL_POINTER); michael@0: *outBRNode = nullptr; michael@0: michael@0: nsCOMPtr testNode; michael@0: int32_t testOffset = 0; michael@0: bool runTest = false; michael@0: michael@0: if (aWhere == kBlockEnd) michael@0: { michael@0: nsCOMPtr rightmostNode = michael@0: mHTMLEditor->GetRightmostChild(aBlock, true); // no block crossing michael@0: michael@0: if (rightmostNode) michael@0: { michael@0: int32_t nodeOffset; michael@0: nsCOMPtr nodeParent = nsEditor::GetNodeLocation(rightmostNode, michael@0: &nodeOffset); michael@0: runTest = true; michael@0: testNode = nodeParent; michael@0: // use offset + 1, because we want the last node included in our michael@0: // evaluation michael@0: testOffset = nodeOffset + 1; michael@0: } michael@0: } michael@0: else if (aOffset) michael@0: { michael@0: runTest = true; michael@0: testNode = aBlock; michael@0: // we'll check everything to the left of the input position michael@0: testOffset = aOffset; michael@0: } michael@0: michael@0: if (runTest) michael@0: { michael@0: nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset); michael@0: if (WSType::br == wsTester.mStartReason) { michael@0: *outBRNode = wsTester.mStartReasonNode; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetInnerContent: aList and aTbl allow the caller to specify what kind michael@0: // of content to "look inside". If aTbl is true, look inside michael@0: // any table content, and insert the inner content into the michael@0: // supplied issupportsarray at offset aIndex. michael@0: // Similarly with aList and list content. michael@0: // aIndex is updated to point past inserted elements. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray &outArrayOfNodes, michael@0: int32_t *aIndex, bool aList, bool aTbl) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aIndex, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node; michael@0: michael@0: nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node)); michael@0: while (NS_SUCCEEDED(res) && node) michael@0: { michael@0: if ( ( aList && (nsHTMLEditUtils::IsList(node) || michael@0: nsHTMLEditUtils::IsListItem(node) ) ) michael@0: || ( aTbl && nsHTMLEditUtils::IsTableElement(node) ) ) michael@0: { michael@0: res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: outArrayOfNodes.InsertObjectAt(node, *aIndex); michael@0: (*aIndex)++; michael@0: } michael@0: nsCOMPtr tmp; michael@0: res = node->GetNextSibling(getter_AddRefs(tmp)); michael@0: node = tmp; michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ExpandSelectionForDeletion: this promotes our selection to include blocks michael@0: // that have all their children selected. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // don't need to touch collapsed selections michael@0: if (aSelection->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t rangeCount; michael@0: nsresult res = aSelection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // we don't need to mess with cell selections, and we assume multirange selections are those. michael@0: if (rangeCount != 1) return NS_OK; michael@0: michael@0: // find current sel start and end michael@0: nsCOMPtr range; michael@0: res = aSelection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selStartNode, selEndNode, selCommon; michael@0: int32_t selStartOffset, selEndOffset; michael@0: michael@0: res = range->GetStartContainer(getter_AddRefs(selStartNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetStartOffset(&selStartOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(selEndNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&selEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // find current selection common block parent michael@0: res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!IsBlockNode(selCommon)) michael@0: selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon); michael@0: michael@0: // set up for loops and cache our root element michael@0: bool stillLooking = true; michael@0: nsCOMPtr visNode, firstBRParent; michael@0: int32_t visOffset=0, firstBROffset=0; michael@0: WSType wsType; michael@0: nsCOMPtr rootContent = mHTMLEditor->GetActiveEditingHost(); michael@0: nsCOMPtr rootElement = do_QueryInterface(rootContent); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE); michael@0: michael@0: // find previous visible thingy before start of selection michael@0: if ((selStartNode!=selCommon) && (selStartNode!=rootElement)) michael@0: { michael@0: while (stillLooking) michael@0: { michael@0: nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset); michael@0: wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::thisBlock) { michael@0: // we want to keep looking up. But stop if we are crossing table element michael@0: // boundaries, or if we hit the root. michael@0: if ( nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) || michael@0: (selCommon == wsObj.mStartReasonNode) || michael@0: (rootElement == wsObj.mStartReasonNode) ) michael@0: { michael@0: stillLooking = false; michael@0: } michael@0: else michael@0: { michael@0: selStartNode = nsEditor::GetNodeLocation(wsObj.mStartReasonNode, michael@0: &selStartOffset); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: stillLooking = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: stillLooking = true; michael@0: // find next visible thingy after end of selection michael@0: if ((selEndNode!=selCommon) && (selEndNode!=rootElement)) michael@0: { michael@0: while (stillLooking) michael@0: { michael@0: nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset); michael@0: wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::br) { michael@0: if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode)) michael@0: { michael@0: stillLooking = false; michael@0: } michael@0: else michael@0: { michael@0: if (!firstBRParent) michael@0: { michael@0: firstBRParent = selEndNode; michael@0: firstBROffset = selEndOffset; michael@0: } michael@0: selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset); michael@0: ++selEndOffset; michael@0: } michael@0: } else if (wsType == WSType::thisBlock) { michael@0: // we want to keep looking up. But stop if we are crossing table element michael@0: // boundaries, or if we hit the root. michael@0: if ( nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) || michael@0: (selCommon == wsObj.mEndReasonNode) || michael@0: (rootElement == wsObj.mEndReasonNode) ) michael@0: { michael@0: stillLooking = false; michael@0: } michael@0: else michael@0: { michael@0: selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset); michael@0: ++selEndOffset; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: stillLooking = false; michael@0: } michael@0: } michael@0: } michael@0: // now set the selection to the new range michael@0: aSelection->Collapse(selStartNode, selStartOffset); michael@0: michael@0: // expand selection endpoint only if we didnt pass a br, michael@0: // or if we really needed to pass that br (ie, its block is now michael@0: // totally selected) michael@0: bool doEndExpansion = true; michael@0: if (firstBRParent) michael@0: { michael@0: // find block node containing br michael@0: nsCOMPtr brBlock = firstBRParent; michael@0: if (!IsBlockNode(brBlock)) michael@0: brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock); michael@0: bool nodeBefore=false, nodeAfter=false; michael@0: michael@0: // create a range that represents expanded selection michael@0: nsCOMPtr node = do_QueryInterface(selStartNode); michael@0: NS_ENSURE_STATE(node); michael@0: nsRefPtr range = new nsRange(node); michael@0: res = range->SetStart(selStartNode, selStartOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->SetEnd(selEndNode, selEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // check if block is entirely inside range michael@0: nsCOMPtr brContentBlock = do_QueryInterface(brBlock); michael@0: res = nsRange::CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter); michael@0: michael@0: // if block isn't contained, forgo grabbing the br in the expanded selection michael@0: if (nodeBefore || nodeAfter) michael@0: doEndExpansion = false; michael@0: } michael@0: if (doEndExpansion) michael@0: { michael@0: res = aSelection->Extend(selEndNode, selEndOffset); michael@0: } michael@0: else michael@0: { michael@0: // only expand to just before br michael@0: res = aSelection->Extend(firstBRParent, firstBROffset); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // NormalizeSelection: tweak non-collapsed selections to be more "natural". michael@0: // Idea here is to adjust selection endpoint so that they do not cross michael@0: // breaks or block boundaries unless something editable beyond that boundary michael@0: // is also selected. This adjustment makes it much easier for the various michael@0: // block operations to determine what nodes to act on. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::NormalizeSelection(nsISelection *inSelection) michael@0: { michael@0: NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // don't need to touch collapsed selections michael@0: if (inSelection->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t rangeCount; michael@0: nsresult res = inSelection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // we don't need to mess with cell selections, and we assume multirange selections are those. michael@0: if (rangeCount != 1) return NS_OK; michael@0: michael@0: nsCOMPtr range; michael@0: res = inSelection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr startNode, endNode; michael@0: int32_t startOffset, endOffset; michael@0: nsCOMPtr newStartNode, newEndNode; michael@0: int32_t newStartOffset, newEndOffset; michael@0: michael@0: res = range->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // adjusted values default to original values michael@0: newStartNode = startNode; michael@0: newStartOffset = startOffset; michael@0: newEndNode = endNode; michael@0: newEndOffset = endOffset; michael@0: michael@0: // some locals we need for whitespace code michael@0: nsCOMPtr someNode; michael@0: int32_t offset; michael@0: WSType wsType; michael@0: michael@0: // let the whitespace code do the heavy lifting michael@0: nsWSRunObject wsEndObj(mHTMLEditor, endNode, endOffset); michael@0: // is there any intervening visible whitespace? if so we can't push selection past that, michael@0: // it would visibly change maening of users selection michael@0: wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode), michael@0: &offset, &wsType); michael@0: if (wsType != WSType::text && wsType != WSType::normalWS) { michael@0: // eThisBlock and eOtherBlock conveniently distinquish cases michael@0: // of going "down" into a block and "up" out of a block. michael@0: if (wsEndObj.mStartReason == WSType::otherBlock) { michael@0: // endpoint is just after the close of a block. michael@0: nsCOMPtr child = mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, true); michael@0: if (child) michael@0: { michael@0: newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset); michael@0: ++newEndOffset; // offset *after* child michael@0: } michael@0: // else block is empty - we can leave selection alone here, i think. michael@0: } else if (wsEndObj.mStartReason == WSType::thisBlock) { michael@0: // endpoint is just after start of this block michael@0: nsCOMPtr child; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child)); michael@0: if (child) michael@0: { michael@0: newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset); michael@0: ++newEndOffset; // offset *after* child michael@0: } michael@0: // else block is empty - we can leave selection alone here, i think. michael@0: } else if (wsEndObj.mStartReason == WSType::br) { michael@0: // endpoint is just after break. lets adjust it to before it. michael@0: newEndNode = nsEditor::GetNodeLocation(wsEndObj.mStartReasonNode, michael@0: &newEndOffset); michael@0: } michael@0: } michael@0: michael@0: michael@0: // similar dealio for start of range michael@0: nsWSRunObject wsStartObj(mHTMLEditor, startNode, startOffset); michael@0: // is there any intervening visible whitespace? if so we can't push selection past that, michael@0: // it would visibly change maening of users selection michael@0: wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode), michael@0: &offset, &wsType); michael@0: if (wsType != WSType::text && wsType != WSType::normalWS) { michael@0: // eThisBlock and eOtherBlock conveniently distinquish cases michael@0: // of going "down" into a block and "up" out of a block. michael@0: if (wsStartObj.mEndReason == WSType::otherBlock) { michael@0: // startpoint is just before the start of a block. michael@0: nsCOMPtr child = mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, true); michael@0: if (child) michael@0: { michael@0: newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset); michael@0: } michael@0: // else block is empty - we can leave selection alone here, i think. michael@0: } else if (wsStartObj.mEndReason == WSType::thisBlock) { michael@0: // startpoint is just before end of this block michael@0: nsCOMPtr child; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child)); michael@0: if (child) michael@0: { michael@0: newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset); michael@0: } michael@0: // else block is empty - we can leave selection alone here, i think. michael@0: } else if (wsStartObj.mEndReason == WSType::br) { michael@0: // startpoint is just before a break. lets adjust it to after it. michael@0: newStartNode = nsEditor::GetNodeLocation(wsStartObj.mEndReasonNode, michael@0: &newStartOffset); michael@0: ++newStartOffset; // offset *after* break michael@0: } michael@0: } michael@0: michael@0: // there is a demented possiblity we have to check for. We might have a very strange selection michael@0: // that is not collapsed and yet does not contain any editable content, and satisfies some of the michael@0: // above conditions that cause tweaking. In this case we don't want to tweak the selection into michael@0: // a block it was never in, etc. There are a variety of strategies one might use to try to michael@0: // detect these cases, but I think the most straightforward is to see if the adjusted locations michael@0: // "cross" the old values: ie, new end before old start, or new start after old end. If so michael@0: // then just leave things alone. michael@0: michael@0: int16_t comp; michael@0: comp = nsContentUtils::ComparePoints(startNode, startOffset, michael@0: newEndNode, newEndOffset); michael@0: if (comp == 1) return NS_OK; // new end before old start michael@0: comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset, michael@0: endNode, endOffset); michael@0: if (comp == 1) return NS_OK; // new start after old end michael@0: michael@0: // otherwise set selection to new values. michael@0: inSelection->Collapse(newStartNode, newStartOffset); michael@0: inSelection->Extend(newEndNode, newEndOffset); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPromotedPoint: figure out where a start or end point for a block michael@0: // operation really is michael@0: void michael@0: nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode, michael@0: int32_t aOffset, michael@0: EditAction actionID, michael@0: nsCOMPtr* outNode, michael@0: int32_t* outOffset) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: MOZ_ASSERT(node && outNode && outOffset); michael@0: michael@0: // default values michael@0: *outNode = node->AsDOMNode(); michael@0: *outOffset = aOffset; michael@0: michael@0: // we do one thing for text actions, something else entirely for other michael@0: // actions michael@0: if (actionID == EditAction::insertText || michael@0: actionID == EditAction::insertIMEText || michael@0: actionID == EditAction::insertBreak || michael@0: actionID == EditAction::deleteText) { michael@0: bool isSpace, isNBSP; michael@0: nsCOMPtr content = do_QueryInterface(node), temp; michael@0: // for text actions, we want to look backwards (or forwards, as michael@0: // appropriate) for additional whitespace or nbsp's. We may have to act on michael@0: // these later even though they are outside of the initial selection. Even michael@0: // if they are in another node! michael@0: while (content) { michael@0: int32_t offset; michael@0: if (aWhere == kStart) { michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset, michael@0: &isSpace, &isNBSP, michael@0: getter_AddRefs(temp), &offset); michael@0: } else { michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: mHTMLEditor->IsNextCharInNodeWhitespace(content, *outOffset, michael@0: &isSpace, &isNBSP, michael@0: getter_AddRefs(temp), &offset); michael@0: } michael@0: if (isSpace || isNBSP) { michael@0: content = temp; michael@0: *outOffset = offset; michael@0: } else { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: *outNode = content->AsDOMNode(); michael@0: return; michael@0: } michael@0: michael@0: int32_t offset = aOffset; michael@0: michael@0: // else not a text section. In this case we want to see if we should grab michael@0: // any adjacent inline nodes and/or parents and other ancestors michael@0: if (aWhere == kStart) { michael@0: // some special casing for text nodes michael@0: if (node->IsNodeOfType(nsINode::eTEXT)) { michael@0: if (!node->GetParentNode()) { michael@0: // Okay, can't promote any further michael@0: return; michael@0: } michael@0: offset = node->GetParentNode()->IndexOf(node); michael@0: node = node->GetParentNode(); michael@0: } michael@0: michael@0: // look back through any further inline nodes that aren't across a
    michael@0: // from us, and that are enclosed in the same block. michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nsCOMPtr priorNode = michael@0: mHTMLEditor->GetPriorHTMLNode(node, offset, true); michael@0: michael@0: while (priorNode && priorNode->GetParentNode() && michael@0: mHTMLEditor && !mHTMLEditor->IsVisBreak(priorNode->AsDOMNode()) && michael@0: !IsBlockNode(priorNode->AsDOMNode())) { michael@0: offset = priorNode->GetParentNode()->IndexOf(priorNode); michael@0: node = priorNode->GetParentNode(); michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); michael@0: } michael@0: michael@0: // finding the real start for this point. look up the tree for as long as michael@0: // we are the first node in the container, and as long as we haven't hit michael@0: // the body node. michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nsCOMPtr nearNode = michael@0: mHTMLEditor->GetPriorHTMLNode(node, offset, true); michael@0: while (!nearNode && node->Tag() != nsGkAtoms::body && michael@0: node->GetParentNode()) { michael@0: // some cutoffs are here: we don't need to also include them in the michael@0: // aWhere == kEnd case. as long as they are in one or the other it will michael@0: // work. special case for outdent: don't keep looking up if we have michael@0: // found a blockquote element to act on michael@0: if (actionID == EditAction::outdent && michael@0: node->Tag() == nsGkAtoms::blockquote) { michael@0: break; michael@0: } michael@0: michael@0: int32_t parentOffset = node->GetParentNode()->IndexOf(node); michael@0: nsCOMPtr parent = node->GetParentNode(); michael@0: michael@0: // Don't walk past the editable section. Note that we need to check michael@0: // before walking up to a parent because we need to return the parent michael@0: // object, so the parent itself might not be in the editable area, but michael@0: // it's OK if we're not performing a block-level action. michael@0: bool blockLevelAction = actionID == EditAction::indent || michael@0: actionID == EditAction::outdent || michael@0: actionID == EditAction::align || michael@0: actionID == EditAction::makeBasicBlock; michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) && michael@0: (blockLevelAction || !mHTMLEditor || michael@0: !mHTMLEditor->IsDescendantOfEditorRoot(node))) { michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: break; michael@0: } michael@0: michael@0: node = parent; michael@0: offset = parentOffset; michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true); michael@0: } michael@0: *outNode = node->AsDOMNode(); michael@0: *outOffset = offset; michael@0: return; michael@0: } michael@0: michael@0: // aWhere == kEnd michael@0: // some special casing for text nodes michael@0: if (node->IsNodeOfType(nsINode::eTEXT)) { michael@0: if (!node->GetParentNode()) { michael@0: // Okay, can't promote any further michael@0: return; michael@0: } michael@0: // want to be after the text node michael@0: offset = 1 + node->GetParentNode()->IndexOf(node); michael@0: node = node->GetParentNode(); michael@0: } michael@0: michael@0: // look ahead through any further inline nodes that aren't across a
    from michael@0: // us, and that are enclosed in the same block. michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nsCOMPtr nextNode = michael@0: mHTMLEditor->GetNextHTMLNode(node, offset, true); michael@0: michael@0: while (nextNode && !IsBlockNode(nextNode->AsDOMNode()) && michael@0: nextNode->GetParentNode()) { michael@0: offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode); michael@0: node = nextNode->GetParentNode(); michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) { michael@0: break; michael@0: } michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); michael@0: } michael@0: michael@0: // finding the real end for this point. look up the tree for as long as we michael@0: // are the last node in the container, and as long as we haven't hit the body michael@0: // node. michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nsCOMPtr nearNode = michael@0: mHTMLEditor->GetNextHTMLNode(node, offset, true); michael@0: while (!nearNode && node->Tag() != nsGkAtoms::body && michael@0: node->GetParentNode()) { michael@0: int32_t parentOffset = node->GetParentNode()->IndexOf(node); michael@0: nsCOMPtr parent = node->GetParentNode(); michael@0: michael@0: // Don't walk past the editable section. Note that we need to check before michael@0: // walking up to a parent because we need to return the parent object, so michael@0: // the parent itself might not be in the editable area, but it's OK. michael@0: if ((!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(node)) && michael@0: (!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(parent))) { michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: break; michael@0: } michael@0: michael@0: node = parent; michael@0: // we want to be AFTER nearNode michael@0: offset = parentOffset + 1; michael@0: NS_ENSURE_TRUE(mHTMLEditor, /* void */); michael@0: nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true); michael@0: } michael@0: *outNode = node->AsDOMNode(); michael@0: *outOffset = offset; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPromotedRanges: run all the selection range endpoint through michael@0: // GetPromotedPoint() michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection, michael@0: nsCOMArray &outArrayOfRanges, michael@0: EditAction inOperationType) michael@0: { michael@0: NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER); michael@0: michael@0: int32_t rangeCount; michael@0: nsresult res = inSelection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: int32_t i; michael@0: nsCOMPtr selectionRange; michael@0: nsCOMPtr opRange; michael@0: michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // clone range so we don't muck with actual selection ranges michael@0: res = selectionRange->CloneRange(getter_AddRefs(opRange)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // make a new adjusted range to represent the appropriate block content. michael@0: // The basic idea is to push out the range endpoints michael@0: // to truly enclose the blocks that we will affect. michael@0: // This call alters opRange. michael@0: res = PromoteRange(opRange, inOperationType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // stuff new opRange into array michael@0: outArrayOfRanges.AppendObject(opRange); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // PromoteRange: expand a range to include any parents for which all michael@0: // editable children are already in range. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, michael@0: EditAction inOperationType) michael@0: { michael@0: NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr startNode, endNode; michael@0: int32_t startOffset, endOffset; michael@0: michael@0: res = inRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // MOOSE major hack: michael@0: // GetPromotedPoint doesn't really do the right thing for collapsed ranges michael@0: // inside block elements that contain nothing but a solo
    . It's easier michael@0: // to put a workaround here than to revamp GetPromotedPoint. :-( michael@0: if ( (startNode == endNode) && (startOffset == endOffset)) michael@0: { michael@0: nsCOMPtr block; michael@0: if (IsBlockNode(startNode)) { michael@0: block = startNode; michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: block = mHTMLEditor->GetBlockNodeParent(startNode); michael@0: } michael@0: if (block) michael@0: { michael@0: bool bIsEmptyNode = false; michael@0: // check for the editing host michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIContent *rootContent = mHTMLEditor->GetActiveEditingHost(); michael@0: nsCOMPtr rootNode = do_QueryInterface(rootContent); michael@0: nsCOMPtr blockNode = do_QueryInterface(block); michael@0: NS_ENSURE_TRUE(rootNode && blockNode, NS_ERROR_UNEXPECTED); michael@0: // Make sure we don't go higher than our root element in the content tree michael@0: if (!nsContentUtils::ContentIsDescendantOf(rootNode, blockNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false); michael@0: } michael@0: if (bIsEmptyNode) michael@0: { michael@0: uint32_t numChildren; michael@0: nsEditor::GetLengthOfDOMNode(block, numChildren); michael@0: startNode = block; michael@0: endNode = block; michael@0: startOffset = 0; michael@0: endOffset = numChildren; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // make a new adjusted range to represent the appropriate block content. michael@0: // this is tricky. the basic idea is to push out the range endpoints michael@0: // to truly enclose the blocks that we will affect michael@0: michael@0: nsCOMPtr opStartNode; michael@0: nsCOMPtr opEndNode; michael@0: int32_t opStartOffset, opEndOffset; michael@0: nsCOMPtr opRange; michael@0: michael@0: GetPromotedPoint(kStart, startNode, startOffset, inOperationType, michael@0: address_of(opStartNode), &opStartOffset); michael@0: GetPromotedPoint(kEnd, endNode, endOffset, inOperationType, michael@0: address_of(opEndNode), &opEndOffset); michael@0: michael@0: // Make sure that the new range ends up to be in the editable section. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) || michael@0: !mHTMLEditor || // Check again, since it may have gone away michael@0: !mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: res = inRange->SetStart(opStartNode, opStartOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = inRange->SetEnd(opEndNode, opEndOffset); michael@0: return res; michael@0: } michael@0: michael@0: class nsUniqueFunctor : public nsBoolDomIterFunctor michael@0: { michael@0: public: michael@0: nsUniqueFunctor(nsCOMArray &aArray) : mArray(aArray) michael@0: { michael@0: } michael@0: virtual bool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers michael@0: { michael@0: return mArray.IndexOf(aNode) < 0; michael@0: } michael@0: michael@0: private: michael@0: nsCOMArray &mArray; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNodesForOperation: run through the ranges in the array and construct michael@0: // a new array of nodes to be acted on. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetNodesForOperation(nsCOMArray& inArrayOfRanges, michael@0: nsCOMArray& outArrayOfNodes, michael@0: EditAction inOperationType, michael@0: bool aDontTouchContent) michael@0: { michael@0: int32_t rangeCount = inArrayOfRanges.Count(); michael@0: michael@0: int32_t i; michael@0: nsCOMPtr opRange; michael@0: michael@0: nsresult res = NS_OK; michael@0: michael@0: // bust up any inlines that cross our range endpoints, michael@0: // but only if we are allowed to touch content. michael@0: michael@0: if (!aDontTouchContent) michael@0: { michael@0: nsTArray > rangeItemArray; michael@0: if (!rangeItemArray.AppendElements(rangeCount)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: NS_ASSERTION(static_cast(rangeCount) == rangeItemArray.Length(), michael@0: "How did that happen?"); michael@0: michael@0: // first register ranges for special editor gravity michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: opRange = inArrayOfRanges[0]; michael@0: rangeItemArray[i] = new nsRangeStore(); michael@0: rangeItemArray[i]->StoreRange(opRange); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]); michael@0: inArrayOfRanges.RemoveObjectAt(0); michael@0: } michael@0: // now bust up inlines. Safe to start at rangeCount-1, since we michael@0: // asserted we have enough items above. michael@0: for (i = rangeCount-1; i >= 0 && NS_SUCCEEDED(res); i--) michael@0: { michael@0: res = BustUpInlinesAtRangeEndpoints(*rangeItemArray[i]); michael@0: } michael@0: // then unregister the ranges michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: nsRangeStore* item = rangeItemArray[i]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mRangeUpdater.DropRangeItem(item); michael@0: nsRefPtr range; michael@0: nsresult res2 = item->GetRange(getter_AddRefs(range)); michael@0: opRange = range; michael@0: if (NS_FAILED(res2) && NS_SUCCEEDED(res)) { michael@0: // Remember the failure, but keep going so we make sure to unregister michael@0: // all our range items. michael@0: res = res2; michael@0: } michael@0: inArrayOfRanges.AppendObject(opRange); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // gather up a list of all the nodes michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: opRange = inArrayOfRanges[i]; michael@0: michael@0: nsDOMSubtreeIterator iter; michael@0: res = iter.Init(opRange); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (outArrayOfNodes.Count() == 0) { michael@0: nsTrivialFunctor functor; michael@0: res = iter.AppendList(functor, outArrayOfNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else { michael@0: // We don't want duplicates in outArrayOfNodes, so we use an michael@0: // iterator/functor that only return nodes that are not already in michael@0: // outArrayOfNodes. michael@0: nsCOMArray nodes; michael@0: nsUniqueFunctor functor(outArrayOfNodes); michael@0: res = iter.AppendList(functor, nodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!outArrayOfNodes.AppendObjects(nodes)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: michael@0: // certain operations should not act on li's and td's, but rather inside michael@0: // them. alter the list as needed michael@0: if (inOperationType == EditAction::makeBasicBlock) { michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr node = outArrayOfNodes[i]; michael@0: if (nsHTMLEditUtils::IsListItem(node)) michael@0: { michael@0: int32_t j=i; michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: res = GetInnerContent(node, outArrayOfNodes, &j); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: // indent/outdent already do something special for list items, but michael@0: // we still need to make sure we don't act on table elements michael@0: else if (inOperationType == EditAction::outdent || michael@0: inOperationType == EditAction::indent || michael@0: inOperationType == EditAction::setAbsolutePosition) { michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr node = outArrayOfNodes[i]; michael@0: if (nsHTMLEditUtils::IsTableElementButNotTable(node)) michael@0: { michael@0: int32_t j=i; michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: res = GetInnerContent(node, outArrayOfNodes, &j); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: // outdent should look inside of divs. michael@0: if (inOperationType == EditAction::outdent && michael@0: (!mHTMLEditor || !mHTMLEditor->IsCSSEnabled())) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr node = outArrayOfNodes[i]; michael@0: if (nsHTMLEditUtils::IsDiv(node)) michael@0: { michael@0: int32_t j=i; michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: res = GetInnerContent(node, outArrayOfNodes, &j, false, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: // post process the list to break up inline containers that contain br's. michael@0: // but only for operations that might care, like making lists or para's... michael@0: if (inOperationType == EditAction::makeBasicBlock || michael@0: inOperationType == EditAction::makeList || michael@0: inOperationType == EditAction::align || michael@0: inOperationType == EditAction::setAbsolutePosition || michael@0: inOperationType == EditAction::indent || michael@0: inOperationType == EditAction::outdent) { michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr node = outArrayOfNodes[i]; michael@0: if (!aDontTouchContent && IsInlineNode(node) && michael@0: (!mHTMLEditor || mHTMLEditor->IsContainer(node)) && michael@0: (!mHTMLEditor || !mHTMLEditor->IsTextNode(node))) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMArray arrayOfInlines; michael@0: res = BustUpInlinesAtBRs(node, arrayOfInlines); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // put these nodes in outArrayOfNodes, replacing the current node michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i); michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetChildNodesForOperation: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode, michael@0: nsCOMArray& outArrayOfNodes) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: for (nsIContent* child = node->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: nsIDOMNode* childNode = child->AsDOMNode(); michael@0: if (!outArrayOfNodes.AppendObject(childNode)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetListActionNodes: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetListActionNodes(nsCOMArray &outArrayOfNodes, michael@0: bool aEntireList, michael@0: bool aDontTouchContent) michael@0: { michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: Selection* sel = static_cast(selection.get()); michael@0: NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); michael@0: // added this in so that ui code can ask to change an entire list, even if selection michael@0: // is only in part of it. used by list item dialog. michael@0: if (aEntireList) michael@0: { michael@0: uint32_t rangeCount = sel->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = sel->GetRangeAt(rangeIdx); michael@0: nsCOMPtr commonParent, parent, tmp; michael@0: range->GetCommonAncestorContainer(getter_AddRefs(commonParent)); michael@0: if (commonParent) michael@0: { michael@0: parent = commonParent; michael@0: while (parent) michael@0: { michael@0: if (nsHTMLEditUtils::IsList(parent)) michael@0: { michael@0: outArrayOfNodes.AppendObject(parent); michael@0: break; michael@0: } michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: parent = tmp; michael@0: } michael@0: } michael@0: } michael@0: // if we didn't find any nodes this way, then try the normal way. perhaps the michael@0: // selection spans multiple lists but with no common list parent. michael@0: if (outArrayOfNodes.Count()) return NS_OK; michael@0: } michael@0: michael@0: { michael@0: // We don't like other people messing with our selection! michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: michael@0: // contruct a list of nodes to act on. michael@0: res = GetNodesFromSelection(selection, EditAction::makeList, michael@0: outArrayOfNodes, aDontTouchContent); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // pre process our list of nodes... michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr testNode = outArrayOfNodes[i]; michael@0: michael@0: // Remove all non-editable nodes. Leave them be. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(testNode)) michael@0: { michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: } michael@0: michael@0: // scan for table elements and divs. If we find table elements other than table, michael@0: // replace it with a list of any editable non-table content. michael@0: if (nsHTMLEditUtils::IsTableElementButNotTable(testNode)) michael@0: { michael@0: int32_t j=i; michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: res = GetInnerContent(testNode, outArrayOfNodes, &j, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // if there is only one node in the array, and it is a list, div, or blockquote, michael@0: // then look inside of it until we find inner list or content. michael@0: res = LookInsideDivBQandList(outArrayOfNodes); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // LookInsideDivBQandList: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray& aNodeArray) michael@0: { michael@0: // if there is only one node in the array, and it is a list, div, or blockquote, michael@0: // then look inside of it until we find inner list or content. michael@0: int32_t listCount = aNodeArray.Count(); michael@0: if (listCount != 1) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr curNode = do_QueryInterface(aNodeArray[0]); michael@0: NS_ENSURE_STATE(curNode); michael@0: michael@0: while (curNode->IsElement() && michael@0: (curNode->AsElement()->IsHTML(nsGkAtoms::div) || michael@0: nsHTMLEditUtils::IsList(curNode) || michael@0: curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { michael@0: // dive as long as there is only one child, and it is a list, div, blockquote michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: uint32_t numChildren = mHTMLEditor->CountEditableChildren(curNode); michael@0: if (numChildren != 1) { michael@0: break; michael@0: } michael@0: michael@0: // keep diving michael@0: // XXX One would expect to dive into the one editable node. michael@0: nsIContent* tmp = curNode->GetFirstChild(); michael@0: if (!tmp->IsElement()) { michael@0: break; michael@0: } michael@0: michael@0: dom::Element* element = tmp->AsElement(); michael@0: if (!element->IsHTML(nsGkAtoms::div) && michael@0: !nsHTMLEditUtils::IsList(element) && michael@0: !element->IsHTML(nsGkAtoms::blockquote)) { michael@0: break; michael@0: } michael@0: michael@0: // check editablility XXX floppy moose michael@0: curNode = tmp; michael@0: } michael@0: michael@0: // we've found innermost list/blockquote/div: michael@0: // replace the one node in the array with these nodes michael@0: aNodeArray.RemoveObjectAt(0); michael@0: if (curNode->IsElement() && michael@0: (curNode->AsElement()->IsHTML(nsGkAtoms::div) || michael@0: curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) { michael@0: int32_t j = 0; michael@0: return GetInnerContent(curNode->AsDOMNode(), aNodeArray, &j, false, false); michael@0: } michael@0: michael@0: aNodeArray.AppendObject(curNode->AsDOMNode()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetDefinitionListItemTypes: michael@0: // michael@0: void michael@0: nsHTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement, bool* aDT, bool* aDD) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: MOZ_ASSERT(aElement->IsHTML(nsGkAtoms::dl)); michael@0: MOZ_ASSERT(aDT); michael@0: MOZ_ASSERT(aDD); michael@0: michael@0: *aDT = *aDD = false; michael@0: for (nsIContent* child = aElement->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: if (child->IsHTML(nsGkAtoms::dt)) { michael@0: *aDT = true; michael@0: } else if (child->IsHTML(nsGkAtoms::dd)) { michael@0: *aDD = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetParagraphFormatNodes: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray& outArrayOfNodes, michael@0: bool aDontTouchContent) michael@0: { michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // contruct a list of nodes to act on. michael@0: res = GetNodesFromSelection(selection, EditAction::makeBasicBlock, michael@0: outArrayOfNodes, aDontTouchContent); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // pre process our list of nodes... michael@0: int32_t listCount = outArrayOfNodes.Count(); michael@0: int32_t i; michael@0: for (i=listCount-1; i>=0; i--) michael@0: { michael@0: nsCOMPtr testNode = outArrayOfNodes[i]; michael@0: michael@0: // Remove all non-editable nodes. Leave them be. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(testNode)) michael@0: { michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: } michael@0: michael@0: // scan for table elements. If we find table elements other than table, michael@0: // replace it with a list of any editable non-table content. Ditto for list elements. michael@0: if (nsHTMLEditUtils::IsTableElement(testNode) || michael@0: nsHTMLEditUtils::IsList(testNode) || michael@0: nsHTMLEditUtils::IsListItem(testNode) ) michael@0: { michael@0: int32_t j=i; michael@0: outArrayOfNodes.RemoveObjectAt(i); michael@0: res = GetInnerContent(testNode, outArrayOfNodes, &j); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // BustUpInlinesAtRangeEndpoints: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item) michael@0: { michael@0: nsresult res = NS_OK; michael@0: bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset)); michael@0: michael@0: nsCOMPtr endInline = GetHighestInlineParent(item.endNode); michael@0: michael@0: // if we have inline parents above range endpoints, split them michael@0: if (endInline && !isCollapsed) michael@0: { michael@0: nsCOMPtr resultEndNode; michael@0: int32_t resultEndOffset; michael@0: endInline->GetParentNode(getter_AddRefs(resultEndNode)); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset, michael@0: &resultEndOffset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // reset range michael@0: item.endNode = resultEndNode; item.endOffset = resultEndOffset; michael@0: } michael@0: michael@0: nsCOMPtr startInline = GetHighestInlineParent(item.startNode); michael@0: michael@0: if (startInline) michael@0: { michael@0: nsCOMPtr resultStartNode; michael@0: int32_t resultStartOffset; michael@0: startInline->GetParentNode(getter_AddRefs(resultStartNode)); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset, michael@0: &resultStartOffset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // reset range michael@0: item.startNode = resultStartNode; item.startOffset = resultStartOffset; michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // BustUpInlinesAtBRs: michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode, michael@0: nsCOMArray& outArrayOfNodes) michael@0: { michael@0: NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // first step is to build up a list of all the break nodes inside michael@0: // the inline container. michael@0: nsCOMArray arrayOfBreaks; michael@0: nsBRNodeFunctor functor; michael@0: nsDOMIterator iter; michael@0: nsresult res = iter.Init(inNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = iter.AppendList(functor, arrayOfBreaks); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if there aren't any breaks, just put inNode itself in the array michael@0: int32_t listCount = arrayOfBreaks.Count(); michael@0: if (!listCount) michael@0: { michael@0: if (!outArrayOfNodes.AppendObject(inNode)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: else michael@0: { michael@0: // else we need to bust up inNode along all the breaks michael@0: nsCOMPtr breakNode; michael@0: nsCOMPtr inlineParentNode; michael@0: nsCOMPtr leftNode; michael@0: nsCOMPtr rightNode; michael@0: nsCOMPtr splitDeepNode = inNode; michael@0: nsCOMPtr splitParentNode; michael@0: int32_t splitOffset, resultOffset, i; michael@0: inNode->GetParentNode(getter_AddRefs(inlineParentNode)); michael@0: michael@0: for (i=0; i< listCount; i++) michael@0: { michael@0: breakNode = arrayOfBreaks[i]; michael@0: NS_ENSURE_TRUE(breakNode, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER); michael@0: splitParentNode = nsEditor::GetNodeLocation(breakNode, &splitOffset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset, michael@0: &resultOffset, false, address_of(leftNode), address_of(rightNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // put left node in node list michael@0: if (leftNode) michael@0: { michael@0: // might not be a left node. a break might have been at the very michael@0: // beginning of inline container, in which case splitnodedeep michael@0: // would not actually split anything michael@0: if (!outArrayOfNodes.AppendObject(leftNode)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // move break outside of container and also put in node list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!outArrayOfNodes.AppendObject(breakNode)) michael@0: return NS_ERROR_FAILURE; michael@0: // now rightNode becomes the new node to split michael@0: splitDeepNode = rightNode; michael@0: } michael@0: // now tack on remaining rightNode, if any, to the list michael@0: if (rightNode) michael@0: { michael@0: if (!outArrayOfNodes.AppendObject(rightNode)) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsCOMPtr michael@0: nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, nullptr); michael@0: if (IsBlockNode(aNode)) return nullptr; michael@0: nsCOMPtr inlineNode, node=aNode; michael@0: michael@0: while (node && IsInlineNode(node)) michael@0: { michael@0: inlineNode = node; michael@0: inlineNode->GetParentNode(getter_AddRefs(node)); michael@0: } michael@0: return inlineNode; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNodesFromPoint: given a particular operation, construct a list michael@0: // of nodes from a point that will be operated on. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetNodesFromPoint(::DOMPoint point, michael@0: EditAction operation, michael@0: nsCOMArray &arrayOfNodes, michael@0: bool dontTouchContent) michael@0: { michael@0: nsresult res; michael@0: michael@0: // get our point michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: point.GetPoint(node, offset); michael@0: michael@0: // use it to make a range michael@0: nsCOMPtr nativeNode = do_QueryInterface(node); michael@0: NS_ENSURE_STATE(nativeNode); michael@0: nsRefPtr range = new nsRange(nativeNode); michael@0: res = range->SetStart(node, offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: /* SetStart() will also set the end for this new range michael@0: res = range->SetEnd(node, offset); michael@0: NS_ENSURE_SUCCESS(res, res); */ michael@0: michael@0: // expand the range to include adjacent inlines michael@0: res = PromoteRange(range, operation); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // make array of ranges michael@0: nsCOMArray arrayOfRanges; michael@0: michael@0: // stuff new opRange into array michael@0: arrayOfRanges.AppendObject(range); michael@0: michael@0: // use these ranges to contruct a list of nodes to act on. michael@0: res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNodesFromSelection: given a particular operation, construct a list michael@0: // of nodes from the selection that will be operated on. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection, michael@0: EditAction operation, michael@0: nsCOMArray& arrayOfNodes, michael@0: bool dontTouchContent) michael@0: { michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: michael@0: // promote selection ranges michael@0: nsCOMArray arrayOfRanges; michael@0: res = GetPromotedRanges(selection, arrayOfRanges, operation); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // use these ranges to contruct a list of nodes to act on. michael@0: res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // MakeTransitionList: detect all the transitions in the array, where a michael@0: // transition means that adjacent nodes in the array michael@0: // don't have the same parent. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::MakeTransitionList(nsCOMArray& inArrayOfNodes, michael@0: nsTArray &inTransitionArray) michael@0: { michael@0: uint32_t listCount = inArrayOfNodes.Count(); michael@0: inTransitionArray.EnsureLengthAtLeast(listCount); michael@0: uint32_t i; michael@0: nsCOMPtr prevElementParent; michael@0: nsCOMPtr curElementParent; michael@0: michael@0: for (i=0; iGetParentNode(getter_AddRefs(curElementParent)); michael@0: if (curElementParent != prevElementParent) michael@0: { michael@0: // different parents, or separated by
    : transition point michael@0: inTransitionArray[i] = true; michael@0: } michael@0: else michael@0: { michael@0: // same parents: these nodes grew up together michael@0: inTransitionArray[i] = false; michael@0: } michael@0: prevElementParent = curElementParent; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /******************************************************** michael@0: * main implementation methods michael@0: ********************************************************/ michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsInListItem: if aNode is the descendant of a listitem, return that li. michael@0: // But table element boundaries are stoppers on the search. michael@0: // Also stops on the active editor host (contenteditable). michael@0: // Also test if aNode is an li itself. michael@0: // michael@0: already_AddRefed michael@0: nsHTMLEditRules::IsInListItem(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: nsCOMPtr retval = do_QueryInterface(IsInListItem(node)); michael@0: return retval.forget(); michael@0: } michael@0: michael@0: nsINode* michael@0: nsHTMLEditRules::IsInListItem(nsINode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, nullptr); michael@0: if (nsHTMLEditUtils::IsListItem(aNode)) { michael@0: return aNode; michael@0: } michael@0: michael@0: nsINode* parent = aNode->GetParentNode(); michael@0: while (parent && mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(parent) && michael@0: !nsHTMLEditUtils::IsTableElement(parent)) { michael@0: if (nsHTMLEditUtils::IsListItem(parent)) { michael@0: return parent; michael@0: } michael@0: parent = parent->GetParentNode(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ReturnInHeader: do the right thing for returns pressed in headers michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection, michael@0: nsIDOMNode *aHeader, michael@0: nsIDOMNode *aNode, michael@0: int32_t aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection && aHeader && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // remeber where the header is michael@0: int32_t offset; michael@0: nsCOMPtr headerParent = nsEditor::GetNodeLocation(aHeader, &offset); michael@0: michael@0: // get ws code to adjust any ws michael@0: nsCOMPtr selNode = aNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, michael@0: address_of(selNode), michael@0: &aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // split the header michael@0: int32_t newOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // if the leftand heading is empty, put a mozbr in it michael@0: nsCOMPtr prevItem; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem)); michael@0: if (prevItem && nsHTMLEditUtils::IsHeader(prevItem)) michael@0: { michael@0: bool bIsEmptyNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bIsEmptyNode) { michael@0: res = CreateMozBR(prevItem, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // if the new (righthand) header node is empty, delete it michael@0: bool isEmpty; michael@0: res = IsEmptyBlock(aHeader, &isEmpty, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isEmpty) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(aHeader); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // layout tells the caret to blink in a weird place michael@0: // if we don't place a break after the header. michael@0: nsCOMPtr sibling; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!sibling || !nsTextEditUtils::IsBreak(sibling)) michael@0: { michael@0: ClearCachedStyles(); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mTypeInState->ClearAllProps(); michael@0: michael@0: // create a paragraph michael@0: NS_NAMED_LITERAL_STRING(pType, "p"); michael@0: nsCOMPtr pNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // append a
    to it michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // set selection to before the break michael@0: res = aSelection->Collapse(pNode, 0); michael@0: } michael@0: else michael@0: { michael@0: headerParent = nsEditor::GetNodeLocation(sibling, &offset); michael@0: // put selection after break michael@0: res = aSelection->Collapse(headerParent,offset+1); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // put selection at front of righthand heading michael@0: res = aSelection->Collapse(aHeader,0); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ReturnInParagraph: do the right thing for returns pressed in paragraphs michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ReturnInParagraph(nsISelection* aSelection, michael@0: nsIDOMNode* aPara, michael@0: nsIDOMNode* aNode, michael@0: int32_t aOffset, michael@0: bool* aCancel, michael@0: bool* aHandled) michael@0: { michael@0: if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: *aCancel = false; michael@0: *aHandled = false; michael@0: nsresult res; michael@0: michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(aNode, &offset); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph(); michael@0: michael@0: bool newBRneeded = false; michael@0: nsCOMPtr sibling; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (aNode == aPara && doesCRCreateNewP) { michael@0: // we are at the edges of the block, newBRneeded not needed! michael@0: sibling = aNode; michael@0: } else if (mHTMLEditor->IsTextNode(aNode)) { michael@0: nsCOMPtr textNode = do_QueryInterface(aNode); michael@0: uint32_t strLength; michael@0: res = textNode->GetLength(&strLength); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // at beginning of text node? michael@0: if (!aOffset) { michael@0: // is there a BR prior to it? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); michael@0: if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || michael@0: nsTextEditUtils::HasMozAttr(sibling)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: newBRneeded = true; michael@0: } michael@0: } else if (aOffset == (int32_t)strLength) { michael@0: // we're at the end of text node... michael@0: // is there a BR after to it? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); michael@0: if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) || michael@0: nsTextEditUtils::HasMozAttr(sibling)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: newBRneeded = true; michael@0: offset++; michael@0: } michael@0: } else { michael@0: if (doesCRCreateNewP) { michael@0: nsCOMPtr tmp; michael@0: res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: aNode = tmp; michael@0: } michael@0: michael@0: newBRneeded = true; michael@0: offset++; michael@0: } michael@0: } else { michael@0: // not in a text node. michael@0: // is there a BR prior to it? michael@0: nsCOMPtr nearNode, selNode = aNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || michael@0: nsTextEditUtils::HasMozAttr(nearNode)) { michael@0: // is there a BR after it? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) || michael@0: nsTextEditUtils::HasMozAttr(nearNode)) { michael@0: newBRneeded = true; michael@0: } michael@0: } michael@0: if (!newBRneeded) { michael@0: sibling = nearNode; michael@0: } michael@0: } michael@0: if (newBRneeded) { michael@0: // if CR does not create a new P, default to BR creation michael@0: NS_ENSURE_TRUE(doesCRCreateNewP, NS_OK); michael@0: michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); michael@0: sibling = brNode; michael@0: } michael@0: nsCOMPtr selNode = aNode; michael@0: *aHandled = true; michael@0: return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // SplitParagraph: split a paragraph at selection point, possibly deleting a br michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara, michael@0: nsIDOMNode *aBRNode, michael@0: nsISelection *aSelection, michael@0: nsCOMPtr *aSelNode, michael@0: int32_t *aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER); michael@0: nsresult res = NS_OK; michael@0: michael@0: // split para michael@0: int32_t newOffset; michael@0: // get ws code to adjust any ws michael@0: nsCOMPtr leftPara, rightPara; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // split the paragraph michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, false, michael@0: address_of(leftPara), address_of(rightPara)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p) michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsVisBreak(aBRNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(aBRNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // remove ID attribute on the paragraph we just created michael@0: nsCOMPtr rightElt = do_QueryInterface(rightPara); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // check both halves of para to see if we need mozBR michael@0: res = InsertMozBRIfNeeded(leftPara); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertMozBRIfNeeded(rightPara); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // selection to beginning of right hand para; michael@0: // look inside any containers that are up front. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr child = mHTMLEditor->GetLeftmostChild(rightPara, true); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsTextNode(child) || !mHTMLEditor || michael@0: mHTMLEditor->IsContainer(child)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: aSelection->Collapse(child,0); michael@0: } michael@0: else michael@0: { michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(child, &offset); michael@0: aSelection->Collapse(parent,offset); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ReturnInListItem: do the right thing for returns pressed in list items michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection, michael@0: nsIDOMNode *aListItem, michael@0: nsIDOMNode *aNode, michael@0: int32_t aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection && aListItem && aNode, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selection(aSelection); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr listitem; michael@0: michael@0: // sanity check michael@0: NS_PRECONDITION(true == nsHTMLEditUtils::IsListItem(aListItem), michael@0: "expected a list item and didn't get one"); michael@0: michael@0: // get the listitem parent and the active editing host. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIContent* rootContent = mHTMLEditor->GetActiveEditingHost(); michael@0: nsCOMPtr rootNode = do_QueryInterface(rootContent); michael@0: int32_t itemOffset; michael@0: nsCOMPtr list = nsEditor::GetNodeLocation(aListItem, &itemOffset); michael@0: michael@0: // if we are in an empty listitem, then we want to pop up out of the list michael@0: // but only if prefs says it's ok and if the parent isn't the active editing host. michael@0: bool isEmpty; michael@0: res = IsEmptyBlock(aListItem, &isEmpty, true, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isEmpty && (rootNode != list) && mReturnInEmptyLIKillsList) michael@0: { michael@0: // get the list offset now -- before we might eventually split the list michael@0: int32_t offset; michael@0: nsCOMPtr listparent = nsEditor::GetNodeLocation(list, &offset); michael@0: michael@0: // are we the last list item in the list? michael@0: bool bIsLast; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!bIsLast) michael@0: { michael@0: // we need to split the list! michael@0: nsCOMPtr tempNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // are we in a sublist? michael@0: if (nsHTMLEditUtils::IsList(listparent)) //in a sublist michael@0: { michael@0: // if so, move this list item out of this list and into the grandparent list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = aSelection->Collapse(aListItem,0); michael@0: } michael@0: else michael@0: { michael@0: // otherwise kill this listitem michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(aListItem); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // time to insert a paragraph michael@0: NS_NAMED_LITERAL_STRING(pType, "p"); michael@0: nsCOMPtr pNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(pType, listparent, offset+1, getter_AddRefs(pNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // append a
    to it michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // set selection to before the break michael@0: res = aSelection->Collapse(pNode, 0); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: // else we want a new list item at the same list level. michael@0: // get ws code to adjust any ws michael@0: nsCOMPtr selNode = aNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // now split list item michael@0: int32_t newOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // hack: until I can change the damaged doc range code back to being michael@0: // extra inclusive, I have to manually detect certain list items that michael@0: // may be left empty. michael@0: nsCOMPtr prevItem; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem)); michael@0: michael@0: if (prevItem && nsHTMLEditUtils::IsListItem(prevItem)) michael@0: { michael@0: bool bIsEmptyNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bIsEmptyNode) { michael@0: res = CreateMozBR(prevItem, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bIsEmptyNode) michael@0: { michael@0: nsCOMPtr nodeAtom = nsEditor::GetTag(aListItem); michael@0: if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt) michael@0: { michael@0: int32_t itemOffset; michael@0: nsCOMPtr list = nsEditor::GetNodeLocation(aListItem, &itemOffset); michael@0: michael@0: nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt")); michael@0: nsCOMPtr newListItem; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mEditor->DeleteNode(aListItem); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: return aSelection->Collapse(newListItem, 0); michael@0: } michael@0: michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (brNode) michael@0: { michael@0: int32_t offset; michael@0: nsCOMPtr brParent = nsEditor::GetNodeLocation(brNode, &offset); michael@0: return aSelection->Collapse(brParent, offset); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsWSRunObject wsObj(mHTMLEditor, aListItem, 0); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset = 0; michael@0: WSType wsType; michael@0: wsObj.NextVisibleNode(aListItem, 0, address_of(visNode), michael@0: &visOffset, &wsType); michael@0: if (wsType == WSType::special || wsType == WSType::br || michael@0: nsHTMLEditUtils::IsHR(visNode)) { michael@0: int32_t offset; michael@0: nsCOMPtr parent = nsEditor::GetNodeLocation(visNode, &offset); michael@0: return aSelection->Collapse(parent, offset); michael@0: } michael@0: else michael@0: { michael@0: return aSelection->Collapse(visNode, visOffset); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: res = aSelection->Collapse(aListItem,0); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // MakeBlockquote: put the list of nodes into one or more blockquotes. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::MakeBlockquote(nsCOMArray& arrayOfNodes) michael@0: { michael@0: // the idea here is to put the nodes into a minimal number of michael@0: // blockquotes. When the user blockquotes something, they expect michael@0: // one blockquote. That may not be possible (for instance, if they michael@0: // have two table cells selected, you need two blockquotes inside the cells). michael@0: michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr curNode, curParent, curBlock, newBlock; michael@0: int32_t offset; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: michael@0: nsCOMPtr prevParent; michael@0: michael@0: int32_t i; michael@0: for (i=0; i childArray; michael@0: res = GetChildNodesForOperation(curNode, childArray); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = MakeBlockquote(childArray); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // if the node has different parent than previous node, michael@0: // further nodes in a new parent michael@0: if (prevParent) michael@0: { michael@0: nsCOMPtr temp; michael@0: curNode->GetParentNode(getter_AddRefs(temp)); michael@0: if (temp != prevParent) michael@0: { michael@0: curBlock = 0; // forget any previous blockquote node we were using michael@0: prevParent = temp; michael@0: } michael@0: } michael@0: else michael@0: michael@0: { michael@0: curNode->GetParentNode(getter_AddRefs(prevParent)); michael@0: } michael@0: michael@0: // if no curBlock, make one michael@0: if (!curBlock) michael@0: { michael@0: NS_NAMED_LITERAL_STRING(quoteType, "blockquote"); michael@0: res = SplitAsNeeded("eType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curBlock; michael@0: // note: doesn't matter if we set mNewBlock multiple times. michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curBlock, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // RemoveBlockStyle: make the nodes have no special block type. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::RemoveBlockStyle(nsCOMArray& arrayOfNodes) michael@0: { michael@0: // intent of this routine is to be used for converting to/from michael@0: // headers, paragraphs, pre, and address. Those blocks michael@0: // that pretty much just contain inline things... michael@0: michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr curBlock, firstNode, lastNode; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (int32_t i = 0; i < listCount; ++i) { michael@0: // get the node to act on, and its location michael@0: nsCOMPtr curNode = arrayOfNodes[i]; michael@0: michael@0: nsCOMPtr curElement = do_QueryInterface(curNode); michael@0: michael@0: // if curNode is a address, p, header, address, or pre, remove it michael@0: if (curElement && nsHTMLEditUtils::IsFormatNode(curElement)) { michael@0: // process any partial progress saved michael@0: if (curBlock) michael@0: { michael@0: res = RemovePartOfBlock(curBlock, firstNode, lastNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlock = 0; firstNode = 0; lastNode = 0; michael@0: } michael@0: // remove curent block michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (curElement && michael@0: (curElement->IsHTML(nsGkAtoms::table) || michael@0: curElement->IsHTML(nsGkAtoms::tr) || michael@0: curElement->IsHTML(nsGkAtoms::tbody) || michael@0: curElement->IsHTML(nsGkAtoms::td) || michael@0: nsHTMLEditUtils::IsList(curElement) || michael@0: curElement->IsHTML(nsGkAtoms::li) || michael@0: curElement->IsHTML(nsGkAtoms::blockquote) || michael@0: curElement->IsHTML(nsGkAtoms::div))) { michael@0: // process any partial progress saved michael@0: if (curBlock) michael@0: { michael@0: res = RemovePartOfBlock(curBlock, firstNode, lastNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlock = 0; firstNode = 0; lastNode = 0; michael@0: } michael@0: // recursion time michael@0: nsCOMArray childArray; michael@0: res = GetChildNodesForOperation(curNode, childArray); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = RemoveBlockStyle(childArray); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (IsInlineNode(curNode)) michael@0: { michael@0: if (curBlock) michael@0: { michael@0: // if so, is this node a descendant? michael@0: if (nsEditorUtils::IsDescendantOf(curNode, curBlock)) michael@0: { michael@0: lastNode = curNode; michael@0: continue; // then we don't need to do anything different for this node michael@0: } michael@0: else michael@0: { michael@0: // otherwise, we have progressed beyond end of curBlock, michael@0: // so lets handle it now. We need to remove the portion of michael@0: // curBlock that contains [firstNode - lastNode]. michael@0: res = RemovePartOfBlock(curBlock, firstNode, lastNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlock = 0; firstNode = 0; lastNode = 0; michael@0: // fall out and handle curNode michael@0: } michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: curBlock = mHTMLEditor->GetBlockNodeParent(curNode); michael@0: if (nsHTMLEditUtils::IsFormatNode(curBlock)) michael@0: { michael@0: firstNode = curNode; michael@0: lastNode = curNode; michael@0: } michael@0: else michael@0: curBlock = 0; // not a block kind that we care about. michael@0: } michael@0: else michael@0: { // some node that is already sans block style. skip over it and michael@0: // process any partial progress saved michael@0: if (curBlock) michael@0: { michael@0: res = RemovePartOfBlock(curBlock, firstNode, lastNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlock = 0; firstNode = 0; lastNode = 0; michael@0: } michael@0: } michael@0: } michael@0: // process any partial progress saved michael@0: if (curBlock) michael@0: { michael@0: res = RemovePartOfBlock(curBlock, firstNode, lastNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curBlock = 0; firstNode = 0; lastNode = 0; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // ApplyBlockStyle: do whatever it takes to make the list of nodes into michael@0: // one or more blocks of type blockTag. michael@0: // michael@0: nsresult michael@0: nsHTMLEditRules::ApplyBlockStyle(nsCOMArray& arrayOfNodes, const nsAString *aBlockTag) michael@0: { michael@0: // intent of this routine is to be used for converting to/from michael@0: // headers, paragraphs, pre, and address. Those blocks michael@0: // that pretty much just contain inline things... michael@0: michael@0: NS_ENSURE_TRUE(aBlockTag, NS_ERROR_NULL_POINTER); michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr curNode, curParent, curBlock, newBlock; michael@0: int32_t offset; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: nsString tString(*aBlockTag);////MJUDGE SCC NEED HELP michael@0: michael@0: // Remove all non-editable nodes. Leave them be. michael@0: int32_t j; michael@0: for (j=listCount-1; j>=0; j--) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(arrayOfNodes[j])) michael@0: { michael@0: arrayOfNodes.RemoveObjectAt(j); michael@0: } michael@0: } michael@0: michael@0: // reset list count michael@0: listCount = arrayOfNodes.Count(); michael@0: michael@0: int32_t i; michael@0: for (i=0; iReplaceContainer(curNode, address_of(newBlock), *aBlockTag, michael@0: nullptr, nullptr, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (nsHTMLEditUtils::IsTable(curNode) || michael@0: (curNodeTag.EqualsLiteral("tbody")) || michael@0: (curNodeTag.EqualsLiteral("tr")) || michael@0: (curNodeTag.EqualsLiteral("td")) || michael@0: nsHTMLEditUtils::IsList(curNode) || michael@0: (curNodeTag.EqualsLiteral("li")) || michael@0: nsHTMLEditUtils::IsBlockquote(curNode) || michael@0: nsHTMLEditUtils::IsDiv(curNode)) michael@0: { michael@0: curBlock = 0; // forget any previous block used for previous inline nodes michael@0: // recursion time michael@0: nsCOMArray childArray; michael@0: res = GetChildNodesForOperation(curNode, childArray); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t childCount = childArray.Count(); michael@0: if (childCount) michael@0: { michael@0: res = ApplyBlockStyle(childArray, aBlockTag); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // make sure we can put a block here michael@0: res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr theBlock; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = theBlock; michael@0: } michael@0: } michael@0: michael@0: // if the node is a break, we honor it by putting further nodes in a new parent michael@0: else if (curNodeTag.EqualsLiteral("br")) michael@0: { michael@0: if (curBlock) michael@0: { michael@0: curBlock = 0; // forget any previous block used for previous inline nodes michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // the break is the first (or even only) node we encountered. Create a michael@0: // block for it. michael@0: res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curBlock; michael@0: // note: doesn't matter if we set mNewBlock multiple times. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curBlock, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: michael@0: // if curNode is inline, pull it into curBlock michael@0: // note: it's assumed that consecutive inline nodes in the michael@0: // arrayOfNodes are actually members of the same block parent. michael@0: // this happens to be true now as a side effect of how michael@0: // arrayOfNodes is contructed, but some additional logic should michael@0: // be added here if that should change michael@0: michael@0: else if (IsInlineNode(curNode)) michael@0: { michael@0: // if curNode is a non editable, drop it if we are going to
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       if (tString.LowerCaseEqualsLiteral("pre") 
    michael@0:         && (!mHTMLEditor->IsEditable(curNode)))
    michael@0:         continue; // do nothing to this block
    michael@0:       
    michael@0:       // if no curBlock, make one
    michael@0:       if (!curBlock)
    michael@0:       {
    michael@0:         res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
    michael@0:         NS_ENSURE_SUCCESS(res, res);
    michael@0:         NS_ENSURE_STATE(mHTMLEditor);
    michael@0:         res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
    michael@0:         NS_ENSURE_SUCCESS(res, res);
    michael@0:         // remember our new block for postprocessing
    michael@0:         mNewBlock = curBlock;
    michael@0:         // note: doesn't matter if we set mNewBlock multiple times.
    michael@0:       }
    michael@0:       
    michael@0:       // if curNode is a Break, replace it with a return if we are going to 
    michael@0:       // xxx floppy moose
    michael@0:  
    michael@0:       // this is a continuation of some inline nodes that belong together in
    michael@0:       // the same block item.  use curBlock
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
    michael@0:       NS_ENSURE_SUCCESS(res, res);
    michael@0:     }
    michael@0:   }
    michael@0:   return res;
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: ///////////////////////////////////////////////////////////////////////////
    michael@0: // SplitAsNeeded:  given a tag name, split inOutParent up to the point   
    michael@0: //                 where we can insert the tag.  Adjust inOutParent and
    michael@0: //                 inOutOffset to pint to new location for tag.
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag, 
    michael@0:                                nsCOMPtr *inOutParent,
    michael@0:                                int32_t *inOutOffset)
    michael@0: {
    michael@0:   NS_ENSURE_TRUE(aTag && inOutParent && inOutOffset, NS_ERROR_NULL_POINTER);
    michael@0:   NS_ENSURE_TRUE(*inOutParent, NS_ERROR_NULL_POINTER);
    michael@0:   nsCOMPtr tagParent, temp, splitNode, parent = *inOutParent;
    michael@0:   nsresult res = NS_OK;
    michael@0:   nsCOMPtr tagAtom = do_GetAtom(*aTag);
    michael@0:    
    michael@0:   // check that we have a place that can legally contain the tag
    michael@0:   while (!tagParent)
    michael@0:   {
    michael@0:     // sniffing up the parent tree until we find 
    michael@0:     // a legal place for the block
    michael@0:     if (!parent) break;
    michael@0:     // Don't leave the active editing host
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     if (!mHTMLEditor->IsDescendantOfEditorRoot(parent)) {
    michael@0:       nsCOMPtr parentContent = do_QueryInterface(parent);
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       if (parentContent != mHTMLEditor->GetActiveEditingHost()) {
    michael@0:         break;
    michael@0:       }
    michael@0:     }
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     if (mHTMLEditor->CanContainTag(parent, tagAtom)) {
    michael@0:       tagParent = parent;
    michael@0:       break;
    michael@0:     }
    michael@0:     splitNode = parent;
    michael@0:     parent->GetParentNode(getter_AddRefs(temp));
    michael@0:     parent = temp;
    michael@0:   }
    michael@0:   if (!tagParent)
    michael@0:   {
    michael@0:     // could not find a place to build tag!
    michael@0:     return NS_ERROR_FAILURE;
    michael@0:   }
    michael@0:   if (splitNode)
    michael@0:   {
    michael@0:     // we found a place for block, but above inOutParent.  We need to split nodes.
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:     *inOutParent = tagParent;
    michael@0:   }
    michael@0:   return res;
    michael@0: }      
    michael@0: 
    michael@0: ///////////////////////////////////////////////////////////////////////////
    michael@0: // JoinNodesSmart:  join two nodes, doing whatever makes sense for their  
    michael@0: //                  children (which often means joining them, too).
    michael@0: //                  aNodeLeft & aNodeRight must be same type of node.
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft, 
    michael@0:                                  nsIDOMNode *aNodeRight, 
    michael@0:                                  nsCOMPtr *aOutMergeParent, 
    michael@0:                                  int32_t *aOutMergeOffset)
    michael@0: {
    michael@0:   // check parms
    michael@0:   NS_ENSURE_TRUE(aNodeLeft &&  
    michael@0:       aNodeRight && 
    michael@0:       aOutMergeParent &&
    michael@0:       aOutMergeOffset, NS_ERROR_NULL_POINTER);
    michael@0:   
    michael@0:   nsresult res = NS_OK;
    michael@0:   // caller responsible for:
    michael@0:   //   left & right node are same type
    michael@0:   int32_t parOffset;
    michael@0:   nsCOMPtr rightParent;
    michael@0:   nsCOMPtr parent = nsEditor::GetNodeLocation(aNodeLeft, &parOffset);
    michael@0:   aNodeRight->GetParentNode(getter_AddRefs(rightParent));
    michael@0: 
    michael@0:   // if they don't have the same parent, first move the 'right' node 
    michael@0:   // to after the 'left' one
    michael@0:   if (parent != rightParent)
    michael@0:   {
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:   }
    michael@0:   
    michael@0:   // defaults for outParams
    michael@0:   *aOutMergeParent = aNodeRight;
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((uint32_t*)aOutMergeOffset));
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0: 
    michael@0:   // separate join rules for differing blocks
    michael@0:   if (nsHTMLEditUtils::IsList(aNodeLeft) ||
    michael@0:       !mHTMLEditor ||
    michael@0:       mHTMLEditor->IsTextNode(aNodeLeft))
    michael@0:   {
    michael@0:     // for list's, merge shallow (wouldn't want to combine list items)
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:     return res;
    michael@0:   }
    michael@0:   else
    michael@0:   {
    michael@0:     // remember the last left child, and firt right child
    michael@0:     nsCOMPtr lastLeft, firstRight;
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft));
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight));
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0: 
    michael@0:     // for list items, divs, etc, merge smart
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0: 
    michael@0:     if (lastLeft && firstRight && mHTMLEditor &&
    michael@0:         mHTMLEditor->NodesSameType(lastLeft, firstRight) &&
    michael@0:         (nsEditor::IsTextNode(lastLeft) ||
    michael@0:          !mHTMLEditor ||
    michael@0:          mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft, firstRight))) {
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);      
    michael@0:       return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
    michael@0:     }
    michael@0:   }
    michael@0:   return res;
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode, 
    michael@0:                                          nsCOMPtr *aOutCiteNode,
    michael@0:                                          bool aPlainText)
    michael@0: {
    michael@0:   // check parms
    michael@0:   NS_ENSURE_TRUE(aNode && aOutCiteNode, NS_ERROR_NULL_POINTER);
    michael@0:   
    michael@0:   nsresult res = NS_OK;
    michael@0:   nsCOMPtr node, parentNode;
    michael@0:   node = do_QueryInterface(aNode);
    michael@0:   
    michael@0:   while (node)
    michael@0:   {
    michael@0:     if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) ||
    michael@0:          nsHTMLEditUtils::IsMailCite(node) )
    michael@0:       *aOutCiteNode = node;
    michael@0:     if (nsTextEditUtils::IsBody(node)) break;
    michael@0:     
    michael@0:     res = node->GetParentNode(getter_AddRefs(parentNode));
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:     node = parentNode;
    michael@0:   }
    michael@0: 
    michael@0:   return res;
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode)
    michael@0: {
    michael@0:   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
    michael@0: 
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   bool useCSS = mHTMLEditor->IsCSSEnabled();
    michael@0: 
    michael@0:   for (int32_t j = 0; j < SIZE_STYLE_TABLE; ++j)
    michael@0:   {
    michael@0:     bool isSet = false;
    michael@0:     nsAutoString outValue;
    michael@0:     // Don't use CSS for , we don't support it usefully (bug 780035)
    michael@0:     if (!useCSS || (mCachedStyles[j].tag == nsGkAtoms::font &&
    michael@0:                     mCachedStyles[j].attr.EqualsLiteral("size"))) {
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag,
    michael@0:                                               &(mCachedStyles[j].attr), nullptr,
    michael@0:                                               isSet, &outValue);
    michael@0:     }
    michael@0:     else
    michael@0:     {
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode,
    michael@0:         mCachedStyles[j].tag, &(mCachedStyles[j].attr), isSet, outValue,
    michael@0:         nsHTMLCSSUtils::eComputed);
    michael@0:     }
    michael@0:     if (isSet)
    michael@0:     {
    michael@0:       mCachedStyles[j].mPresent = true;
    michael@0:       mCachedStyles[j].value.Assign(outValue);
    michael@0:     }
    michael@0:   }
    michael@0:   return NS_OK;
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: nsresult
    michael@0: nsHTMLEditRules::ReapplyCachedStyles()
    michael@0: {
    michael@0:   // The idea here is to examine our cached list of styles and see if any have
    michael@0:   // been removed.  If so, add typeinstate for them, so that they will be
    michael@0:   // reinserted when new content is added.
    michael@0: 
    michael@0:   // remember if we are in css mode
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   bool useCSS = mHTMLEditor->IsCSSEnabled();
    michael@0: 
    michael@0:   // get selection point; if it doesn't exist, we have nothing to do
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   nsRefPtr selection = mHTMLEditor->GetSelection();
    michael@0:   MOZ_ASSERT(selection);
    michael@0:   if (!selection->GetRangeCount()) {
    michael@0:     // Nothing to do
    michael@0:     return NS_OK;
    michael@0:   }
    michael@0:   nsCOMPtr selNode =
    michael@0:     do_QueryInterface(selection->GetRangeAt(0)->GetStartParent());
    michael@0:   if (!selNode) {
    michael@0:     // Nothing to do
    michael@0:     return NS_OK;
    michael@0:   }
    michael@0: 
    michael@0:   for (int32_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
    michael@0:     if (mCachedStyles[i].mPresent) {
    michael@0:       bool bFirst, bAny, bAll;
    michael@0:       bFirst = bAny = bAll = false;
    michael@0: 
    michael@0:       nsAutoString curValue;
    michael@0:       if (useCSS) {
    michael@0:         // check computed style first in css case
    michael@0:         NS_ENSURE_STATE(mHTMLEditor);
    michael@0:         bAny = mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
    michael@0:           selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue,
    michael@0:           nsHTMLCSSUtils::eComputed);
    michael@0:       }
    michael@0:       if (!bAny) {
    michael@0:         // then check typeinstate and html style
    michael@0:         NS_ENSURE_STATE(mHTMLEditor);
    michael@0:         nsresult res = mHTMLEditor->GetInlinePropertyBase(mCachedStyles[i].tag,
    michael@0:                                                      &(mCachedStyles[i].attr),
    michael@0:                                                      &(mCachedStyles[i].value),
    michael@0:                                                      &bFirst, &bAny, &bAll,
    michael@0:                                                      &curValue, false);
    michael@0:         NS_ENSURE_SUCCESS(res, res);
    michael@0:       }
    michael@0:       // this style has disappeared through deletion.  Add to our typeinstate:
    michael@0:       if (!bAny || IsStyleCachePreservingAction(mTheAction)) {
    michael@0:         NS_ENSURE_STATE(mHTMLEditor);
    michael@0:         mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
    michael@0:                                            mCachedStyles[i].attr,
    michael@0:                                            mCachedStyles[i].value);
    michael@0:       }
    michael@0:     }
    michael@0:   }
    michael@0: 
    michael@0:   return NS_OK;
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: void
    michael@0: nsHTMLEditRules::ClearCachedStyles()
    michael@0: {
    michael@0:   // clear the mPresent bits in mCachedStyles array
    michael@0:   for (uint32_t j = 0; j < SIZE_STYLE_TABLE; j++) {
    michael@0:     mCachedStyles[j].mPresent = false;
    michael@0:     mCachedStyles[j].value.Truncate();
    michael@0:   }
    michael@0: }
    michael@0: 
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::AdjustSpecialBreaks(bool aSafeToAskFrames)
    michael@0: {
    michael@0:   nsCOMArray arrayOfNodes;
    michael@0:   nsCOMPtr isupports;
    michael@0:   int32_t nodeCount,j;
    michael@0:   
    michael@0:   // gather list of empty nodes
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   nsEmptyEditableFunctor functor(mHTMLEditor);
    michael@0:   nsDOMIterator iter;
    michael@0:   nsresult res = iter.Init(mDocChangeRange);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   res = iter.AppendList(functor, arrayOfNodes);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0: 
    michael@0:   // put moz-br's into these empty li's and td's
    michael@0:   nodeCount = arrayOfNodes.Count();
    michael@0:   for (j = 0; j < nodeCount; j++)
    michael@0:   {
    michael@0:     // need to put br at END of node.  It may have
    michael@0:     // empty containers in it and still pass the "IsEmptynode" test,
    michael@0:     // and we want the br's to be after them.  Also, we want the br
    michael@0:     // to be after the selection if the selection is in this node.
    michael@0:     uint32_t len;
    michael@0:     nsCOMPtr theNode = arrayOfNodes[0];
    michael@0:     arrayOfNodes.RemoveObjectAt(0);
    michael@0:     res = nsEditor::GetLengthOfDOMNode(theNode, len);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:     res = CreateMozBR(theNode, (int32_t)len);
    michael@0:     NS_ENSURE_SUCCESS(res, res);
    michael@0:   }
    michael@0:   
    michael@0:   return res;
    michael@0: }
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
    michael@0: {
    michael@0:   // get selection point
    michael@0:   nsCOMPtr selNode;
    michael@0:   int32_t selOffset;
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   
    michael@0:   // ask whitespace object to tweak nbsp's
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
    michael@0: }
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::PinSelectionToNewBlock(nsISelection *aSelection)
    michael@0: {
    michael@0:   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
    michael@0:   if (!aSelection->Collapsed()) {
    michael@0:     return NS_OK;
    michael@0:   }
    michael@0: 
    michael@0:   // get the (collapsed) selection location
    michael@0:   nsCOMPtr selNode, temp;
    michael@0:   int32_t selOffset;
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   temp = selNode;
    michael@0:   
    michael@0:   // use ranges and sRangeHelper to compare sel point to new block
    michael@0:   nsCOMPtr node = do_QueryInterface(selNode);
    michael@0:   NS_ENSURE_STATE(node);
    michael@0:   nsRefPtr range = new nsRange(node);
    michael@0:   res = range->SetStart(selNode, selOffset);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   res = range->SetEnd(selNode, selOffset);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   nsCOMPtr block (do_QueryInterface(mNewBlock));
    michael@0:   NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
    michael@0:   bool nodeBefore, nodeAfter;
    michael@0:   res = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0:   
    michael@0:   if (nodeBefore && nodeAfter)
    michael@0:     return NS_OK;  // selection is inside block
    michael@0:   else if (nodeBefore)
    michael@0:   {
    michael@0:     // selection is after block.  put at end of block.
    michael@0:     nsCOMPtr tmp = mNewBlock;
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     mHTMLEditor->GetLastEditableChild(mNewBlock, address_of(tmp));
    michael@0:     uint32_t endPoint;
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     if (mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor ||
    michael@0:         mHTMLEditor->IsContainer(tmp))
    michael@0:     {
    michael@0:       NS_ENSURE_STATE(mHTMLEditor);
    michael@0:       res = nsEditor::GetLengthOfDOMNode(tmp, endPoint);
    michael@0:       NS_ENSURE_SUCCESS(res, res);
    michael@0:     }
    michael@0:     else
    michael@0:     {
    michael@0:       tmp = nsEditor::GetNodeLocation(tmp, (int32_t*)&endPoint);
    michael@0:       endPoint++;  // want to be after this node
    michael@0:     }
    michael@0:     return aSelection->Collapse(tmp, (int32_t)endPoint);
    michael@0:   }
    michael@0:   else
    michael@0:   {
    michael@0:     // selection is before block.  put at start of block.
    michael@0:     nsCOMPtr tmp = mNewBlock;
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     mHTMLEditor->GetFirstEditableChild(mNewBlock, address_of(tmp));
    michael@0:     int32_t offset;
    michael@0:     if (!(mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor ||
    michael@0:           mHTMLEditor->IsContainer(tmp)))
    michael@0:     {
    michael@0:       tmp = nsEditor::GetNodeLocation(tmp, &offset);
    michael@0:     }
    michael@0:     NS_ENSURE_STATE(mHTMLEditor);
    michael@0:     return aSelection->Collapse(tmp, 0);
    michael@0:   }
    michael@0: }
    michael@0: 
    michael@0: nsresult 
    michael@0: nsHTMLEditRules::CheckInterlinePosition(nsISelection *aSelection)
    michael@0: {
    michael@0:   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
    michael@0:   nsCOMPtr selection(aSelection);
    michael@0:   nsCOMPtr selPriv(do_QueryInterface(selection));
    michael@0: 
    michael@0:   // if the selection isn't collapsed, do nothing.
    michael@0:   if (!aSelection->Collapsed()) {
    michael@0:     return NS_OK;
    michael@0:   }
    michael@0: 
    michael@0:   // get the (collapsed) selection location
    michael@0:   nsCOMPtr selNode, node;
    michael@0:   int32_t selOffset;
    michael@0:   NS_ENSURE_STATE(mHTMLEditor);
    michael@0:   nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
    michael@0:   NS_ENSURE_SUCCESS(res, res);
    michael@0: 
    michael@0:   // First, let's check to see if we are after a 
    . We take care of this michael@0: // special-case first so that we don't accidentally fall through into one michael@0: // of the other conditionals. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), true); michael@0: if (node && nsTextEditUtils::IsBreak(node)) michael@0: { michael@0: selPriv->SetInterlinePosition(true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // are we after a block? If so try set caret to following content michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node)); michael@0: if (node && IsBlockNode(node)) michael@0: { michael@0: selPriv->SetInterlinePosition(true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // are we before a block? If so try set caret to prior content michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node)); michael@0: if (node && IsBlockNode(node)) michael@0: selPriv->SetInterlinePosition(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction) michael@0: { michael@0: NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr selection(aSelection); michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: michael@0: // if the selection isn't collapsed, do nothing. michael@0: // moose: one thing to do instead is check for the case of michael@0: // only a single break selected, and collapse it. Good thing? Beats me. michael@0: if (!aSelection->Collapsed()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // get the (collapsed) selection location michael@0: nsCOMPtr selNode, temp; michael@0: int32_t selOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: temp = selNode; michael@0: michael@0: // are we in an editable node? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: while (!mHTMLEditor->IsEditable(selNode)) michael@0: { michael@0: // scan up the tree until we find an editable place to be michael@0: selNode = nsEditor::GetNodeLocation(temp, &selOffset); michael@0: NS_ENSURE_TRUE(selNode, NS_ERROR_FAILURE); michael@0: temp = selNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: } michael@0: michael@0: // make sure we aren't in an empty block - user will see no cursor. If this michael@0: // is happening, put a
    in the block if allowed. michael@0: nsCOMPtr theblock; michael@0: if (IsBlockNode(selNode)) { michael@0: theblock = selNode; michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: theblock = mHTMLEditor->GetBlockNodeParent(selNode); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (theblock && mHTMLEditor->IsEditable(theblock)) { michael@0: bool bIsEmptyNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // check if br can go into the destination node michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (bIsEmptyNode && mHTMLEditor->CanContainTag(selNode, nsGkAtoms::br)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr rootNode = do_QueryInterface(mHTMLEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE); michael@0: if (selNode == rootNode) michael@0: { michael@0: // Our root node is completely empty. Don't add a
    here. michael@0: // AfterEditInner() will add one for us when it calls michael@0: // CreateBogusNodeIfNeeded()! michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we know we can skip the rest of this routine given the cirumstance michael@0: return CreateMozBR(selNode, selOffset); michael@0: } michael@0: } michael@0: michael@0: // are we in a text node? michael@0: nsCOMPtr textNode = do_QueryInterface(selNode); michael@0: if (textNode) michael@0: return NS_OK; // we LIKE it when we are in a text node. that RULZ michael@0: michael@0: // do we need to insert a special mozBR? We do if we are: michael@0: // 1) prior node is in same block where selection is AND michael@0: // 2) prior node is a br AND michael@0: // 3) that br is not visible michael@0: michael@0: nsCOMPtr nearNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nearNode) michael@0: { michael@0: // is nearNode also a descendant of same block? michael@0: nsCOMPtr block, nearBlock; michael@0: if (IsBlockNode(selNode)) { michael@0: block = selNode; michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: block = mHTMLEditor->GetBlockNodeParent(selNode); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode); michael@0: if (block == nearBlock) michael@0: { michael@0: if (nearNode && nsTextEditUtils::IsBreak(nearNode) ) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsVisBreak(nearNode)) michael@0: { michael@0: // need to insert special moz BR. Why? Because if we don't michael@0: // the user will see no new line for the break. Also, things michael@0: // like table cells won't grow in height. michael@0: nsCOMPtr brNode; michael@0: res = CreateMozBR(selNode, selOffset, getter_AddRefs(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: selNode = nsEditor::GetNodeLocation(brNode, &selOffset); michael@0: // selection stays *before* moz-br, sticking to it michael@0: selPriv->SetInterlinePosition(true); michael@0: res = aSelection->Collapse(selNode,selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr nextNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetNextHTMLNode(nearNode, address_of(nextNode), true); michael@0: if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) michael@0: { michael@0: // selection between br and mozbr. make it stick to mozbr michael@0: // so that it will be on blank line. michael@0: selPriv->SetInterlinePosition(true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // we aren't in a textnode: are we adjacent to text or a break or an image? michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode), true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nearNode && (nsTextEditUtils::IsBreak(nearNode) michael@0: || nsEditor::IsTextNode(nearNode) michael@0: || nsHTMLEditUtils::IsImage(nearNode) michael@0: || nsHTMLEditUtils::IsHR(nearNode))) michael@0: return NS_OK; // this is a good place for the caret to be michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, address_of(nearNode), true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nearNode && (nsTextEditUtils::IsBreak(nearNode) michael@0: || nsEditor::IsTextNode(nearNode) michael@0: || nsHTMLEditUtils::IsImage(nearNode) michael@0: || nsHTMLEditUtils::IsHR(nearNode))) michael@0: return NS_OK; // this is a good place for the caret to be michael@0: michael@0: // look for a nearby text node. michael@0: // prefer the correct direction. michael@0: res = FindNearSelectableNode(selNode, selOffset, aAction, address_of(nearNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (nearNode) michael@0: { michael@0: // is the nearnode a text node? michael@0: textNode = do_QueryInterface(nearNode); michael@0: if (textNode) michael@0: { michael@0: int32_t offset = 0; michael@0: // put selection in right place: michael@0: if (aAction == nsIEditor::ePrevious) michael@0: textNode->GetLength((uint32_t*)&offset); michael@0: res = aSelection->Collapse(nearNode,offset); michael@0: } michael@0: else // must be break or image michael@0: { michael@0: selNode = nsEditor::GetNodeLocation(nearNode, &selOffset); michael@0: if (aAction == nsIEditor::ePrevious) selOffset++; // want to be beyond it if we backed up to it michael@0: res = aSelection->Collapse(selNode, selOffset); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode, michael@0: int32_t aSelOffset, michael@0: nsIEditor::EDirection &aDirection, michael@0: nsCOMPtr *outSelectableNode) michael@0: { michael@0: NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER); michael@0: *outSelectableNode = nullptr; michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr nearNode, curNode; michael@0: if (aDirection == nsIEditor::ePrevious) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!nearNode) // try the other direction then michael@0: { michael@0: if (aDirection == nsIEditor::ePrevious) michael@0: aDirection = nsIEditor::eNext; michael@0: else michael@0: aDirection = nsIEditor::ePrevious; michael@0: michael@0: if (aDirection == nsIEditor::ePrevious) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // scan in the right direction until we find an eligible text node, michael@0: // but don't cross any breaks, images, or table elements. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: while (nearNode && !(mHTMLEditor->IsTextNode(nearNode) michael@0: || nsTextEditUtils::IsBreak(nearNode) michael@0: || nsHTMLEditUtils::IsImage(nearNode))) michael@0: { michael@0: curNode = nearNode; michael@0: if (aDirection == nsIEditor::ePrevious) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode)); michael@0: } else { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: } michael@0: michael@0: if (nearNode) michael@0: { michael@0: // don't cross any table elements michael@0: if (InDifferentTableElements(nearNode, aSelNode)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // otherwise, ok, we have found a good spot to put the selection michael@0: *outSelectableNode = do_QueryInterface(nearNode); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: bool nsHTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1, michael@0: nsIDOMNode* aNode2) michael@0: { michael@0: nsCOMPtr node1 = do_QueryInterface(aNode1); michael@0: nsCOMPtr node2 = do_QueryInterface(aNode2); michael@0: return InDifferentTableElements(node1, node2); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditRules::InDifferentTableElements(nsINode* aNode1, nsINode* aNode2) michael@0: { michael@0: MOZ_ASSERT(aNode1 && aNode2); michael@0: michael@0: while (aNode1 && !nsHTMLEditUtils::IsTableElement(aNode1)) { michael@0: aNode1 = aNode1->GetParentNode(); michael@0: } michael@0: michael@0: while (aNode2 && !nsHTMLEditUtils::IsTableElement(aNode2)) { michael@0: aNode2 = aNode2->GetParentNode(); michael@0: } michael@0: michael@0: return aNode1 != aNode2; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::RemoveEmptyNodes() michael@0: { michael@0: // some general notes on the algorithm used here: the goal is to examine all the michael@0: // nodes in mDocChangeRange, and remove the empty ones. We do this by using a michael@0: // content iterator to traverse all the nodes in the range, and placing the empty michael@0: // nodes into an array. After finishing the iteration, we delete the empty nodes michael@0: // in the array. (they cannot be deleted as we find them becasue that would michael@0: // invalidate the iterator.) michael@0: // Since checking to see if a node is empty can be costly for nodes with many michael@0: // descendants, there are some optimizations made. I rely on the fact that the michael@0: // iterator is post-order: it will visit children of a node before visiting the michael@0: // parent node. So if I find that a child node is not empty, I know that its michael@0: // parent is not empty without even checking. So I put the parent on a "skipList" michael@0: // which is just a voidArray of nodes I can skip the empty check on. If I michael@0: // encounter a node on the skiplist, i skip the processing for that node and replace michael@0: // its slot in the skiplist with that node's parent. michael@0: // An interseting idea is to go ahead and regard parent nodes that are NOT on the michael@0: // skiplist as being empty (without even doing the IsEmptyNode check) on the theory michael@0: // that if they weren't empty, we would have encountered a non-empty child earlier michael@0: // and thus put this parent node on the skiplist. michael@0: // Unfortunately I can't use that strategy here, because the range may include michael@0: // some children of a node while excluding others. Thus I could find all the michael@0: // _examined_ children empty, but still not have an empty parent. michael@0: michael@0: // need an iterator michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1"); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = iter->Init(mDocChangeRange); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMArray arrayOfEmptyNodes, arrayOfEmptyCites; michael@0: nsTArray > skipList; michael@0: michael@0: // check for empty nodes michael@0: while (!iter->IsDone()) { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: nsINode* parent = node->GetParentNode(); michael@0: michael@0: uint32_t idx = skipList.IndexOf(node); michael@0: if (idx != skipList.NoIndex) { michael@0: // this node is on our skip list. Skip processing for this node, michael@0: // and replace its value in the skip list with the value of its parent michael@0: skipList[idx] = parent; michael@0: } else { michael@0: bool bIsCandidate = false; michael@0: bool bIsEmptyNode = false; michael@0: bool bIsMailCite = false; michael@0: michael@0: if (node->IsElement()) { michael@0: dom::Element* element = node->AsElement(); michael@0: if (element->IsHTML(nsGkAtoms::body)) { michael@0: // don't delete the body michael@0: } else if ((bIsMailCite = nsHTMLEditUtils::IsMailCite(element)) || michael@0: element->IsHTML(nsGkAtoms::a) || michael@0: nsHTMLEditUtils::IsInlineStyle(element) || michael@0: nsHTMLEditUtils::IsList(element) || michael@0: element->IsHTML(nsGkAtoms::div)) { michael@0: // only consider certain nodes to be empty for purposes of removal michael@0: bIsCandidate = true; michael@0: } else if (nsHTMLEditUtils::IsFormatNode(element) || michael@0: nsHTMLEditUtils::IsListItem(element) || michael@0: element->IsHTML(nsGkAtoms::blockquote)) { michael@0: // these node types are candidates if selection is not in them michael@0: // if it is one of these, don't delete if selection inside. michael@0: // this is so we can create empty headings, etc, for the michael@0: // user to type into. michael@0: bool bIsSelInNode; michael@0: res = SelectionEndpointInNode(node, &bIsSelInNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!bIsSelInNode) michael@0: { michael@0: bIsCandidate = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (bIsCandidate) { michael@0: // we delete mailcites even if they have a solo br in them michael@0: // other nodes we require to be empty michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode, michael@0: bIsMailCite, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (bIsEmptyNode) { michael@0: if (bIsMailCite) { michael@0: // mailcites go on a separate list from other empty nodes michael@0: arrayOfEmptyCites.AppendObject(node); michael@0: } else { michael@0: arrayOfEmptyNodes.AppendObject(node); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!bIsEmptyNode) { michael@0: // put parent on skip list michael@0: skipList.AppendElement(parent); michael@0: } michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: // now delete the empty nodes michael@0: int32_t nodeCount = arrayOfEmptyNodes.Count(); michael@0: for (int32_t j = 0; j < nodeCount; j++) { michael@0: nsCOMPtr delNode = arrayOfEmptyNodes[0]->AsDOMNode(); michael@0: arrayOfEmptyNodes.RemoveObjectAt(0); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsModifiableNode(delNode)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(delNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // now delete the empty mailcites michael@0: // this is a separate step because we want to pull out any br's and preserve them. michael@0: nodeCount = arrayOfEmptyCites.Count(); michael@0: for (int32_t j = 0; j < nodeCount; j++) { michael@0: nsCOMPtr delNode = arrayOfEmptyCites[0]->AsDOMNode(); michael@0: arrayOfEmptyCites.RemoveObjectAt(0); michael@0: bool bIsEmptyNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsEmptyNode(delNode, &bIsEmptyNode, false, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!bIsEmptyNode) michael@0: { michael@0: // we are deleting a cite that has just a br. We want to delete cite, michael@0: // but preserve br. michael@0: nsCOMPtr parent, brNode; michael@0: int32_t offset; michael@0: parent = nsEditor::GetNodeLocation(delNode, &offset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(delNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::SelectionEndpointInNode(nsINode* aNode, bool* aResult) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsIDOMNode* node = aNode->AsDOMNode(); michael@0: michael@0: *aResult = false; michael@0: michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: Selection* sel = static_cast(selection.get()); michael@0: uint32_t rangeCount = sel->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsRefPtr range = sel->GetRangeAt(rangeIdx); michael@0: nsCOMPtr startParent, endParent; michael@0: range->GetStartContainer(getter_AddRefs(startParent)); michael@0: if (startParent) michael@0: { michael@0: if (node == startParent) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: if (nsEditorUtils::IsDescendantOf(startParent, node)) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: range->GetEndContainer(getter_AddRefs(endParent)); michael@0: if (startParent == endParent) continue; michael@0: if (endParent) michael@0: { michael@0: if (node == endParent) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: if (nsEditorUtils::IsDescendantOf(endParent, node)) { michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsEmptyInline: return true if aNode is an empty inline container michael@0: // michael@0: // michael@0: bool michael@0: nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode) michael@0: { michael@0: if (aNode && IsInlineNode(aNode) && mHTMLEditor && michael@0: mHTMLEditor->IsContainer(aNode)) michael@0: { michael@0: bool bEmpty; michael@0: NS_ENSURE_TRUE(mHTMLEditor, false); michael@0: mHTMLEditor->IsEmptyNode(aNode, &bEmpty); michael@0: return bEmpty; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsHTMLEditRules::ListIsEmptyLine(nsCOMArray &arrayOfNodes) michael@0: { michael@0: // we have a list of nodes which we are candidates for being moved michael@0: // into a new block. Determine if it's anything more than a blank line. michael@0: // Look for editable content above and beyond one single BR. michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: NS_ENSURE_TRUE(listCount, true); michael@0: nsCOMPtr somenode; michael@0: int32_t j, brCount=0; michael@0: for (j = 0; j < listCount; j++) michael@0: { michael@0: somenode = arrayOfNodes[j]; michael@0: NS_ENSURE_TRUE(mHTMLEditor, false); michael@0: if (somenode && mHTMLEditor->IsEditable(somenode)) michael@0: { michael@0: if (nsTextEditUtils::IsBreak(somenode)) michael@0: { michael@0: // first break doesn't count michael@0: if (brCount) return false; michael@0: brCount++; michael@0: } michael@0: else if (IsEmptyInline(somenode)) michael@0: { michael@0: // empty inline, keep looking michael@0: } michael@0: else return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, bool *aOutOfList) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aListItem && aOutOfList, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out params michael@0: *aOutOfList = false; michael@0: michael@0: nsCOMPtr curNode( do_QueryInterface(aListItem)); michael@0: int32_t offset; michael@0: nsCOMPtr curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: if (!nsHTMLEditUtils::IsListItem(curNode)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // if it's first or last list item, don't need to split the list michael@0: // otherwise we do. michael@0: int32_t parOffset; michael@0: nsCOMPtr curParPar = nsEditor::GetNodeLocation(curParent, &parOffset); michael@0: michael@0: bool bIsFirstListItem; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->IsFirstEditableChild(curNode, &bIsFirstListItem); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: bool bIsLastListItem; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->IsLastEditableChild(curNode, &bIsLastListItem); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!bIsFirstListItem && !bIsLastListItem) michael@0: { michael@0: // split the list michael@0: nsCOMPtr newBlock; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (!bIsFirstListItem) parOffset++; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curParPar, parOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // unwrap list item contents if they are no longer in a list michael@0: if (!nsHTMLEditUtils::IsList(curParPar) michael@0: && nsHTMLEditUtils::IsListItem(curNode)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *aOutOfList = true; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::RemoveListStructure(nsIDOMNode *aList) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aList); michael@0: michael@0: nsresult res; michael@0: michael@0: nsCOMPtr child; michael@0: aList->GetFirstChild(getter_AddRefs(child)); michael@0: michael@0: while (child) michael@0: { michael@0: if (nsHTMLEditUtils::IsListItem(child)) michael@0: { michael@0: bool bOutOfList; michael@0: do michael@0: { michael@0: res = PopListItem(child, &bOutOfList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } while (!bOutOfList); // keep popping it out until it's not in a list anymore michael@0: } michael@0: else if (nsHTMLEditUtils::IsList(child)) michael@0: { michael@0: res = RemoveListStructure(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else michael@0: { michael@0: // delete any non- list items for now michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: aList->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: // delete the now-empty list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveBlockContainer(aList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::ConfirmSelectionInBody() michael@0: { michael@0: nsresult res = NS_OK; michael@0: michael@0: // get the body michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr rootElement = do_QueryInterface(mHTMLEditor->GetRoot()); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED); michael@0: michael@0: // get the selection michael@0: nsCOMPtrselection; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // get the selection start location michael@0: nsCOMPtr selNode, temp, parent; michael@0: int32_t selOffset; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: temp = selNode; michael@0: michael@0: // check that selNode is inside body michael@0: while (temp && !nsTextEditUtils::IsBody(temp)) michael@0: { michael@0: res = temp->GetParentNode(getter_AddRefs(parent)); michael@0: temp = parent; michael@0: } michael@0: michael@0: // if we aren't in the body, force the issue michael@0: if (!temp) michael@0: { michael@0: // uncomment this to see when we get bad selections michael@0: // NS_NOTREACHED("selection not in body"); michael@0: selection->Collapse(rootElement, 0); michael@0: } michael@0: michael@0: // get the selection end location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: temp = selNode; michael@0: michael@0: // check that selNode is inside body michael@0: while (temp && !nsTextEditUtils::IsBody(temp)) michael@0: { michael@0: res = temp->GetParentNode(getter_AddRefs(parent)); michael@0: temp = parent; michael@0: } michael@0: michael@0: // if we aren't in the body, force the issue michael@0: if (!temp) michael@0: { michael@0: // uncomment this to see when we get bad selections michael@0: // NS_NOTREACHED("selection not in body"); michael@0: selection->Collapse(rootElement, 0); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange) michael@0: { michael@0: nsresult res = NS_OK; michael@0: michael@0: // first make sure aRange is in the document. It might not be if michael@0: // portions of our editting action involved manipulating nodes michael@0: // prior to placing them in the document (e.g., populating a list item michael@0: // before placing it in its list) michael@0: nsCOMPtr startNode; michael@0: res = aRange->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsDescendantOfRoot(startNode)) { michael@0: // just return - we don't need to adjust mDocChangeRange in this case michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mDocChangeRange) michael@0: { michael@0: // clone aRange. michael@0: nsCOMPtr range; michael@0: res = aRange->CloneRange(getter_AddRefs(range)); michael@0: mDocChangeRange = static_cast(range.get()); michael@0: } michael@0: else michael@0: { michael@0: int16_t result; michael@0: michael@0: // compare starts of ranges michael@0: res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, aRange, &result); michael@0: if (res == NS_ERROR_NOT_INITIALIZED) { michael@0: // This will happen is mDocChangeRange is non-null, but the range is michael@0: // uninitialized. In this case we'll set the start to aRange start. michael@0: // The same test won't be needed further down since after we've set michael@0: // the start the range will be collapsed to that point. michael@0: result = 1; michael@0: res = NS_OK; michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (result > 0) // positive result means mDocChangeRange start is after aRange start michael@0: { michael@0: int32_t startOffset; michael@0: res = aRange->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mDocChangeRange->SetStart(startNode, startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // compare ends of ranges michael@0: res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange, &result); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (result < 0) // negative result means mDocChangeRange end is before aRange end michael@0: { michael@0: nsCOMPtr endNode; michael@0: int32_t endOffset; michael@0: res = aRange->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = aRange->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mDocChangeRange->SetEnd(endNode, endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::InsertMozBRIfNeeded(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: if (!IsBlockNode(aNode)) return NS_OK; michael@0: michael@0: bool isEmpty; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->IsEmptyNode(aNode, &isEmpty); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!isEmpty) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return CreateMozBR(aNode, 0); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidCreateNode(const nsAString& aTag, michael@0: nsIDOMNode *aNode, michael@0: nsIDOMNode *aParent, michael@0: int32_t aPosition, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: // assumption that Join keeps the righthand node michael@0: nsresult res = mUtilRange->SelectNode(aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidInsertNode(nsIDOMNode *aNode, michael@0: nsIDOMNode *aParent, michael@0: int32_t aPosition, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: nsresult res = mUtilRange->SelectNode(aNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillDeleteNode(nsIDOMNode *aChild) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: nsresult res = mUtilRange->SelectNode(aChild); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidSplitNode(nsIDOMNode *aExistingRightNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode *aNewLeftNode, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: nsresult res = mUtilRange->SetStart(aNewLeftNode, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetEnd(aExistingRightNode, 0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: // remember split point michael@0: nsresult res = nsEditor::GetLengthOfDOMNode(aLeftNode, mJoinOffset); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidJoinNodes(nsIDOMNode *aLeftNode, michael@0: nsIDOMNode *aRightNode, michael@0: nsIDOMNode *aParent, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: // assumption that Join keeps the righthand node michael@0: nsresult res = mUtilRange->SetStart(aRightNode, mJoinOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetEnd(aRightNode, mJoinOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidInsertText(nsIDOMCharacterData *aTextNode, michael@0: int32_t aOffset, michael@0: const nsAString &aString, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: int32_t length = aString.Length(); michael@0: nsCOMPtr theNode = do_QueryInterface(aTextNode); michael@0: nsresult res = mUtilRange->SetStart(theNode, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetEnd(theNode, aOffset+length); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode, michael@0: int32_t aOffset, michael@0: int32_t aLength, michael@0: nsresult aResult) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr theNode = do_QueryInterface(aTextNode); michael@0: nsresult res = mUtilRange->SetStart(theNode, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetEnd(theNode, aOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection) michael@0: { michael@0: if (!mListenerEnabled) { michael@0: return NS_OK; michael@0: } michael@0: // get the (collapsed) selection location michael@0: nsCOMPtr selNode; michael@0: int32_t selOffset; michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetStart(selNode, selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = mUtilRange->SetEnd(selNode, selOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = UpdateDocChangeRange(mUtilRange); michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Let's remove all alignment hints in the children of aNode; it can michael@0: // be an ALIGN attribute (in case we just remove it) or a CENTER michael@0: // element (here we have to remove the container and keep its michael@0: // children). We break on tables and don't look at their children. michael@0: nsresult michael@0: nsHTMLEditRules::RemoveAlignment(nsIDOMNode * aNode, const nsAString & aAlignType, bool aChildrenOnly) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsTextNode(aNode) || nsHTMLEditUtils::IsTable(aNode)) return NS_OK; michael@0: nsresult res = NS_OK; michael@0: michael@0: nsCOMPtr child = aNode,tmp; michael@0: if (aChildrenOnly) michael@0: { michael@0: aNode->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: bool useCSS = mHTMLEditor->IsCSSEnabled(); michael@0: michael@0: while (child) michael@0: { michael@0: if (aChildrenOnly) { michael@0: // get the next sibling right now because we could have to remove child michael@0: child->GetNextSibling(getter_AddRefs(tmp)); michael@0: } michael@0: else michael@0: { michael@0: tmp = nullptr; michael@0: } michael@0: bool isBlock; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (nsEditor::NodeIsType(child, nsEditProperty::center)) michael@0: { michael@0: // the current node is a CENTER element michael@0: // first remove children's alignment michael@0: res = RemoveAlignment(child, aAlignType, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // we may have to insert BRs in first and last position of element's children michael@0: // if the nodes before/after are not blocks and not BRs michael@0: res = MakeSureElemStartsOrEndsOnCR(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // now remove the CENTER container michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveContainer(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (isBlock || nsHTMLEditUtils::IsHR(child)) michael@0: { michael@0: // the current node is a block element michael@0: nsCOMPtr curElem = do_QueryInterface(child); michael@0: if (nsHTMLEditUtils::SupportsAlignAttr(child)) michael@0: { michael@0: // remove the ALIGN attribute if this element can have it michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: if (useCSS) michael@0: { michael@0: if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child)) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, false); michael@0: } michael@0: else michael@0: { michael@0: nsAutoString dummyCssValue; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, dummyCssValue); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: if (!nsHTMLEditUtils::IsTable(child)) michael@0: { michael@0: // unless this is a table, look at children michael@0: res = RemoveAlignment(child, aAlignType, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: child = tmp; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Let's insert a BR as first (resp. last) child of aNode if its michael@0: // first (resp. last) child is not a block nor a BR, and if the michael@0: // previous (resp. next) sibling is not a block nor a BR michael@0: nsresult michael@0: nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode, bool aStarts) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr child; michael@0: nsresult res; michael@0: if (aStarts) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(child)); michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetLastEditableChild(aNode, address_of(child)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(child, NS_OK); michael@0: bool isChildBlock; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: bool foundCR = false; michael@0: if (isChildBlock || nsTextEditUtils::IsBreak(child)) michael@0: { michael@0: foundCR = true; michael@0: } michael@0: else michael@0: { michael@0: nsCOMPtr sibling; michael@0: if (aStarts) michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling)); michael@0: } michael@0: else michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling)); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (sibling) michael@0: { michael@0: bool isBlock; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isBlock || nsTextEditUtils::IsBreak(sibling)) michael@0: { michael@0: foundCR = true; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: foundCR = true; michael@0: } michael@0: } michael@0: if (!foundCR) michael@0: { michael@0: int32_t offset = 0; michael@0: if (!aStarts) { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_STATE(node); michael@0: offset = node->GetChildCount(); michael@0: } michael@0: nsCOMPtr brNode; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode) michael@0: { michael@0: nsresult res = MakeSureElemStartsOrEndsOnCR(aNode, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = MakeSureElemStartsOrEndsOnCR(aNode, true); michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::AlignBlock(nsIDOMElement * aElement, const nsAString * aAlignType, bool aContentsOnly) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aElement); michael@0: bool isBlock = IsBlockNode(node); michael@0: if (!isBlock && !nsHTMLEditUtils::IsHR(node)) { michael@0: // we deal only with blocks; early way out michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult res = RemoveAlignment(node, *aAlignType, aContentsOnly); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_NAMED_LITERAL_STRING(attr, "align"); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (mHTMLEditor->IsCSSEnabled()) { michael@0: // let's use CSS alignment; we use margin-left and margin-right for tables michael@0: // and text-align for other block-level elements michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttributeOrEquivalent(aElement, attr, *aAlignType, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else { michael@0: // HTML case; this code is supposed to be called ONLY if the element michael@0: // supports the align attribute but we'll never know... michael@0: if (nsHTMLEditUtils::SupportsAlignAttr(node)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->SetAttribute(aElement, attr, *aAlignType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::RelativeChangeIndentationOfElementNode(nsIDOMNode *aNode, int8_t aRelativeChange) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode); michael@0: michael@0: if (aRelativeChange != 1 && aRelativeChange != -1) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: if (!element) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, element); michael@0: nsAutoString value; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(aNode, marginProperty, value); michael@0: float f; michael@0: nsCOMPtr unit; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit)); michael@0: if (0 == f) { michael@0: nsAutoString defaultLengthUnit; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit); michael@0: unit = do_GetAtom(defaultLengthUnit); michael@0: } michael@0: if (nsEditProperty::cssInUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange; michael@0: else if (nsEditProperty::cssCmUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange; michael@0: else if (nsEditProperty::cssMmUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange; michael@0: else if (nsEditProperty::cssPtUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange; michael@0: else if (nsEditProperty::cssPcUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange; michael@0: else if (nsEditProperty::cssEmUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange; michael@0: else if (nsEditProperty::cssExUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange; michael@0: else if (nsEditProperty::cssPxUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange; michael@0: else if (nsEditProperty::cssPercentUnit == unit) michael@0: f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange; michael@0: michael@0: if (0 < f) { michael@0: nsAutoString newValue; michael@0: newValue.AppendFloat(f); michael@0: newValue.Append(nsDependentAtomString(unit)); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(element, marginProperty, newValue, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(element, marginProperty, value, false); michael@0: michael@0: // remove unnecessary DIV blocks: michael@0: // we could skip this section but that would cause a FAIL in michael@0: // editor/libeditor/html/tests/browserscope/richtext.html, which expects michael@0: // to unapply a CSS "indent" (
    ) by michael@0: // removing the DIV container instead of just removing the CSS property. michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: if (!node || !node->IsHTML(nsGkAtoms::div) || michael@0: !mHTMLEditor || michael@0: node == mHTMLEditor->GetActiveEditingHost() || michael@0: !mHTMLEditor->IsDescendantOfEditorRoot(node) || michael@0: nsHTMLEditor::HasAttributes(node)) { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: return mHTMLEditor->RemoveContainer(element); michael@0: } michael@0: michael@0: // michael@0: // Support for Absolute Positioning michael@0: // michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillAbsolutePosition(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore result of WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: nsCOMPtr focusElement; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetSelectionContainer(getter_AddRefs(focusElement)); michael@0: if (focusElement) { michael@0: nsCOMPtr node = do_QueryInterface(focusElement); michael@0: if (nsHTMLEditUtils::IsImage(node)) { michael@0: mNewBlock = node; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: res = NormalizeSelection(aSelection); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: // convert the selection ranges into "promoted" selection ranges: michael@0: // this basically just expands the range to include the immediate michael@0: // block parent, and then further expands to include any ancestors michael@0: // whose children are all in the range michael@0: michael@0: nsCOMArray arrayOfRanges; michael@0: res = GetPromotedRanges(aSelection, arrayOfRanges, michael@0: EditAction::setAbsolutePosition); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // use these ranges to contruct a list of nodes to act on. michael@0: nsCOMArray arrayOfNodes; michael@0: res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, michael@0: EditAction::setAbsolutePosition); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: NS_NAMED_LITERAL_STRING(divType, "div"); michael@0: michael@0: michael@0: // if nothing visible in list, make an empty block michael@0: if (ListIsEmptyLine(arrayOfNodes)) michael@0: { michael@0: nsCOMPtr parent, thePositionedDiv; michael@0: int32_t offset; michael@0: michael@0: // get selection location michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // make sure we can put a block here michael@0: res = SplitAsNeeded(&divType, address_of(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(thePositionedDiv)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = thePositionedDiv; michael@0: // delete anything that was in the list of nodes michael@0: for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j) michael@0: { michael@0: nsCOMPtr curNode = arrayOfNodes[0]; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->DeleteNode(curNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: arrayOfNodes.RemoveObjectAt(0); michael@0: } michael@0: // put selection in new block michael@0: res = aSelection->Collapse(thePositionedDiv,0); michael@0: selectionResetter.Abort(); // to prevent selection reseter from overriding us. michael@0: *aHandled = true; michael@0: return res; michael@0: } michael@0: michael@0: // Ok, now go through all the nodes and put them in a blockquote, michael@0: // or whatever is appropriate. Wohoo! michael@0: int32_t i; michael@0: nsCOMPtr curParent, curPositionedDiv, curList, indentedLI, sibling; michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: for (i=0; i curNode = arrayOfNodes[i]; michael@0: michael@0: // Ignore all non-editable nodes. Leave them be. michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: if (!mHTMLEditor->IsEditable(curNode)) continue; michael@0: michael@0: int32_t offset; michael@0: curParent = nsEditor::GetNodeLocation(curNode, &offset); michael@0: michael@0: // some logic for putting list items into nested lists... michael@0: if (nsHTMLEditUtils::IsList(curParent)) michael@0: { michael@0: // check to see if curList is still appropriate. Which it is if michael@0: // curNode is still right after it in the same list. michael@0: if (curList) michael@0: { michael@0: sibling = nullptr; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: } michael@0: michael@0: if (!curList || (sibling && sibling != curList) ) michael@0: { michael@0: nsAutoString listTag; michael@0: nsEditor::GetTagString(curParent,listTag); michael@0: ToLowerCase(listTag); michael@0: // create a new nested list of correct type michael@0: res = SplitAsNeeded(&listTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!curPositionedDiv) { michael@0: int32_t parentOffset; michael@0: nsCOMPtr curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); michael@0: mNewBlock = curPositionedDiv; michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // curList is now the correct thing to put curNode in michael@0: // remember our new block for postprocessing michael@0: // mNewBlock = curList; michael@0: } michael@0: // tuck the node into the end of the active list michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // forget curPositionedDiv, if any michael@0: // curPositionedDiv = nullptr; michael@0: } michael@0: michael@0: else // not a list item, use blockquote? michael@0: { michael@0: // if we are inside a list item, we don't want to blockquote, we want michael@0: // to sublist the list item. We may have several nodes listed in the michael@0: // array of nodes to act on, that are in the same list item. Since michael@0: // we only want to indent that li once, we must keep track of the most michael@0: // recent indented list item, and not indent it if we find another node michael@0: // to act on that is still inside the same li. michael@0: nsCOMPtr listitem=IsInListItem(curNode); michael@0: if (listitem) michael@0: { michael@0: if (indentedLI == listitem) continue; // already indented this list item michael@0: curParent = nsEditor::GetNodeLocation(listitem, &offset); michael@0: // check to see if curList is still appropriate. Which it is if michael@0: // curNode is still right after it in the same list. michael@0: if (curList) michael@0: { michael@0: sibling = nullptr; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling)); michael@0: } michael@0: michael@0: if (!curList || (sibling && sibling != curList) ) michael@0: { michael@0: nsAutoString listTag; michael@0: nsEditor::GetTagString(curParent,listTag); michael@0: ToLowerCase(listTag); michael@0: // create a new nested list of correct type michael@0: res = SplitAsNeeded(&listTag, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!curPositionedDiv) { michael@0: int32_t parentOffset; michael@0: nsCOMPtr curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv)); michael@0: mNewBlock = curPositionedDiv; michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(listitem, curList, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember we indented this li michael@0: indentedLI = listitem; michael@0: } michael@0: michael@0: else michael@0: { michael@0: // need to make a div to put things in if we haven't already michael@0: michael@0: if (!curPositionedDiv) michael@0: { michael@0: if (nsHTMLEditUtils::IsDiv(curNode)) michael@0: { michael@0: curPositionedDiv = curNode; michael@0: mNewBlock = curPositionedDiv; michael@0: curList = nullptr; michael@0: continue; michael@0: } michael@0: res = SplitAsNeeded(&divType, address_of(curParent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curPositionedDiv)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // remember our new block for postprocessing michael@0: mNewBlock = curPositionedDiv; michael@0: // curPositionedDiv is now the correct thing to put curNode in michael@0: } michael@0: michael@0: // tuck the node into the end of the active blockquote michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // forget curList, if any michael@0: curList = nullptr; michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::DidAbsolutePosition() michael@0: { michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr absPosHTMLEditor = mHTMLEditor; michael@0: nsCOMPtr elt = do_QueryInterface(mNewBlock); michael@0: return absPosHTMLEditor->AbsolutelyPositionElement(elt, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection, michael@0: bool* aCancel, bool* aHandled) { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore aCancel from WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: nsCOMPtr elt; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr absPosHTMLEditor = mHTMLEditor; michael@0: return absPosHTMLEditor->AbsolutelyPositionElement(elt, false); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection, michael@0: int32_t aChange, michael@0: bool *aCancel, michael@0: bool * aHandled) michael@0: { michael@0: if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } michael@0: nsresult res = WillInsert(aSelection, aCancel); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // initialize out param michael@0: // we want to ignore aCancel from WillInsert() michael@0: *aCancel = false; michael@0: *aHandled = true; michael@0: michael@0: nsCOMPtr elt; michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor); michael@0: michael@0: NS_ENSURE_STATE(mHTMLEditor); michael@0: nsCOMPtr absPosHTMLEditor = mHTMLEditor; michael@0: int32_t zIndex; michael@0: return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditRules::DocumentModified() michael@0: { michael@0: nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, &nsHTMLEditRules::DocumentModifiedWorker)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditRules::DocumentModifiedWorker() michael@0: { michael@0: if (!mHTMLEditor) { michael@0: return; michael@0: } michael@0: michael@0: // DeleteNode below may cause a flush, which could destroy the editor michael@0: nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker; michael@0: michael@0: nsCOMPtr kungFuDeathGrip(mHTMLEditor); michael@0: nsCOMPtr selection; michael@0: nsresult rv = mHTMLEditor->GetSelection(getter_AddRefs(selection)); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: // Delete our bogus node, if we have one, since the document might not be michael@0: // empty any more. michael@0: if (mBogusNode) { michael@0: mEditor->DeleteNode(mBogusNode); michael@0: mBogusNode = nullptr; michael@0: } michael@0: michael@0: // Try to recreate the bogus node if needed. michael@0: CreateBogusNodeIfNeeded(selection); michael@0: }