michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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 "mozilla/Assertions.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMCharacterData.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsRange.h" michael@0: #include "nsSelectionState.h" michael@0: #include "nsString.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsWSRunObject.h" michael@0: michael@0: const char16_t nbsp = 160; 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: //- constructor / destructor ----------------------------------------------- michael@0: nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, int32_t aOffset) : michael@0: mNode(aNode) michael@0: ,mOffset(aOffset) michael@0: ,mPRE(false) michael@0: ,mStartNode() michael@0: ,mStartOffset(0) michael@0: ,mStartReason() michael@0: ,mStartReasonNode() michael@0: ,mEndNode() michael@0: ,mEndOffset(0) michael@0: ,mEndReason() michael@0: ,mEndReasonNode() michael@0: ,mFirstNBSPNode() michael@0: ,mFirstNBSPOffset(0) michael@0: ,mLastNBSPNode() michael@0: ,mLastNBSPOffset(0) michael@0: ,mNodeArray() michael@0: ,mStartRun(nullptr) michael@0: ,mEndRun(nullptr) michael@0: ,mHTMLEditor(aEd) michael@0: { michael@0: GetWSNodes(); michael@0: GetRuns(); michael@0: } michael@0: michael@0: nsWSRunObject::~nsWSRunObject() michael@0: { michael@0: ClearRuns(); michael@0: } michael@0: michael@0: michael@0: michael@0: //-------------------------------------------------------------------------------------------- michael@0: // public static methods michael@0: //-------------------------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsWSRunObject::ScrubBlockBoundary(nsHTMLEditor *aHTMLEd, michael@0: nsCOMPtr *aBlock, michael@0: BlockBoundary aBoundary, michael@0: int32_t *aOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aBlock && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: if ((aBoundary == kBlockStart) || (aBoundary == kBlockEnd)) michael@0: return ScrubBlockBoundaryInner(aHTMLEd, aBlock, aBoundary); michael@0: michael@0: // else we are scrubbing an outer boundary - just before or after michael@0: // a block element. michael@0: NS_ENSURE_TRUE(aOffset, NS_ERROR_NULL_POINTER); michael@0: nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aBlock, aOffset); michael@0: nsWSRunObject theWSObj(aHTMLEd, *aBlock, *aOffset); michael@0: return theWSObj.Scrub(); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd, michael@0: nsIDOMNode *aLeftParent, michael@0: nsIDOMNode *aRightParent) michael@0: { michael@0: NS_ENSURE_TRUE(aLeftParent && aRightParent && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: uint32_t count; michael@0: aHTMLEd->GetLengthOfDOMNode(aLeftParent, count); michael@0: nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count); michael@0: nsWSRunObject rightWSObj(aHTMLEd, aRightParent, 0); michael@0: michael@0: return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrepareToDeleteRange(nsHTMLEditor *aHTMLEd, michael@0: nsCOMPtr *aStartNode, michael@0: int32_t *aStartOffset, michael@0: nsCOMPtr *aEndNode, michael@0: int32_t *aEndOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aStartNode && aEndNode && *aStartNode && *aEndNode && aStartOffset && aEndOffset && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoTrackDOMPoint trackerStart(aHTMLEd->mRangeUpdater, aStartNode, aStartOffset); michael@0: nsAutoTrackDOMPoint trackerEnd(aHTMLEd->mRangeUpdater, aEndNode, aEndOffset); michael@0: michael@0: nsWSRunObject leftWSObj(aHTMLEd, *aStartNode, *aStartOffset); michael@0: nsWSRunObject rightWSObj(aHTMLEd, *aEndNode, *aEndOffset); michael@0: michael@0: return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor *aHTMLEd, michael@0: nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: michael@0: int32_t offset; michael@0: nsCOMPtr parent = aHTMLEd->GetNodeLocation(aNode, &offset); michael@0: michael@0: nsWSRunObject leftWSObj(aHTMLEd, parent, offset); michael@0: nsWSRunObject rightWSObj(aHTMLEd, parent, offset+1); michael@0: michael@0: return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd, michael@0: nsCOMPtr *aSplitNode, michael@0: int32_t *aSplitOffset) michael@0: { michael@0: NS_ENSURE_TRUE(aSplitNode && aSplitOffset && *aSplitNode && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset); michael@0: michael@0: nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset); michael@0: michael@0: return wsObj.PrepareToSplitAcrossBlocksPriv(); michael@0: } michael@0: michael@0: //-------------------------------------------------------------------------------------------- michael@0: // public instance methods michael@0: //-------------------------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsWSRunObject::InsertBreak(nsCOMPtr *aInOutParent, michael@0: int32_t *aInOutOffset, michael@0: nsCOMPtr *outBRNode, michael@0: nsIEditor::EDirection aSelect) michael@0: { michael@0: // MOOSE: for now, we always assume non-PRE formatting. Fix this later. michael@0: // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp michael@0: NS_ENSURE_TRUE(aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = NS_OK; michael@0: WSFragment *beforeRun, *afterRun; michael@0: FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false); michael@0: FindRun(*aInOutParent, *aInOutOffset, &afterRun, true); michael@0: michael@0: { michael@0: // some scoping for nsAutoTrackDOMPoint. This will track our insertion point michael@0: // while we tweak any surrounding whitespace michael@0: nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset); michael@0: michael@0: // handle any changes needed to ws run after inserted br michael@0: if (!afterRun) { michael@0: // don't need to do anything. just insert break. ws won't change. michael@0: } else if (afterRun->mType & WSType::trailingWS) { michael@0: // don't need to do anything. just insert break. ws won't change. michael@0: } else if (afterRun->mType & WSType::leadingWS) { michael@0: // delete the leading ws that is after insertion point. We don't michael@0: // have to (it would still not be significant after br), but it's michael@0: // just more aesthetically pleasing to. michael@0: res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (afterRun->mType == WSType::normalWS) { michael@0: // need to determine if break at front of non-nbsp run. if so michael@0: // convert run to nbsp. michael@0: WSPoint thePoint = GetCharAfter(*aInOutParent, *aInOutOffset); michael@0: if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) { michael@0: WSPoint prevPoint = GetCharBefore(thePoint); michael@0: if (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) { michael@0: // we are at start of non-nbsps. convert to a single nbsp. michael@0: res = ConvertToNBSP(thePoint); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // handle any changes needed to ws run before inserted br michael@0: if (!beforeRun) { michael@0: // don't need to do anything. just insert break. ws won't change. michael@0: } else if (beforeRun->mType & WSType::leadingWS) { michael@0: // don't need to do anything. just insert break. ws won't change. michael@0: } else if (beforeRun->mType & WSType::trailingWS) { michael@0: // need to delete the trailing ws that is before insertion point, because it michael@0: // would become significant after break inserted. michael@0: res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (beforeRun->mType == WSType::normalWS) { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation michael@0: res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // ready, aim, fire! michael@0: return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::InsertText(const nsAString& aStringToInsert, michael@0: nsCOMPtr *aInOutParent, michael@0: int32_t *aInOutOffset, michael@0: nsIDOMDocument *aDoc) michael@0: { michael@0: // MOOSE: for now, we always assume non-PRE formatting. Fix this later. michael@0: // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp michael@0: michael@0: // MOOSE: for now, just getting the ws logic straight. This implementation michael@0: // is very slow. Will need to replace edit rules impl with a more efficient michael@0: // text sink here that does the minimal amount of searching/replacing/copying michael@0: michael@0: NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = NS_OK; michael@0: if (aStringToInsert.IsEmpty()) return res; michael@0: michael@0: // string copying sux. michael@0: nsAutoString theString(aStringToInsert); michael@0: michael@0: WSFragment *beforeRun, *afterRun; michael@0: FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false); michael@0: FindRun(*aInOutParent, *aInOutOffset, &afterRun, true); michael@0: michael@0: { michael@0: // some scoping for nsAutoTrackDOMPoint. This will track our insertion point michael@0: // while we tweak any surrounding whitespace michael@0: nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset); michael@0: michael@0: // handle any changes needed to ws run after inserted text michael@0: if (!afterRun) { michael@0: // don't need to do anything. just insert text. ws won't change. michael@0: } else if (afterRun->mType & WSType::trailingWS) { michael@0: // don't need to do anything. just insert text. ws won't change. michael@0: } else if (afterRun->mType & WSType::leadingWS) { michael@0: // delete the leading ws that is after insertion point, because it michael@0: // would become significant after text inserted. michael@0: res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (afterRun->mType == WSType::normalWS) { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation michael@0: res = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // handle any changes needed to ws run before inserted text michael@0: if (!beforeRun) { michael@0: // don't need to do anything. just insert text. ws won't change. michael@0: } else if (beforeRun->mType & WSType::leadingWS) { michael@0: // don't need to do anything. just insert text. ws won't change. michael@0: } else if (beforeRun->mType & WSType::trailingWS) { michael@0: // need to delete the trailing ws that is before insertion point, because it michael@0: // would become significant after text inserted. michael@0: res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (beforeRun->mType == WSType::normalWS) { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation michael@0: res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // next up, tweak head and tail of string as needed. michael@0: // first the head: michael@0: // there are a variety of circumstances that would require us to convert a michael@0: // leading ws char into an nbsp: michael@0: michael@0: if (nsCRT::IsAsciiSpace(theString[0])) michael@0: { michael@0: // we have a leading space michael@0: if (beforeRun) { michael@0: if (beforeRun->mType & WSType::leadingWS) { michael@0: theString.SetCharAt(nbsp, 0); michael@0: } else if (beforeRun->mType & WSType::normalWS) { michael@0: WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset); michael@0: if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) { michael@0: theString.SetCharAt(nbsp, 0); michael@0: } michael@0: } michael@0: } else { michael@0: if (mStartReason & WSType::block || mStartReason == WSType::br) { michael@0: theString.SetCharAt(nbsp, 0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // then the tail michael@0: uint32_t lastCharIndex = theString.Length()-1; michael@0: michael@0: if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) michael@0: { michael@0: // we have a leading space michael@0: if (afterRun) michael@0: { michael@0: if (afterRun->mType & WSType::trailingWS) { michael@0: theString.SetCharAt(nbsp, lastCharIndex); michael@0: } else if (afterRun->mType & WSType::normalWS) { michael@0: WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset); michael@0: if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) { michael@0: theString.SetCharAt(nbsp, lastCharIndex); michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: if (mEndReason & WSType::block) { michael@0: theString.SetCharAt(nbsp, lastCharIndex); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // next scan string for adjacent ws and convert to nbsp/space combos michael@0: // MOOSE: don't need to convert tabs here since that is done by WillInsertText() michael@0: // before we are called. Eventually, all that logic will be pushed down into michael@0: // here and made more efficient. michael@0: uint32_t j; michael@0: bool prevWS = false; michael@0: for (j=0; j<=lastCharIndex; j++) michael@0: { michael@0: if (nsCRT::IsAsciiSpace(theString[j])) michael@0: { michael@0: if (prevWS) michael@0: { michael@0: theString.SetCharAt(nbsp, j-1); // j-1 can't be negative because prevWS starts out false michael@0: } michael@0: else michael@0: { michael@0: prevWS = true; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: prevWS = false; michael@0: } michael@0: } michael@0: michael@0: // ready, aim, fire! michael@0: res = mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::DeleteWSBackward() michael@0: { michael@0: nsresult res = NS_OK; michael@0: WSPoint point = GetCharBefore(mNode, mOffset); michael@0: NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete michael@0: michael@0: if (mPRE) // easy case, preformatted ws michael@0: { michael@0: if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp)) michael@0: { michael@0: nsCOMPtr node(do_QueryInterface(point.mTextNode)); michael@0: int32_t startOffset = point.mOffset; michael@0: int32_t endOffset = point.mOffset+1; michael@0: return DeleteChars(node, startOffset, node, endOffset); michael@0: } michael@0: } michael@0: michael@0: // callers job to insure that previous char is really ws. michael@0: // If it is normal ws, we need to delete the whole run michael@0: if (nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: nsCOMPtr startNode, endNode, node(do_QueryInterface(point.mTextNode)); michael@0: int32_t startOffset, endOffset; michael@0: GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode), michael@0: &startOffset, address_of(endNode), &endOffset); michael@0: michael@0: // adjust surrounding ws michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, michael@0: address_of(endNode), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that ws michael@0: return DeleteChars(startNode, startOffset, endNode, endOffset); michael@0: } michael@0: else if (point.mChar == nbsp) michael@0: { michael@0: nsCOMPtr node(do_QueryInterface(point.mTextNode)); michael@0: // adjust surrounding ws michael@0: int32_t startOffset = point.mOffset; michael@0: int32_t endOffset = point.mOffset+1; michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, michael@0: address_of(node), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that ws michael@0: return DeleteChars(node, startOffset, node, endOffset); michael@0: michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::DeleteWSForward() michael@0: { michael@0: nsresult res = NS_OK; michael@0: WSPoint point = GetCharAfter(mNode, mOffset); michael@0: NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete michael@0: michael@0: if (mPRE) // easy case, preformatted ws michael@0: { michael@0: if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp)) michael@0: { michael@0: nsCOMPtr node(do_QueryInterface(point.mTextNode)); michael@0: int32_t startOffset = point.mOffset; michael@0: int32_t endOffset = point.mOffset+1; michael@0: return DeleteChars(node, startOffset, node, endOffset); michael@0: } michael@0: } michael@0: michael@0: // callers job to insure that next char is really ws. michael@0: // If it is normal ws, we need to delete the whole run michael@0: if (nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: nsCOMPtr startNode, endNode, node(do_QueryInterface(point.mTextNode)); michael@0: int32_t startOffset, endOffset; michael@0: GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode), michael@0: &startOffset, address_of(endNode), &endOffset); michael@0: michael@0: // adjust surrounding ws michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, michael@0: address_of(endNode), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that ws michael@0: return DeleteChars(startNode, startOffset, endNode, endOffset); michael@0: } michael@0: else if (point.mChar == nbsp) michael@0: { michael@0: nsCOMPtr node(do_QueryInterface(point.mTextNode)); michael@0: // adjust surrounding ws michael@0: int32_t startOffset = point.mOffset; michael@0: int32_t endOffset = point.mOffset+1; michael@0: res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, michael@0: address_of(node), &endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that ws michael@0: return DeleteChars(node, startOffset, node, endOffset); michael@0: michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode, michael@0: int32_t aOffset, michael@0: nsCOMPtr *outVisNode, michael@0: int32_t *outVisOffset, michael@0: WSType *outType) michael@0: { michael@0: // Find first visible thing before the point. position outVisNode/outVisOffset michael@0: // just _after_ that thing. If we don't find anything return start of ws. michael@0: MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType); michael@0: michael@0: *outType = WSType::none; michael@0: WSFragment *run; michael@0: FindRun(aNode, aOffset, &run, false); michael@0: michael@0: // is there a visible run there or earlier? michael@0: while (run) michael@0: { michael@0: if (run->mType == WSType::normalWS) { michael@0: WSPoint point = GetCharBefore(aNode, aOffset); michael@0: if (point.mTextNode) michael@0: { michael@0: *outVisNode = do_QueryInterface(point.mTextNode); michael@0: *outVisOffset = point.mOffset+1; michael@0: if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp)) michael@0: { michael@0: *outType = WSType::normalWS; michael@0: } michael@0: else if (!point.mChar) michael@0: { michael@0: // MOOSE: not possible? michael@0: *outType = WSType::none; michael@0: } michael@0: else michael@0: { michael@0: *outType = WSType::text; michael@0: } michael@0: return; michael@0: } michael@0: // else if no text node then keep looking. We should eventually fall out of loop michael@0: } michael@0: michael@0: run = run->mLeft; michael@0: } michael@0: michael@0: // if we get here then nothing in ws data to find. return start reason michael@0: *outVisNode = mStartReasonNode; michael@0: *outVisOffset = mStartOffset; // this really isn't meaningful if mStartReasonNode!=mStartNode michael@0: *outType = mStartReason; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode, michael@0: int32_t aOffset, michael@0: nsCOMPtr *outVisNode, michael@0: int32_t *outVisOffset, michael@0: WSType *outType) michael@0: { michael@0: // Find first visible thing after the point. position outVisNode/outVisOffset michael@0: // just _before_ that thing. If we don't find anything return end of ws. michael@0: MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType); michael@0: michael@0: WSFragment *run; michael@0: FindRun(aNode, aOffset, &run, true); michael@0: michael@0: // is there a visible run there or later? michael@0: while (run) michael@0: { michael@0: if (run->mType == WSType::normalWS) { michael@0: WSPoint point = GetCharAfter(aNode, aOffset); michael@0: if (point.mTextNode) michael@0: { michael@0: *outVisNode = do_QueryInterface(point.mTextNode); michael@0: *outVisOffset = point.mOffset; michael@0: if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp)) michael@0: { michael@0: *outType = WSType::normalWS; michael@0: } michael@0: else if (!point.mChar) michael@0: { michael@0: // MOOSE: not possible? michael@0: *outType = WSType::none; michael@0: } michael@0: else michael@0: { michael@0: *outType = WSType::text; michael@0: } michael@0: return; michael@0: } michael@0: // else if no text node then keep looking. We should eventually fall out of loop michael@0: } michael@0: michael@0: run = run->mRight; michael@0: } michael@0: michael@0: // if we get here then nothing in ws data to find. return end reason michael@0: *outVisNode = mEndReasonNode; michael@0: *outVisOffset = mEndOffset; // this really isn't meaningful if mEndReasonNode!=mEndNode michael@0: *outType = mEndReason; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::AdjustWhitespace() michael@0: { michael@0: // this routine examines a run of ws and tries to get rid of some unneeded nbsp's, michael@0: // replacing them with regualr ascii space if possible. Keeping things simple michael@0: // for now and just trying to fix up the trailing ws in the run. michael@0: if (!mLastNBSPNode) { michael@0: // nothing to do! michael@0: return NS_OK; michael@0: } michael@0: nsresult res = NS_OK; michael@0: WSFragment *curRun = mStartRun; michael@0: while (curRun) michael@0: { michael@0: // look for normal ws run michael@0: if (curRun->mType == WSType::normalWS) { michael@0: res = CheckTrailingNBSPOfRun(curRun); michael@0: break; michael@0: } michael@0: curRun = curRun->mRight; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: michael@0: //-------------------------------------------------------------------------------------------- michael@0: // protected methods michael@0: //-------------------------------------------------------------------------------------------- michael@0: michael@0: already_AddRefed michael@0: nsWSRunObject::GetWSBoundingParent() michael@0: { michael@0: NS_ENSURE_TRUE(mNode, nullptr); michael@0: nsCOMPtr wsBoundingParent = mNode; michael@0: while (!IsBlockNode(wsBoundingParent)) michael@0: { michael@0: nsCOMPtr parent; michael@0: wsBoundingParent->GetParentNode(getter_AddRefs(parent)); michael@0: if (!parent || !mHTMLEditor->IsEditable(parent)) michael@0: break; michael@0: wsBoundingParent.swap(parent); michael@0: } michael@0: return wsBoundingParent.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetWSNodes() michael@0: { michael@0: // collect up an array of nodes that are contiguous with the insertion point michael@0: // and which contain only whitespace. Stop if you reach non-ws text or a new michael@0: // block boundary. michael@0: nsresult res = NS_OK; michael@0: michael@0: DOMPoint start(mNode, mOffset), end(mNode, mOffset); michael@0: nsCOMPtr wsBoundingParent = GetWSBoundingParent(); michael@0: michael@0: // first look backwards to find preceding ws nodes michael@0: if (mHTMLEditor->IsTextNode(mNode)) michael@0: { michael@0: nsCOMPtr textNode(do_QueryInterface(mNode)); michael@0: const nsTextFragment *textFrag = textNode->GetText(); michael@0: michael@0: res = PrependNodeToList(mNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (mOffset) michael@0: { michael@0: int32_t pos; michael@0: for (pos=mOffset-1; pos>=0; pos--) michael@0: { michael@0: // sanity bounds check the char position. bug 136165 michael@0: if (uint32_t(pos) >= textFrag->GetLength()) michael@0: { michael@0: NS_NOTREACHED("looking beyond end of text fragment"); michael@0: continue; michael@0: } michael@0: char16_t theChar = textFrag->CharAt(pos); michael@0: if (!nsCRT::IsAsciiSpace(theChar)) michael@0: { michael@0: if (theChar != nbsp) michael@0: { michael@0: mStartNode = mNode; michael@0: mStartOffset = pos+1; michael@0: mStartReason = WSType::text; michael@0: mStartReasonNode = mNode; michael@0: break; michael@0: } michael@0: // as we look backwards update our earliest found nbsp michael@0: mFirstNBSPNode = mNode; michael@0: mFirstNBSPOffset = pos; michael@0: // also keep track of latest nbsp so far michael@0: if (!mLastNBSPNode) michael@0: { michael@0: mLastNBSPNode = mNode; michael@0: mLastNBSPOffset = pos; michael@0: } michael@0: } michael@0: start.SetPoint(mNode,pos); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr priorNode; michael@0: while (!mStartNode) michael@0: { michael@0: // we haven't found the start of ws yet. Keep looking michael@0: res = GetPreviousWSNode(start, wsBoundingParent, address_of(priorNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (priorNode) michael@0: { michael@0: if (IsBlockNode(priorNode)) michael@0: { michael@0: start.GetPoint(mStartNode, mStartOffset); michael@0: mStartReason = WSType::otherBlock; michael@0: mStartReasonNode = priorNode; michael@0: } michael@0: else if (mHTMLEditor->IsTextNode(priorNode)) michael@0: { michael@0: res = PrependNodeToList(priorNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr textNode(do_QueryInterface(priorNode)); michael@0: const nsTextFragment *textFrag; michael@0: if (!textNode || !(textFrag = textNode->GetText())) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: uint32_t len = textNode->TextLength(); michael@0: michael@0: if (len < 1) michael@0: { michael@0: // Zero length text node. Set start point to it michael@0: // so we can get past it! michael@0: start.SetPoint(priorNode,0); michael@0: } michael@0: else michael@0: { michael@0: int32_t pos; michael@0: for (pos=len-1; pos>=0; pos--) michael@0: { michael@0: // sanity bounds check the char position. bug 136165 michael@0: if (uint32_t(pos) >= textFrag->GetLength()) michael@0: { michael@0: NS_NOTREACHED("looking beyond end of text fragment"); michael@0: continue; michael@0: } michael@0: char16_t theChar = textFrag->CharAt(pos); michael@0: if (!nsCRT::IsAsciiSpace(theChar)) michael@0: { michael@0: if (theChar != nbsp) michael@0: { michael@0: mStartNode = priorNode; michael@0: mStartOffset = pos+1; michael@0: mStartReason = WSType::text; michael@0: mStartReasonNode = priorNode; michael@0: break; michael@0: } michael@0: // as we look backwards update our earliest found nbsp michael@0: mFirstNBSPNode = priorNode; michael@0: mFirstNBSPOffset = pos; michael@0: // also keep track of latest nbsp so far michael@0: if (!mLastNBSPNode) michael@0: { michael@0: mLastNBSPNode = priorNode; michael@0: mLastNBSPOffset = pos; michael@0: } michael@0: } michael@0: start.SetPoint(priorNode,pos); michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // it's a break or a special node, like , that is not a block and not michael@0: // a break but still serves as a terminator to ws runs. michael@0: start.GetPoint(mStartNode, mStartOffset); michael@0: if (nsTextEditUtils::IsBreak(priorNode)) michael@0: mStartReason = WSType::br; michael@0: else michael@0: mStartReason = WSType::special; michael@0: mStartReasonNode = priorNode; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // no prior node means we exhausted wsBoundingParent michael@0: start.GetPoint(mStartNode, mStartOffset); michael@0: mStartReason = WSType::thisBlock; michael@0: mStartReasonNode = wsBoundingParent; michael@0: } michael@0: } michael@0: michael@0: // then look ahead to find following ws nodes michael@0: if (mHTMLEditor->IsTextNode(mNode)) michael@0: { michael@0: // don't need to put it on list. it already is from code above michael@0: nsCOMPtr textNode(do_QueryInterface(mNode)); michael@0: const nsTextFragment *textFrag = textNode->GetText(); michael@0: michael@0: uint32_t len = textNode->TextLength(); michael@0: if (uint16_t(mOffset)=textFrag->GetLength())) michael@0: { michael@0: NS_NOTREACHED("looking beyond end of text fragment"); michael@0: continue; michael@0: } michael@0: char16_t theChar = textFrag->CharAt(pos); michael@0: if (!nsCRT::IsAsciiSpace(theChar)) michael@0: { michael@0: if (theChar != nbsp) michael@0: { michael@0: mEndNode = mNode; michael@0: mEndOffset = pos; michael@0: mEndReason = WSType::text; michael@0: mEndReasonNode = mNode; michael@0: break; michael@0: } michael@0: // as we look forwards update our latest found nbsp michael@0: mLastNBSPNode = mNode; michael@0: mLastNBSPOffset = pos; michael@0: // also keep track of earliest nbsp so far michael@0: if (!mFirstNBSPNode) michael@0: { michael@0: mFirstNBSPNode = mNode; michael@0: mFirstNBSPOffset = pos; michael@0: } michael@0: } michael@0: end.SetPoint(mNode,pos+1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr nextNode; michael@0: while (!mEndNode) michael@0: { michael@0: // we haven't found the end of ws yet. Keep looking michael@0: res = GetNextWSNode(end, wsBoundingParent, address_of(nextNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nextNode) michael@0: { michael@0: if (IsBlockNode(nextNode)) michael@0: { michael@0: // we encountered a new block. therefore no more ws. michael@0: end.GetPoint(mEndNode, mEndOffset); michael@0: mEndReason = WSType::otherBlock; michael@0: mEndReasonNode = nextNode; michael@0: } michael@0: else if (mHTMLEditor->IsTextNode(nextNode)) michael@0: { michael@0: res = AppendNodeToList(nextNode); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr textNode(do_QueryInterface(nextNode)); michael@0: const nsTextFragment *textFrag; michael@0: if (!textNode || !(textFrag = textNode->GetText())) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: uint32_t len = textNode->TextLength(); michael@0: michael@0: if (len < 1) michael@0: { michael@0: // Zero length text node. Set end point to it michael@0: // so we can get past it! michael@0: end.SetPoint(nextNode,0); michael@0: } michael@0: else michael@0: { michael@0: int32_t pos; michael@0: for (pos=0; uint32_t(pos)= textFrag->GetLength()) michael@0: { michael@0: NS_NOTREACHED("looking beyond end of text fragment"); michael@0: continue; michael@0: } michael@0: char16_t theChar = textFrag->CharAt(pos); michael@0: if (!nsCRT::IsAsciiSpace(theChar)) michael@0: { michael@0: if (theChar != nbsp) michael@0: { michael@0: mEndNode = nextNode; michael@0: mEndOffset = pos; michael@0: mEndReason = WSType::text; michael@0: mEndReasonNode = nextNode; michael@0: break; michael@0: } michael@0: // as we look forwards update our latest found nbsp michael@0: mLastNBSPNode = nextNode; michael@0: mLastNBSPOffset = pos; michael@0: // also keep track of earliest nbsp so far michael@0: if (!mFirstNBSPNode) michael@0: { michael@0: mFirstNBSPNode = nextNode; michael@0: mFirstNBSPOffset = pos; michael@0: } michael@0: } michael@0: end.SetPoint(nextNode,pos+1); michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // we encountered a break or a special node, like , michael@0: // that is not a block and not a break but still michael@0: // serves as a terminator to ws runs. michael@0: end.GetPoint(mEndNode, mEndOffset); michael@0: if (nsTextEditUtils::IsBreak(nextNode)) michael@0: mEndReason = WSType::br; michael@0: else michael@0: mEndReason = WSType::special; michael@0: mEndReasonNode = nextNode; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // no next node means we exhausted wsBoundingParent michael@0: end.GetPoint(mEndNode, mEndOffset); michael@0: mEndReason = WSType::thisBlock; michael@0: mEndReasonNode = wsBoundingParent; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::GetRuns() michael@0: { michael@0: ClearRuns(); michael@0: michael@0: // handle some easy cases first michael@0: mHTMLEditor->IsPreformatted(mNode, &mPRE); michael@0: // if it's preformatedd, or if we are surrounded by text or special, it's all one michael@0: // big normal ws run michael@0: if (mPRE || michael@0: ((mStartReason == WSType::text || mStartReason == WSType::special) && michael@0: (mEndReason == WSType::text || mEndReason == WSType::special || michael@0: mEndReason == WSType::br))) { michael@0: MakeSingleWSRun(WSType::normalWS); michael@0: return; michael@0: } michael@0: michael@0: // if we are before or after a block (or after a break), and there are no nbsp's, michael@0: // then it's all non-rendering ws. michael@0: if (!mFirstNBSPNode && !mLastNBSPNode && michael@0: ((mStartReason & WSType::block) || mStartReason == WSType::br || michael@0: (mEndReason & WSType::block))) { michael@0: WSType wstype; michael@0: if ((mStartReason & WSType::block) || mStartReason == WSType::br) { michael@0: wstype = WSType::leadingWS; michael@0: } michael@0: if (mEndReason & WSType::block) { michael@0: wstype |= WSType::trailingWS; michael@0: } michael@0: MakeSingleWSRun(wstype); michael@0: return; michael@0: } michael@0: michael@0: // otherwise a little trickier. shucks. michael@0: mStartRun = new WSFragment(); michael@0: mStartRun->mStartNode = mStartNode; michael@0: mStartRun->mStartOffset = mStartOffset; michael@0: michael@0: if (mStartReason & WSType::block || mStartReason == WSType::br) { michael@0: // set up mStartRun michael@0: mStartRun->mType = WSType::leadingWS; michael@0: mStartRun->mEndNode = mFirstNBSPNode; michael@0: mStartRun->mEndOffset = mFirstNBSPOffset; michael@0: mStartRun->mLeftType = mStartReason; michael@0: mStartRun->mRightType = WSType::normalWS; michael@0: michael@0: // set up next run michael@0: WSFragment *normalRun = new WSFragment(); michael@0: mStartRun->mRight = normalRun; michael@0: normalRun->mType = WSType::normalWS; michael@0: normalRun->mStartNode = mFirstNBSPNode; michael@0: normalRun->mStartOffset = mFirstNBSPOffset; michael@0: normalRun->mLeftType = WSType::leadingWS; michael@0: normalRun->mLeft = mStartRun; michael@0: if (mEndReason != WSType::block) { michael@0: // then no trailing ws. this normal run ends the overall ws run. michael@0: normalRun->mRightType = mEndReason; michael@0: normalRun->mEndNode = mEndNode; michael@0: normalRun->mEndOffset = mEndOffset; michael@0: mEndRun = normalRun; michael@0: } michael@0: else michael@0: { michael@0: // we might have trailing ws. michael@0: // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1} michael@0: // will point to it, even though in general start/end points not michael@0: // guaranteed to be in text nodes. michael@0: if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1))) michael@0: { michael@0: // normal ws runs right up to adjacent block (nbsp next to block) michael@0: normalRun->mRightType = mEndReason; michael@0: normalRun->mEndNode = mEndNode; michael@0: normalRun->mEndOffset = mEndOffset; michael@0: mEndRun = normalRun; michael@0: } michael@0: else michael@0: { michael@0: normalRun->mEndNode = mLastNBSPNode; michael@0: normalRun->mEndOffset = mLastNBSPOffset+1; michael@0: normalRun->mRightType = WSType::trailingWS; michael@0: michael@0: // set up next run michael@0: WSFragment *lastRun = new WSFragment(); michael@0: lastRun->mType = WSType::trailingWS; michael@0: lastRun->mStartNode = mLastNBSPNode; michael@0: lastRun->mStartOffset = mLastNBSPOffset+1; michael@0: lastRun->mEndNode = mEndNode; michael@0: lastRun->mEndOffset = mEndOffset; michael@0: lastRun->mLeftType = WSType::normalWS; michael@0: lastRun->mLeft = normalRun; michael@0: lastRun->mRightType = mEndReason; michael@0: mEndRun = lastRun; michael@0: normalRun->mRight = lastRun; michael@0: } michael@0: } michael@0: } else { michael@0: // mStartReason is not WSType::block or WSType::br; set up mStartRun michael@0: mStartRun->mType = WSType::normalWS; michael@0: mStartRun->mEndNode = mLastNBSPNode; michael@0: mStartRun->mEndOffset = mLastNBSPOffset+1; michael@0: mStartRun->mLeftType = mStartReason; michael@0: michael@0: // we might have trailing ws. michael@0: // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1} michael@0: // will point to it, even though in general start/end points not michael@0: // guaranteed to be in text nodes. michael@0: if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1))) michael@0: { michael@0: mStartRun->mRightType = mEndReason; michael@0: mStartRun->mEndNode = mEndNode; michael@0: mStartRun->mEndOffset = mEndOffset; michael@0: mEndRun = mStartRun; michael@0: } michael@0: else michael@0: { michael@0: // set up next run michael@0: WSFragment *lastRun = new WSFragment(); michael@0: lastRun->mType = WSType::trailingWS; michael@0: lastRun->mStartNode = mLastNBSPNode; michael@0: lastRun->mStartOffset = mLastNBSPOffset+1; michael@0: lastRun->mLeftType = WSType::normalWS; michael@0: lastRun->mLeft = mStartRun; michael@0: lastRun->mRightType = mEndReason; michael@0: mEndRun = lastRun; michael@0: mStartRun->mRight = lastRun; michael@0: mStartRun->mRightType = WSType::trailingWS; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::ClearRuns() michael@0: { michael@0: WSFragment *tmp, *run; michael@0: run = mStartRun; michael@0: while (run) michael@0: { michael@0: tmp = run->mRight; michael@0: delete run; michael@0: run = tmp; michael@0: } michael@0: mStartRun = 0; michael@0: mEndRun = 0; michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::MakeSingleWSRun(WSType aType) michael@0: { michael@0: mStartRun = new WSFragment(); michael@0: michael@0: mStartRun->mStartNode = mStartNode; michael@0: mStartRun->mStartOffset = mStartOffset; michael@0: mStartRun->mType = aType; michael@0: mStartRun->mEndNode = mEndNode; michael@0: mStartRun->mEndOffset = mEndOffset; michael@0: mStartRun->mLeftType = mStartReason; michael@0: mStartRun->mRightType = mEndReason; michael@0: michael@0: mEndRun = mStartRun; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrependNodeToList(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: if (!mNodeArray.InsertObjectAt(aNode, 0)) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::AppendNodeToList(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: if (!mNodeArray.AppendObject(aNode)) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aPriorNode) michael@0: { michael@0: // can't really recycle various getnext/prior routines because we michael@0: // have special needs here. Need to step into inline containers but michael@0: // not block containers. michael@0: NS_ENSURE_TRUE(aStartNode && aBlockParent && aPriorNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = aStartNode->GetPreviousSibling(getter_AddRefs(*aPriorNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr temp, curNode = aStartNode; michael@0: while (!*aPriorNode) michael@0: { michael@0: // we have exhausted nodes in parent of aStartNode. michael@0: res = curNode->GetParentNode(getter_AddRefs(temp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(temp, NS_ERROR_NULL_POINTER); michael@0: if (temp == aBlockParent) michael@0: { michael@0: // we have exhausted nodes in the block parent. The convention here is to return null. michael@0: *aPriorNode = nullptr; michael@0: return NS_OK; michael@0: } michael@0: // we have a parent: look for previous sibling michael@0: res = temp->GetPreviousSibling(getter_AddRefs(*aPriorNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curNode = temp; michael@0: } michael@0: // we have a prior node. If it's a block, return it. michael@0: if (IsBlockNode(*aPriorNode)) michael@0: return NS_OK; michael@0: // else if it's a container, get deep rightmost child michael@0: else if (mHTMLEditor->IsContainer(*aPriorNode)) michael@0: { michael@0: temp = mHTMLEditor->GetRightmostChild(*aPriorNode); michael@0: if (temp) michael@0: *aPriorNode = temp; michael@0: return NS_OK; michael@0: } michael@0: // else return the node itself michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetPreviousWSNode(DOMPoint aPoint, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aPriorNode) michael@0: { michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: aPoint.GetPoint(node, offset); michael@0: return GetPreviousWSNode(node,offset,aBlockParent,aPriorNode); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aPriorNode) michael@0: { michael@0: // can't really recycle various getnext/prior routines because we michael@0: // have special needs here. Need to step into inline containers but michael@0: // not block containers. michael@0: NS_ENSURE_TRUE(aStartNode && aBlockParent && aPriorNode, NS_ERROR_NULL_POINTER); michael@0: *aPriorNode = 0; michael@0: michael@0: if (mHTMLEditor->IsTextNode(aStartNode)) michael@0: return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode); michael@0: if (!mHTMLEditor->IsContainer(aStartNode)) michael@0: return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode); michael@0: michael@0: if (!aOffset) michael@0: { michael@0: if (aStartNode==aBlockParent) michael@0: { michael@0: // we are at start of the block. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we are at start of non-block container michael@0: return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode); michael@0: } michael@0: michael@0: nsCOMPtr startContent( do_QueryInterface(aStartNode) ); michael@0: NS_ENSURE_STATE(startContent); michael@0: nsIContent *priorContent = startContent->GetChildAt(aOffset - 1); michael@0: NS_ENSURE_TRUE(priorContent, NS_ERROR_NULL_POINTER); michael@0: *aPriorNode = do_QueryInterface(priorContent); michael@0: // we have a prior node. If it's a block, return it. michael@0: if (IsBlockNode(*aPriorNode)) michael@0: return NS_OK; michael@0: // else if it's a container, get deep rightmost child michael@0: else if (mHTMLEditor->IsContainer(*aPriorNode)) michael@0: { michael@0: nsCOMPtr temp; michael@0: temp = mHTMLEditor->GetRightmostChild(*aPriorNode); michael@0: if (temp) michael@0: *aPriorNode = temp; michael@0: return NS_OK; michael@0: } michael@0: // else return the node itself michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aNextNode) michael@0: { michael@0: // can't really recycle various getnext/prior routines because we michael@0: // have special needs here. Need to step into inline containers but michael@0: // not block containers. michael@0: NS_ENSURE_TRUE(aStartNode && aBlockParent && aNextNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aNextNode = 0; michael@0: nsresult res = aStartNode->GetNextSibling(getter_AddRefs(*aNextNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr temp, curNode = aStartNode; michael@0: while (!*aNextNode) michael@0: { michael@0: // we have exhausted nodes in parent of aStartNode. michael@0: res = curNode->GetParentNode(getter_AddRefs(temp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(temp, NS_ERROR_NULL_POINTER); michael@0: if (temp == aBlockParent) michael@0: { michael@0: // we have exhausted nodes in the block parent. The convention michael@0: // here is to return null. michael@0: *aNextNode = nullptr; michael@0: return NS_OK; michael@0: } michael@0: // we have a parent: look for next sibling michael@0: res = temp->GetNextSibling(getter_AddRefs(*aNextNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: curNode = temp; michael@0: } michael@0: // we have a next node. If it's a block, return it. michael@0: if (IsBlockNode(*aNextNode)) michael@0: return NS_OK; michael@0: // else if it's a container, get deep leftmost child michael@0: else if (mHTMLEditor->IsContainer(*aNextNode)) michael@0: { michael@0: temp = mHTMLEditor->GetLeftmostChild(*aNextNode); michael@0: if (temp) michael@0: *aNextNode = temp; michael@0: return NS_OK; michael@0: } michael@0: // else return the node itself michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetNextWSNode(DOMPoint aPoint, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aNextNode) michael@0: { michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: aPoint.GetPoint(node, offset); michael@0: return GetNextWSNode(node,offset,aBlockParent,aNextNode); michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode, michael@0: int32_t aOffset, michael@0: nsIDOMNode *aBlockParent, michael@0: nsCOMPtr *aNextNode) michael@0: { michael@0: // can't really recycle various getnext/prior routines because we have special needs michael@0: // here. Need to step into inline containers but not block containers. michael@0: NS_ENSURE_TRUE(aStartNode && aBlockParent && aNextNode, NS_ERROR_NULL_POINTER); michael@0: *aNextNode = 0; michael@0: michael@0: if (mHTMLEditor->IsTextNode(aStartNode)) michael@0: return GetNextWSNode(aStartNode, aBlockParent, aNextNode); michael@0: if (!mHTMLEditor->IsContainer(aStartNode)) michael@0: return GetNextWSNode(aStartNode, aBlockParent, aNextNode); michael@0: michael@0: nsCOMPtr startContent( do_QueryInterface(aStartNode) ); michael@0: NS_ENSURE_STATE(startContent); michael@0: nsIContent *nextContent = startContent->GetChildAt(aOffset); michael@0: if (!nextContent) michael@0: { michael@0: if (aStartNode==aBlockParent) michael@0: { michael@0: // we are at end of the block. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we are at end of non-block container michael@0: return GetNextWSNode(aStartNode, aBlockParent, aNextNode); michael@0: } michael@0: michael@0: *aNextNode = do_QueryInterface(nextContent); michael@0: // we have a next node. If it's a block, return it. michael@0: if (IsBlockNode(*aNextNode)) michael@0: return NS_OK; michael@0: // else if it's a container, get deep leftmost child michael@0: else if (mHTMLEditor->IsContainer(*aNextNode)) michael@0: { michael@0: nsCOMPtr temp; michael@0: temp = mHTMLEditor->GetLeftmostChild(*aNextNode); michael@0: if (temp) michael@0: *aNextNode = temp; michael@0: return NS_OK; michael@0: } michael@0: // else return the node itself michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject) michael@0: { michael@0: // this routine adjust whitespace before *this* and after aEndObject michael@0: // in preperation for the two areas to become adjacent after the michael@0: // intervening content is deleted. It's overly agressive right michael@0: // now. There might be a block boundary remaining between them after michael@0: // the deletion, in which case these adjstments are unneeded (though michael@0: // I don't think they can ever be harmful?) michael@0: michael@0: NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER); michael@0: nsresult res = NS_OK; michael@0: michael@0: // get the runs before and after selection michael@0: WSFragment *beforeRun, *afterRun; michael@0: FindRun(mNode, mOffset, &beforeRun, false); michael@0: aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, true); michael@0: michael@0: // trim after run of any leading ws michael@0: if (afterRun && (afterRun->mType & WSType::leadingWS)) { michael@0: res = aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset, afterRun->mEndNode, afterRun->mEndOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // adjust normal ws in afterRun if needed michael@0: if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) { michael@0: if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) || michael@0: (!beforeRun && ((mStartReason & WSType::block) || michael@0: mStartReason == WSType::br))) { michael@0: // make sure leading char of following ws is an nbsp, so that it will show up michael@0: WSPoint point = aEndObject->GetCharAfter(aEndObject->mNode, michael@0: aEndObject->mOffset); michael@0: if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: res = aEndObject->ConvertToNBSP(point, eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: // trim before run of any trailing ws michael@0: if (beforeRun && (beforeRun->mType & WSType::trailingWS)) { michael@0: res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, mNode, mOffset, michael@0: eOutsideUserSelectAll); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) { michael@0: if ((afterRun && (afterRun->mType & WSType::trailingWS)) || michael@0: (afterRun && afterRun->mType == WSType::normalWS) || michael@0: (!afterRun && (aEndObject->mEndReason & WSType::block))) { michael@0: // make sure trailing char of starting ws is an nbsp, so that it will show up michael@0: WSPoint point = GetCharBefore(mNode, mOffset); michael@0: if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: nsCOMPtr wsStartNode, wsEndNode; michael@0: int32_t wsStartOffset, wsEndOffset; michael@0: GetAsciiWSBounds(eBoth, mNode, mOffset, address_of(wsStartNode), michael@0: &wsStartOffset, address_of(wsEndNode), &wsEndOffset); michael@0: point.mTextNode = do_QueryInterface(wsStartNode); michael@0: if (!point.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: // Not sure if this is needed, but it'll maintain the same michael@0: // functionality michael@0: point.mTextNode = nullptr; michael@0: } michael@0: point.mOffset = wsStartOffset; michael@0: res = ConvertToNBSP(point, eOutsideUserSelectAll); 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: nsWSRunObject::PrepareToSplitAcrossBlocksPriv() michael@0: { michael@0: // used to prepare ws to be split across two blocks. The main issue michael@0: // here is make sure normalWS doesn't end up becoming non-significant michael@0: // leading or trailing ws after the split. michael@0: nsresult res = NS_OK; michael@0: michael@0: // get the runs before and after selection michael@0: WSFragment *beforeRun, *afterRun; michael@0: FindRun(mNode, mOffset, &beforeRun, false); michael@0: FindRun(mNode, mOffset, &afterRun, true); michael@0: michael@0: // adjust normal ws in afterRun if needed michael@0: if (afterRun && afterRun->mType == WSType::normalWS) { michael@0: // make sure leading char of following ws is an nbsp, so that it will show up michael@0: WSPoint point = GetCharAfter(mNode, mOffset); michael@0: if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: res = ConvertToNBSP(point); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // adjust normal ws in beforeRun if needed michael@0: if (beforeRun && beforeRun->mType == WSType::normalWS) { michael@0: // make sure trailing char of starting ws is an nbsp, so that it will show up michael@0: WSPoint point = GetCharBefore(mNode, mOffset); michael@0: if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: nsCOMPtr wsStartNode, wsEndNode; michael@0: int32_t wsStartOffset, wsEndOffset; michael@0: GetAsciiWSBounds(eBoth, mNode, mOffset, address_of(wsStartNode), michael@0: &wsStartOffset, address_of(wsEndNode), &wsEndOffset); michael@0: point.mTextNode = do_QueryInterface(wsStartNode); michael@0: if (!point.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: // Not sure if this is needed, but it'll maintain the same michael@0: // functionality michael@0: point.mTextNode = nullptr; michael@0: } michael@0: point.mOffset = wsStartOffset; michael@0: res = ConvertToNBSP(point); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, int32_t aStartOffset, michael@0: nsIDOMNode *aEndNode, int32_t aEndOffset, michael@0: AreaRestriction aAR) michael@0: { michael@0: // MOOSE: this routine needs to be modified to preserve the integrity of the michael@0: // wsFragment info. michael@0: NS_ENSURE_TRUE(aStartNode && aEndNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (aAR == eOutsideUserSelectAll) michael@0: { michael@0: nsCOMPtr san = mHTMLEditor->FindUserSelectAllNode(aStartNode); michael@0: if (san) michael@0: return NS_OK; michael@0: michael@0: if (aStartNode != aEndNode) michael@0: { michael@0: san = mHTMLEditor->FindUserSelectAllNode(aEndNode); michael@0: if (san) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if ((aStartNode == aEndNode) && (aStartOffset == aEndOffset)) michael@0: return NS_OK; // nothing to delete michael@0: michael@0: nsresult res = NS_OK; michael@0: int32_t idx = mNodeArray.IndexOf(aStartNode); michael@0: if (idx==-1) idx = 0; // if our strarting point wasn't one of our ws text nodes, michael@0: // then just go through them from the beginning. michael@0: nsCOMPtr node; michael@0: nsCOMPtr textnode; michael@0: nsRefPtr range; michael@0: michael@0: if (aStartNode == aEndNode) michael@0: { michael@0: textnode = do_QueryInterface(aStartNode); michael@0: if (textnode) michael@0: { michael@0: return mHTMLEditor->DeleteText(textnode, (uint32_t)aStartOffset, michael@0: (uint32_t)(aEndOffset-aStartOffset)); michael@0: } michael@0: } michael@0: michael@0: int32_t count = mNodeArray.Count(); michael@0: while (idx < count) michael@0: { michael@0: node = mNodeArray[idx]; michael@0: if (!node) michael@0: break; // we ran out of ws nodes; must have been deleting to end michael@0: if (node == aStartNode) michael@0: { michael@0: textnode = do_QueryInterface(node); michael@0: uint32_t len; michael@0: textnode->GetLength(&len); michael@0: if (uint32_t(aStartOffset)DeleteText(textnode, (uint32_t)aStartOffset, len-aStartOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else if (node == aEndNode) michael@0: { michael@0: if (aEndOffset) michael@0: { michael@0: textnode = do_QueryInterface(node); michael@0: res = mHTMLEditor->DeleteText(textnode, 0, (uint32_t)aEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: break; michael@0: } michael@0: else michael@0: { michael@0: if (!range) michael@0: { michael@0: nsCOMPtr startNode = do_QueryInterface(aStartNode); michael@0: NS_ENSURE_STATE(startNode); michael@0: range = new nsRange(startNode); michael@0: res = range->SetStart(startNode, aStartOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->SetEnd(aEndNode, aEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: bool nodeBefore, nodeAfter; michael@0: nsCOMPtr content (do_QueryInterface(node)); michael@0: res = nsRange::CompareNodeToRange(content, range, &nodeBefore, &nodeAfter); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (nodeAfter) michael@0: { michael@0: break; michael@0: } michael@0: if (!nodeBefore) michael@0: { michael@0: res = mHTMLEditor->DeleteNode(node); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: mNodeArray.RemoveObject(node); michael@0: --count; michael@0: --idx; michael@0: } michael@0: } michael@0: idx++; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetCharAfter(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: int32_t idx = mNodeArray.IndexOf(aNode); michael@0: if (idx == -1) michael@0: { michael@0: // use range comparisons to get right ws node michael@0: return GetWSPointAfter(aNode, aOffset); michael@0: } michael@0: else michael@0: { michael@0: // use wspoint version of GetCharAfter() michael@0: WSPoint point(aNode,aOffset,0); michael@0: return GetCharAfter(point); michael@0: } michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetCharBefore(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: int32_t idx = mNodeArray.IndexOf(aNode); michael@0: if (idx == -1) michael@0: { michael@0: // use range comparisons to get right ws node michael@0: return GetWSPointBefore(aNode, aOffset); michael@0: } michael@0: else michael@0: { michael@0: // use wspoint version of GetCharBefore() michael@0: WSPoint point(aNode,aOffset,0); michael@0: return GetCharBefore(point); michael@0: } michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetCharAfter(const WSPoint &aPoint) michael@0: { michael@0: MOZ_ASSERT(aPoint.mTextNode); michael@0: michael@0: WSPoint outPoint; michael@0: outPoint.mTextNode = nullptr; michael@0: outPoint.mOffset = 0; michael@0: outPoint.mChar = 0; michael@0: michael@0: nsCOMPtr pointTextNode(do_QueryInterface(aPoint.mTextNode)); michael@0: int32_t idx = mNodeArray.IndexOf(pointTextNode); michael@0: if (idx == -1) { michael@0: // can't find point, but it's not an error michael@0: return outPoint; michael@0: } michael@0: int32_t numNodes = mNodeArray.Count(); michael@0: michael@0: if (uint16_t(aPoint.mOffset) < aPoint.mTextNode->TextLength()) michael@0: { michael@0: outPoint = aPoint; michael@0: outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset); michael@0: return outPoint; michael@0: } else if (idx + 1 < (int32_t)numNodes) { michael@0: nsIDOMNode* node = mNodeArray[idx+1]; michael@0: MOZ_ASSERT(node); michael@0: outPoint.mTextNode = do_QueryInterface(node); michael@0: if (!outPoint.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) { michael@0: // Not sure if this is needed, but it'll maintain the same michael@0: // functionality michael@0: outPoint.mTextNode = nullptr; michael@0: } michael@0: outPoint.mOffset = 0; michael@0: outPoint.mChar = GetCharAt(outPoint.mTextNode, 0); michael@0: } michael@0: return outPoint; michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetCharBefore(const WSPoint &aPoint) michael@0: { michael@0: MOZ_ASSERT(aPoint.mTextNode); michael@0: michael@0: WSPoint outPoint; michael@0: outPoint.mTextNode = nullptr; michael@0: outPoint.mOffset = 0; michael@0: outPoint.mChar = 0; michael@0: michael@0: nsCOMPtr pointTextNode(do_QueryInterface(aPoint.mTextNode)); michael@0: int32_t idx = mNodeArray.IndexOf(pointTextNode); michael@0: if (idx == -1) { michael@0: // can't find point, but it's not an error michael@0: return outPoint; michael@0: } michael@0: michael@0: if (aPoint.mOffset != 0) michael@0: { michael@0: outPoint = aPoint; michael@0: outPoint.mOffset--; michael@0: outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset-1); michael@0: return outPoint; michael@0: } michael@0: else if (idx) michael@0: { michael@0: nsIDOMNode* node = mNodeArray[idx-1]; michael@0: MOZ_ASSERT(node); michael@0: outPoint.mTextNode = do_QueryInterface(node); michael@0: michael@0: uint32_t len = outPoint.mTextNode->TextLength(); michael@0: michael@0: if (len) michael@0: { michael@0: outPoint.mOffset = len-1; michael@0: outPoint.mChar = GetCharAt(outPoint.mTextNode, len-1); michael@0: } michael@0: } michael@0: return outPoint; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::ConvertToNBSP(WSPoint aPoint, AreaRestriction aAR) michael@0: { michael@0: // MOOSE: this routine needs to be modified to preserve the integrity of the michael@0: // wsFragment info. michael@0: NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (aAR == eOutsideUserSelectAll) michael@0: { michael@0: nsCOMPtr domnode = do_QueryInterface(aPoint.mTextNode); michael@0: if (domnode) michael@0: { michael@0: nsCOMPtr san = mHTMLEditor->FindUserSelectAllNode(domnode); michael@0: if (san) michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr textNode(do_QueryInterface(aPoint.mTextNode)); michael@0: NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr node(do_QueryInterface(textNode)); michael@0: michael@0: // first, insert an nbsp michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString nbspStr(nbsp); michael@0: nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, aPoint.mOffset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // next, find range of ws it will replace michael@0: nsCOMPtr startNode, endNode; michael@0: int32_t startOffset=0, endOffset=0; michael@0: michael@0: GetAsciiWSBounds(eAfter, node, aPoint.mOffset+1, address_of(startNode), michael@0: &startOffset, address_of(endNode), &endOffset); michael@0: michael@0: // finally, delete that replaced ws, if any michael@0: if (startNode) michael@0: { michael@0: res = DeleteChars(startNode, startOffset, endNode, endOffset); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::GetAsciiWSBounds(int16_t aDir, nsIDOMNode *aNode, int32_t aOffset, michael@0: nsCOMPtr *outStartNode, int32_t *outStartOffset, michael@0: nsCOMPtr *outEndNode, int32_t *outEndOffset) michael@0: { michael@0: MOZ_ASSERT(aNode && outStartNode && outEndNode); michael@0: michael@0: nsCOMPtr startNode, endNode; michael@0: int32_t startOffset=0, endOffset=0; michael@0: michael@0: if (aDir & eAfter) michael@0: { michael@0: WSPoint point = GetCharAfter(aNode, aOffset); michael@0: if (point.mTextNode) { michael@0: // we found a text node, at least michael@0: endNode = do_QueryInterface(point.mTextNode); michael@0: endOffset = point.mOffset; michael@0: startNode = endNode; michael@0: startOffset = endOffset; michael@0: michael@0: // scan ahead to end of ascii ws michael@0: while (nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: endNode = do_QueryInterface(point.mTextNode); michael@0: point.mOffset++; // endOffset is _after_ ws michael@0: endOffset = point.mOffset; michael@0: point = GetCharAfter(point); michael@0: if (!point.mTextNode) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aDir & eBefore) michael@0: { michael@0: WSPoint point = GetCharBefore(aNode, aOffset); michael@0: if (point.mTextNode) { michael@0: // we found a text node, at least michael@0: startNode = do_QueryInterface(point.mTextNode); michael@0: startOffset = point.mOffset+1; michael@0: if (!endNode) michael@0: { michael@0: endNode = startNode; michael@0: endOffset = startOffset; michael@0: } michael@0: michael@0: // scan back to start of ascii ws michael@0: while (nsCRT::IsAsciiSpace(point.mChar)) michael@0: { michael@0: startNode = do_QueryInterface(point.mTextNode); michael@0: startOffset = point.mOffset; michael@0: point = GetCharBefore(point); michael@0: if (!point.mTextNode) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: *outStartNode = startNode; michael@0: *outStartOffset = startOffset; michael@0: *outEndNode = endNode; michael@0: *outEndOffset = endOffset; michael@0: } michael@0: michael@0: void michael@0: nsWSRunObject::FindRun(nsIDOMNode *aNode, int32_t aOffset, WSFragment **outRun, bool after) michael@0: { michael@0: *outRun = nullptr; michael@0: // given a dompoint, find the ws run that is before or after it, as caller needs michael@0: MOZ_ASSERT(aNode && outRun); michael@0: michael@0: WSFragment *run = mStartRun; michael@0: while (run) michael@0: { michael@0: int16_t comp = nsContentUtils::ComparePoints(aNode, aOffset, run->mStartNode, michael@0: run->mStartOffset); michael@0: if (comp <= 0) michael@0: { michael@0: if (after) michael@0: { michael@0: *outRun = run; michael@0: } michael@0: else // before michael@0: { michael@0: *outRun = nullptr; michael@0: } michael@0: return; michael@0: } michael@0: comp = nsContentUtils::ComparePoints(aNode, aOffset, michael@0: run->mEndNode, run->mEndOffset); michael@0: if (comp < 0) michael@0: { michael@0: *outRun = run; michael@0: return; michael@0: } michael@0: else if (comp == 0) michael@0: { michael@0: if (after) michael@0: { michael@0: *outRun = run->mRight; michael@0: } michael@0: else // before michael@0: { michael@0: *outRun = run; michael@0: } michael@0: return; michael@0: } michael@0: if (!run->mRight) michael@0: { michael@0: if (after) michael@0: { michael@0: *outRun = nullptr; michael@0: } michael@0: else // before michael@0: { michael@0: *outRun = run; michael@0: } michael@0: return; michael@0: } michael@0: run = run->mRight; michael@0: } michael@0: } michael@0: michael@0: char16_t michael@0: nsWSRunObject::GetCharAt(nsIContent *aTextNode, int32_t aOffset) michael@0: { michael@0: // return 0 if we can't get a char, for whatever reason michael@0: NS_ENSURE_TRUE(aTextNode, 0); michael@0: michael@0: int32_t len = int32_t(aTextNode->TextLength()); michael@0: if (aOffset < 0 || aOffset >= len) michael@0: return 0; michael@0: michael@0: return aTextNode->GetText()->CharAt(aOffset); michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetWSPointAfter(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: // Note: only to be called if aNode is not a ws node. michael@0: michael@0: // binary search on wsnodes michael@0: int32_t numNodes, firstNum, curNum, lastNum; michael@0: numNodes = mNodeArray.Count(); michael@0: michael@0: if (!numNodes) { michael@0: // do nothing if there are no nodes to search michael@0: WSPoint outPoint; michael@0: return outPoint; michael@0: } michael@0: michael@0: firstNum = 0; michael@0: curNum = numNodes/2; michael@0: lastNum = numNodes; michael@0: int16_t cmp=0; michael@0: nsCOMPtr curNode; michael@0: michael@0: // begin binary search michael@0: // we do this because we need to minimize calls to ComparePoints(), michael@0: // which is mongo expensive michael@0: while (curNum != lastNum) michael@0: { michael@0: curNode = mNodeArray[curNum]; michael@0: cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0); michael@0: if (cmp < 0) michael@0: lastNum = curNum; michael@0: else michael@0: firstNum = curNum + 1; michael@0: curNum = (lastNum - firstNum) / 2 + firstNum; michael@0: NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search"); michael@0: } michael@0: michael@0: // When the binary search is complete, we always know that the current node michael@0: // is the same as the end node, which is always past our range. Therefore, michael@0: // we've found the node immediately after the point of interest. michael@0: if (curNum == mNodeArray.Count()) { michael@0: // they asked for past our range (it's after the last node). GetCharAfter michael@0: // will do the work for us when we pass it the last index of the last node. michael@0: nsCOMPtr textNode(do_QueryInterface(mNodeArray[curNum-1])); michael@0: WSPoint point(textNode, textNode->TextLength(), 0); michael@0: return GetCharAfter(point); michael@0: } else { michael@0: // The char after the point of interest is the first character of our range. michael@0: nsCOMPtr textNode(do_QueryInterface(mNodeArray[curNum])); michael@0: WSPoint point(textNode, 0, 0); michael@0: return GetCharAfter(point); michael@0: } michael@0: } michael@0: michael@0: nsWSRunObject::WSPoint michael@0: nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: // Note: only to be called if aNode is not a ws node. michael@0: michael@0: // binary search on wsnodes michael@0: int32_t numNodes, firstNum, curNum, lastNum; michael@0: numNodes = mNodeArray.Count(); michael@0: michael@0: if (!numNodes) { michael@0: // do nothing if there are no nodes to search michael@0: WSPoint outPoint; michael@0: return outPoint; michael@0: } michael@0: michael@0: firstNum = 0; michael@0: curNum = numNodes/2; michael@0: lastNum = numNodes; michael@0: int16_t cmp=0; michael@0: nsCOMPtr curNode; michael@0: michael@0: // begin binary search michael@0: // we do this because we need to minimize calls to ComparePoints(), michael@0: // which is mongo expensive michael@0: while (curNum != lastNum) michael@0: { michael@0: curNode = mNodeArray[curNum]; michael@0: cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0); michael@0: if (cmp < 0) michael@0: lastNum = curNum; michael@0: else michael@0: firstNum = curNum + 1; michael@0: curNum = (lastNum - firstNum) / 2 + firstNum; michael@0: NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search"); michael@0: } michael@0: michael@0: // When the binary search is complete, we always know that the current node michael@0: // is the same as the end node, which is always past our range. Therefore, michael@0: // we've found the node immediately after the point of interest. michael@0: if (curNum == mNodeArray.Count()) { michael@0: // get the point before the end of the last node, we can pass the length michael@0: // of the node into GetCharBefore, and it will return the last character. michael@0: nsCOMPtr textNode(do_QueryInterface(mNodeArray[curNum - 1])); michael@0: WSPoint point(textNode, textNode->TextLength(), 0); michael@0: return GetCharBefore(point); michael@0: } else { michael@0: // we can just ask the current node for the point immediately before it, michael@0: // it will handle moving to the previous node (if any) and returning the michael@0: // appropriate character michael@0: nsCOMPtr textNode(do_QueryInterface(mNodeArray[curNum])); michael@0: WSPoint point(textNode, 0, 0); michael@0: return GetCharBefore(point); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun) michael@0: { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. michael@0: // examine what is before and after the trailing nbsp, if any. michael@0: NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: bool leftCheck = false; michael@0: bool spaceNBSP = false; michael@0: bool rightCheck = false; michael@0: michael@0: // confirm run is normalWS michael@0: if (aRun->mType != WSType::normalWS) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // first check for trailing nbsp michael@0: WSPoint thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset); michael@0: if (thePoint.mTextNode && thePoint.mChar == nbsp) { michael@0: // now check that what is to the left of it is compatible with replacing nbsp with space michael@0: WSPoint prevPoint = GetCharBefore(thePoint); michael@0: if (prevPoint.mTextNode) { michael@0: if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) leftCheck = true; michael@0: else spaceNBSP = true; michael@0: } else if (aRun->mLeftType == WSType::text) { michael@0: leftCheck = true; michael@0: } else if (aRun->mLeftType == WSType::special) { michael@0: leftCheck = true; michael@0: } michael@0: if (leftCheck || spaceNBSP) michael@0: { michael@0: // now check that what is to the right of it is compatible with replacing nbsp with space michael@0: if (aRun->mRightType == WSType::text) { michael@0: rightCheck = true; michael@0: } michael@0: if (aRun->mRightType == WSType::special) { michael@0: rightCheck = true; michael@0: } michael@0: if (aRun->mRightType == WSType::br) { michael@0: rightCheck = true; michael@0: } michael@0: if ((aRun->mRightType & WSType::block) && michael@0: IsBlockNode(nsCOMPtr(GetWSBoundingParent()))) { michael@0: // we are at a block boundary. Insert a
. Why? Well, first note that michael@0: // the br will have no visible effect since it is up against a block boundary. michael@0: // |foo

