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

bar| renders like |foo

bar| and similarly + // |

foo

bar| renders like |

foo

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