bar| renders like |foo

bar| and similarly michael@0: // |

foo

bar| renders like |

foo

bar|. What this
addition michael@0: // gets us is the ability to convert a trailing nbsp to a space. Consider: michael@0: // |foo. '|, where ' represents selection. User types space attempting michael@0: // to put 2 spaces after the end of their sentence. We used to do this as: michael@0: // |foo.  | This caused problems with soft wrapping: the nbsp michael@0: // would wrap to the next line, which looked attrocious. If you try to do: michael@0: // |foo.  | instead, the trailing space is invisible because it michael@0: // is against a block boundary. If you do: |foo.  | then michael@0: // you get an even uglier soft wrapping problem, where foo is on one line until michael@0: // you type the final space, and then "foo " jumps down to the next line. Ugh. michael@0: // The best way I can find out of this is to throw in a harmless
michael@0: // here, which allows us to do: |foo. 
|, which doesn't michael@0: // cause foo to jump lines, doesn't cause spaces to show up at the beginning of michael@0: // soft wrapped lines, and lets the user see 2 spaces when they type 2 spaces. michael@0: michael@0: nsCOMPtr brNode; michael@0: res = mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // refresh thePoint, prevPoint michael@0: thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset); michael@0: prevPoint = GetCharBefore(thePoint); michael@0: rightCheck = true; michael@0: } michael@0: } michael@0: if (leftCheck && rightCheck) michael@0: { michael@0: // now replace nbsp with space michael@0: // first, insert a space michael@0: nsCOMPtr textNode(do_QueryInterface(thePoint.mTextNode)); michael@0: NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString spaceStr(char16_t(32)); michael@0: res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that nbsp michael@0: nsCOMPtr delNode(do_QueryInterface(thePoint.mTextNode)); michael@0: res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if (!mPRE && spaceNBSP && rightCheck) // don't mess with this preformatted for now. michael@0: { michael@0: // we have a run of ascii whitespace (which will render as one space) michael@0: // followed by an nbsp (which is at the end of the whitespace run). Let's michael@0: // switch their order. This will insure that if someone types two spaces michael@0: // after a sentence, and the editor softwraps at this point, the spaces wont michael@0: // be split across lines, which looks ugly and is bad for the moose. michael@0: michael@0: nsCOMPtr startNode, endNode, thenode(do_QueryInterface(prevPoint.mTextNode)); michael@0: int32_t startOffset, endOffset; michael@0: GetAsciiWSBounds(eBoth, thenode, prevPoint.mOffset+1, address_of(startNode), michael@0: &startOffset, address_of(endNode), &endOffset); michael@0: michael@0: // delete that nbsp michael@0: nsCOMPtr delNode(do_QueryInterface(thePoint.mTextNode)); michael@0: res = DeleteChars(delNode, thePoint.mOffset, delNode, thePoint.mOffset+1); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, insert that nbsp before the ascii ws run michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString nbspStr(nbsp); michael@0: nsCOMPtr textNode(do_QueryInterface(startNode)); michael@0: res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, startOffset, true); 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: nsWSRunObject::CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. michael@0: // this routine is called when we about to make this point in the ws abut an inserted break michael@0: // or text, so we don't have to worry about what is after it. What is after it now will michael@0: // end up after the inserted object. michael@0: NS_ENSURE_TRUE(aRun && aNode, NS_ERROR_NULL_POINTER); michael@0: bool canConvert = false; michael@0: WSPoint thePoint = GetCharBefore(aNode, aOffset); michael@0: if (thePoint.mTextNode && thePoint.mChar == nbsp) { michael@0: WSPoint prevPoint = GetCharBefore(thePoint); michael@0: if (prevPoint.mTextNode) { michael@0: if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) canConvert = true; michael@0: } else if (aRun->mLeftType == WSType::text) { michael@0: canConvert = true; michael@0: } else if (aRun->mLeftType == WSType::special) { michael@0: canConvert = true; michael@0: } michael@0: } michael@0: if (canConvert) michael@0: { michael@0: // first, insert a space michael@0: nsCOMPtr textNode(do_QueryInterface(thePoint.mTextNode)); michael@0: NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString spaceStr(char16_t(32)); michael@0: nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, michael@0: thePoint.mOffset, michael@0: true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that nbsp michael@0: nsCOMPtr delNode(do_QueryInterface(thePoint.mTextNode)); michael@0: res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWSRunObject::CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, int32_t aOffset) michael@0: { michael@0: // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation michael@0: // this routine is called when we about to make this point in the ws abut an inserted michael@0: // text, so we don't have to worry about what is before it. What is before it now will michael@0: // end up before the inserted text. michael@0: bool canConvert = false; michael@0: WSPoint thePoint = GetCharAfter(aNode, aOffset); michael@0: if (thePoint.mChar == nbsp) { michael@0: WSPoint tmp = thePoint; michael@0: tmp.mOffset++; // we want to be after thePoint michael@0: WSPoint nextPoint = GetCharAfter(tmp); michael@0: if (nextPoint.mTextNode) { michael@0: if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) canConvert = true; michael@0: } else if (aRun->mRightType == WSType::text) { michael@0: canConvert = true; michael@0: } else if (aRun->mRightType == WSType::special) { michael@0: canConvert = true; michael@0: } else if (aRun->mRightType == WSType::br) { michael@0: canConvert = true; michael@0: } michael@0: } michael@0: if (canConvert) michael@0: { michael@0: // first, insert a space michael@0: nsCOMPtr textNode(do_QueryInterface(thePoint.mTextNode)); michael@0: NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor); michael@0: nsAutoString spaceStr(char16_t(32)); michael@0: nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, michael@0: thePoint.mOffset, michael@0: true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // finally, delete that nbsp michael@0: nsCOMPtr delNode(do_QueryInterface(thePoint.mTextNode)); michael@0: res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsWSRunObject::ScrubBlockBoundaryInner(nsHTMLEditor *aHTMLEd, michael@0: nsCOMPtr *aBlock, michael@0: BlockBoundary aBoundary) michael@0: { michael@0: NS_ENSURE_TRUE(aBlock && aHTMLEd, NS_ERROR_NULL_POINTER); michael@0: int32_t offset=0; michael@0: if (aBoundary == kBlockEnd) michael@0: { michael@0: uint32_t uOffset; michael@0: aHTMLEd->GetLengthOfDOMNode(*aBlock, uOffset); michael@0: offset = uOffset; michael@0: } michael@0: nsWSRunObject theWSObj(aHTMLEd, *aBlock, offset); michael@0: return theWSObj.Scrub(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsWSRunObject::Scrub() michael@0: { michael@0: WSFragment *run = mStartRun; michael@0: while (run) michael@0: { michael@0: if (run->mType & (WSType::leadingWS | WSType::trailingWS)) { michael@0: nsresult res = DeleteChars(run->mStartNode, run->mStartOffset, run->mEndNode, run->mEndOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: run = run->mRight; michael@0: } michael@0: return NS_OK; michael@0: }