editor/libeditor/html/nsWSRunObject.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/Assertions.h"
     7 #include "mozilla/mozalloc.h"
     8 #include "nsAString.h"
     9 #include "nsAutoPtr.h"
    10 #include "nsCRT.h"
    11 #include "nsContentUtils.h"
    12 #include "nsDebug.h"
    13 #include "nsEditorUtils.h"
    14 #include "nsError.h"
    15 #include "nsHTMLEditor.h"
    16 #include "nsIContent.h"
    17 #include "nsIDOMCharacterData.h"
    18 #include "nsIDOMNode.h"
    19 #include "nsIDOMRange.h"
    20 #include "nsISupportsImpl.h"
    21 #include "nsRange.h"
    22 #include "nsSelectionState.h"
    23 #include "nsString.h"
    24 #include "nsTextEditUtils.h"
    25 #include "nsTextFragment.h"
    26 #include "nsWSRunObject.h"
    28 const char16_t nbsp = 160;
    30 static bool IsBlockNode(nsIDOMNode* node)
    31 {
    32   bool isBlock (false);
    33   nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
    34   return isBlock;
    35 }
    37 //- constructor / destructor -----------------------------------------------
    38 nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, int32_t aOffset) :
    39 mNode(aNode)
    40 ,mOffset(aOffset)
    41 ,mPRE(false)
    42 ,mStartNode()
    43 ,mStartOffset(0)
    44 ,mStartReason()
    45 ,mStartReasonNode()
    46 ,mEndNode()
    47 ,mEndOffset(0)
    48 ,mEndReason()
    49 ,mEndReasonNode()
    50 ,mFirstNBSPNode()
    51 ,mFirstNBSPOffset(0)
    52 ,mLastNBSPNode()
    53 ,mLastNBSPOffset(0)
    54 ,mNodeArray()
    55 ,mStartRun(nullptr)
    56 ,mEndRun(nullptr)
    57 ,mHTMLEditor(aEd)
    58 {
    59   GetWSNodes();
    60   GetRuns();
    61 }
    63 nsWSRunObject::~nsWSRunObject()
    64 {
    65   ClearRuns();
    66 }
    70 //--------------------------------------------------------------------------------------------
    71 //   public static methods
    72 //--------------------------------------------------------------------------------------------
    74 nsresult
    75 nsWSRunObject::ScrubBlockBoundary(nsHTMLEditor *aHTMLEd, 
    76                                   nsCOMPtr<nsIDOMNode> *aBlock,
    77                                   BlockBoundary aBoundary,
    78                                   int32_t *aOffset)
    79 {
    80   NS_ENSURE_TRUE(aBlock && aHTMLEd, NS_ERROR_NULL_POINTER);
    81   if ((aBoundary == kBlockStart) || (aBoundary == kBlockEnd))
    82     return ScrubBlockBoundaryInner(aHTMLEd, aBlock, aBoundary);
    84   // else we are scrubbing an outer boundary - just before or after
    85   // a block element.
    86   NS_ENSURE_TRUE(aOffset, NS_ERROR_NULL_POINTER);
    87   nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aBlock, aOffset);
    88   nsWSRunObject theWSObj(aHTMLEd, *aBlock, *aOffset);
    89   return theWSObj.Scrub();
    90 }
    92 nsresult 
    93 nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor *aHTMLEd, 
    94                                    nsIDOMNode *aLeftParent, 
    95                                    nsIDOMNode *aRightParent)
    96 {
    97   NS_ENSURE_TRUE(aLeftParent && aRightParent && aHTMLEd, NS_ERROR_NULL_POINTER);
    98   uint32_t count;
    99   aHTMLEd->GetLengthOfDOMNode(aLeftParent, count);
   100   nsWSRunObject leftWSObj(aHTMLEd, aLeftParent, count);
   101   nsWSRunObject rightWSObj(aHTMLEd, aRightParent, 0);
   103   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
   104 }
   106 nsresult 
   107 nsWSRunObject::PrepareToDeleteRange(nsHTMLEditor *aHTMLEd, 
   108                                     nsCOMPtr<nsIDOMNode> *aStartNode,
   109                                     int32_t *aStartOffset, 
   110                                     nsCOMPtr<nsIDOMNode> *aEndNode,
   111                                     int32_t *aEndOffset)
   112 {
   113   NS_ENSURE_TRUE(aStartNode && aEndNode && *aStartNode && *aEndNode && aStartOffset && aEndOffset && aHTMLEd, NS_ERROR_NULL_POINTER);
   115   nsAutoTrackDOMPoint trackerStart(aHTMLEd->mRangeUpdater, aStartNode, aStartOffset);
   116   nsAutoTrackDOMPoint trackerEnd(aHTMLEd->mRangeUpdater, aEndNode, aEndOffset);
   118   nsWSRunObject leftWSObj(aHTMLEd, *aStartNode, *aStartOffset);
   119   nsWSRunObject rightWSObj(aHTMLEd, *aEndNode, *aEndOffset);
   121   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
   122 }
   124 nsresult 
   125 nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor *aHTMLEd, 
   126                                    nsIDOMNode *aNode)
   127 {
   128   NS_ENSURE_TRUE(aNode && aHTMLEd, NS_ERROR_NULL_POINTER);
   130   int32_t offset;
   131   nsCOMPtr<nsIDOMNode> parent = aHTMLEd->GetNodeLocation(aNode, &offset);
   133   nsWSRunObject leftWSObj(aHTMLEd, parent, offset);
   134   nsWSRunObject rightWSObj(aHTMLEd, parent, offset+1);
   136   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
   137 }
   139 nsresult 
   140 nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor *aHTMLEd, 
   141                                           nsCOMPtr<nsIDOMNode> *aSplitNode, 
   142                                           int32_t *aSplitOffset)
   143 {
   144   NS_ENSURE_TRUE(aSplitNode && aSplitOffset && *aSplitNode && aHTMLEd, NS_ERROR_NULL_POINTER);
   146   nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset);
   148   nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset);
   150   return wsObj.PrepareToSplitAcrossBlocksPriv();
   151 }
   153 //--------------------------------------------------------------------------------------------
   154 //   public instance methods
   155 //--------------------------------------------------------------------------------------------
   157 nsresult 
   158 nsWSRunObject::InsertBreak(nsCOMPtr<nsIDOMNode> *aInOutParent, 
   159                            int32_t *aInOutOffset, 
   160                            nsCOMPtr<nsIDOMNode> *outBRNode, 
   161                            nsIEditor::EDirection aSelect)
   162 {
   163   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
   164   // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
   165   NS_ENSURE_TRUE(aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER);
   167   nsresult res = NS_OK;
   168   WSFragment *beforeRun, *afterRun;
   169   FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
   170   FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
   172   {
   173     // some scoping for nsAutoTrackDOMPoint.  This will track our insertion point
   174     // while we tweak any surrounding whitespace
   175     nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset);
   177     // handle any changes needed to ws run after inserted br
   178     if (!afterRun) {
   179       // don't need to do anything.  just insert break.  ws won't change.
   180     } else if (afterRun->mType & WSType::trailingWS) {
   181       // don't need to do anything.  just insert break.  ws won't change.
   182     } else if (afterRun->mType & WSType::leadingWS) {
   183       // delete the leading ws that is after insertion point.  We don't
   184       // have to (it would still not be significant after br), but it's 
   185       // just more aesthetically pleasing to.
   186       res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset,
   187                         eOutsideUserSelectAll);
   188       NS_ENSURE_SUCCESS(res, res);
   189     } else if (afterRun->mType == WSType::normalWS) {
   190       // need to determine if break at front of non-nbsp run.  if so
   191       // convert run to nbsp.
   192       WSPoint thePoint = GetCharAfter(*aInOutParent, *aInOutOffset);
   193       if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
   194         WSPoint prevPoint = GetCharBefore(thePoint);
   195         if (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) {
   196           // we are at start of non-nbsps.  convert to a single nbsp.
   197           res = ConvertToNBSP(thePoint);
   198           NS_ENSURE_SUCCESS(res, res);
   199         }
   200       }
   201     }
   203     // handle any changes needed to ws run before inserted br
   204     if (!beforeRun) {
   205       // don't need to do anything.  just insert break.  ws won't change.
   206     } else if (beforeRun->mType & WSType::leadingWS) {
   207       // don't need to do anything.  just insert break.  ws won't change.
   208     } else if (beforeRun->mType & WSType::trailingWS) {
   209       // need to delete the trailing ws that is before insertion point, because it 
   210       // would become significant after break inserted.
   211       res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset,
   212                         eOutsideUserSelectAll);
   213       NS_ENSURE_SUCCESS(res, res);
   214     } else if (beforeRun->mType == WSType::normalWS) {
   215       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
   216       res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
   217       NS_ENSURE_SUCCESS(res, res);
   218     }
   219   }
   221   // ready, aim, fire!
   222   return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, outBRNode, aSelect);
   223 }
   225 nsresult 
   226 nsWSRunObject::InsertText(const nsAString& aStringToInsert, 
   227                           nsCOMPtr<nsIDOMNode> *aInOutParent, 
   228                           int32_t *aInOutOffset,
   229                           nsIDOMDocument *aDoc)
   230 {
   231   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
   232   // meanwhile, the pre case is handled in WillInsertText in nsHTMLEditRules.cpp
   234   // MOOSE: for now, just getting the ws logic straight.  This implementation
   235   // is very slow.  Will need to replace edit rules impl with a more efficient
   236   // text sink here that does the minimal amount of searching/replacing/copying
   238   NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
   240   nsresult res = NS_OK;
   241   if (aStringToInsert.IsEmpty()) return res;
   243   // string copying sux.  
   244   nsAutoString theString(aStringToInsert);
   246   WSFragment *beforeRun, *afterRun;
   247   FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
   248   FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
   250   {
   251     // some scoping for nsAutoTrackDOMPoint.  This will track our insertion point
   252     // while we tweak any surrounding whitespace
   253     nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent, aInOutOffset);
   255     // handle any changes needed to ws run after inserted text
   256     if (!afterRun) {
   257       // don't need to do anything.  just insert text.  ws won't change.
   258     } else if (afterRun->mType & WSType::trailingWS) {
   259       // don't need to do anything.  just insert text.  ws won't change.
   260     } else if (afterRun->mType & WSType::leadingWS) {
   261       // delete the leading ws that is after insertion point, because it 
   262       // would become significant after text inserted.
   263       res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode, afterRun->mEndOffset,
   264                          eOutsideUserSelectAll);
   265       NS_ENSURE_SUCCESS(res, res);
   266     } else if (afterRun->mType == WSType::normalWS) {
   267       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
   268       res = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
   269       NS_ENSURE_SUCCESS(res, res);
   270     }
   272     // handle any changes needed to ws run before inserted text
   273     if (!beforeRun) {
   274       // don't need to do anything.  just insert text.  ws won't change.
   275     } else if (beforeRun->mType & WSType::leadingWS) {
   276       // don't need to do anything.  just insert text.  ws won't change.
   277     } else if (beforeRun->mType & WSType::trailingWS) {
   278       // need to delete the trailing ws that is before insertion point, because it 
   279       // would become significant after text inserted.
   280       res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, *aInOutParent, *aInOutOffset,
   281                         eOutsideUserSelectAll);
   282       NS_ENSURE_SUCCESS(res, res);
   283     } else if (beforeRun->mType == WSType::normalWS) {
   284       // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation
   285       res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
   286       NS_ENSURE_SUCCESS(res, res);
   287     }
   288   }
   290   // next up, tweak head and tail of string as needed.
   291   // first the head:
   292   // there are a variety of circumstances that would require us to convert a 
   293   // leading ws char into an nbsp:
   295   if (nsCRT::IsAsciiSpace(theString[0]))
   296   {
   297     // we have a leading space
   298     if (beforeRun) {
   299       if (beforeRun->mType & WSType::leadingWS) {
   300         theString.SetCharAt(nbsp, 0);
   301       } else if (beforeRun->mType & WSType::normalWS) {
   302         WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset);
   303         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
   304           theString.SetCharAt(nbsp, 0);
   305         }
   306       }
   307     } else {
   308       if (mStartReason & WSType::block || mStartReason == WSType::br) {
   309         theString.SetCharAt(nbsp, 0);
   310       }
   311     }
   312   }
   314   // then the tail
   315   uint32_t lastCharIndex = theString.Length()-1;
   317   if (nsCRT::IsAsciiSpace(theString[lastCharIndex]))
   318   {
   319     // we have a leading space
   320     if (afterRun)
   321     {
   322       if (afterRun->mType & WSType::trailingWS) {
   323         theString.SetCharAt(nbsp, lastCharIndex);
   324       } else if (afterRun->mType & WSType::normalWS) {
   325         WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset);
   326         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
   327           theString.SetCharAt(nbsp, lastCharIndex);
   328         }
   329       }
   330     }
   331     else
   332     {
   333       if (mEndReason & WSType::block) {
   334         theString.SetCharAt(nbsp, lastCharIndex);
   335       }
   336     }
   337   }
   339   // next scan string for adjacent ws and convert to nbsp/space combos
   340   // MOOSE: don't need to convert tabs here since that is done by WillInsertText() 
   341   // before we are called.  Eventually, all that logic will be pushed down into
   342   // here and made more efficient.
   343   uint32_t j;
   344   bool prevWS = false;
   345   for (j=0; j<=lastCharIndex; j++)
   346   {
   347     if (nsCRT::IsAsciiSpace(theString[j]))
   348     {
   349       if (prevWS)
   350       {
   351         theString.SetCharAt(nbsp, j-1);  // j-1 can't be negative because prevWS starts out false
   352       }
   353       else
   354       {
   355         prevWS = true;
   356       }
   357     }
   358     else
   359     {
   360       prevWS = false;
   361     }
   362   }
   364   // ready, aim, fire!
   365   res = mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc);
   366   return NS_OK;
   367 }
   369 nsresult 
   370 nsWSRunObject::DeleteWSBackward()
   371 {
   372   nsresult res = NS_OK;
   373   WSPoint point = GetCharBefore(mNode, mOffset);
   374   NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete
   376   if (mPRE)  // easy case, preformatted ws
   377   {
   378     if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp))
   379     {
   380       nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
   381       int32_t startOffset = point.mOffset;
   382       int32_t endOffset = point.mOffset+1;
   383       return DeleteChars(node, startOffset, node, endOffset);
   384     }
   385   }
   387   // callers job to insure that previous char is really ws.
   388   // If it is normal ws, we need to delete the whole run
   389   if (nsCRT::IsAsciiSpace(point.mChar))
   390   {
   391     nsCOMPtr<nsIDOMNode> startNode, endNode, node(do_QueryInterface(point.mTextNode));
   392     int32_t startOffset, endOffset;
   393     GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode),
   394                      &startOffset, address_of(endNode), &endOffset);
   396     // adjust surrounding ws
   397     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, 
   398                                               address_of(endNode), &endOffset);
   399     NS_ENSURE_SUCCESS(res, res);
   401     // finally, delete that ws
   402     return DeleteChars(startNode, startOffset, endNode, endOffset);
   403   }
   404   else if (point.mChar == nbsp)
   405   {
   406     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
   407     // adjust surrounding ws
   408     int32_t startOffset = point.mOffset;
   409     int32_t endOffset = point.mOffset+1;
   410     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, 
   411                                               address_of(node), &endOffset);
   412     NS_ENSURE_SUCCESS(res, res);
   414     // finally, delete that ws
   415     return DeleteChars(node, startOffset, node, endOffset);
   417   }
   418   return NS_OK;
   419 }
   421 nsresult 
   422 nsWSRunObject::DeleteWSForward()
   423 {
   424   nsresult res = NS_OK;
   425   WSPoint point = GetCharAfter(mNode, mOffset);
   426   NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete
   428   if (mPRE)  // easy case, preformatted ws
   429   {
   430     if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar == nbsp))
   431     {
   432       nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
   433       int32_t startOffset = point.mOffset;
   434       int32_t endOffset = point.mOffset+1;
   435       return DeleteChars(node, startOffset, node, endOffset);
   436     }
   437   }
   439   // callers job to insure that next char is really ws.
   440   // If it is normal ws, we need to delete the whole run
   441   if (nsCRT::IsAsciiSpace(point.mChar))
   442   {
   443     nsCOMPtr<nsIDOMNode> startNode, endNode, node(do_QueryInterface(point.mTextNode));
   444     int32_t startOffset, endOffset;
   445     GetAsciiWSBounds(eBoth, node, point.mOffset+1, address_of(startNode),
   446                      &startOffset, address_of(endNode), &endOffset);
   448     // adjust surrounding ws
   449     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode), &startOffset, 
   450                                               address_of(endNode), &endOffset);
   451     NS_ENSURE_SUCCESS(res, res);
   453     // finally, delete that ws
   454     return DeleteChars(startNode, startOffset, endNode, endOffset);
   455   }
   456   else if (point.mChar == nbsp)
   457   {
   458     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(point.mTextNode));
   459     // adjust surrounding ws
   460     int32_t startOffset = point.mOffset;
   461     int32_t endOffset = point.mOffset+1;
   462     res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(node), &startOffset, 
   463                                               address_of(node), &endOffset);
   464     NS_ENSURE_SUCCESS(res, res);
   466     // finally, delete that ws
   467     return DeleteChars(node, startOffset, node, endOffset);
   469   }
   470   return NS_OK;
   471 }
   473 void
   474 nsWSRunObject::PriorVisibleNode(nsIDOMNode *aNode, 
   475                                 int32_t aOffset, 
   476                                 nsCOMPtr<nsIDOMNode> *outVisNode, 
   477                                 int32_t *outVisOffset,
   478                                 WSType *outType)
   479 {
   480   // Find first visible thing before the point.  position outVisNode/outVisOffset
   481   // just _after_ that thing.  If we don't find anything return start of ws.
   482   MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
   484   *outType = WSType::none;
   485   WSFragment *run;
   486   FindRun(aNode, aOffset, &run, false);
   488   // is there a visible run there or earlier?
   489   while (run)
   490   {
   491     if (run->mType == WSType::normalWS) {
   492       WSPoint point = GetCharBefore(aNode, aOffset);
   493       if (point.mTextNode)
   494       {
   495         *outVisNode = do_QueryInterface(point.mTextNode);
   496         *outVisOffset = point.mOffset+1;
   497         if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
   498         {
   499           *outType = WSType::normalWS;
   500         }
   501         else if (!point.mChar)
   502         {
   503           // MOOSE: not possible?
   504           *outType = WSType::none;
   505         }
   506         else
   507         {
   508           *outType = WSType::text;
   509         }
   510         return;
   511       }
   512       // else if no text node then keep looking.  We should eventually fall out of loop
   513     }
   515     run = run->mLeft;
   516   }
   518   // if we get here then nothing in ws data to find.  return start reason
   519   *outVisNode = mStartReasonNode;
   520   *outVisOffset = mStartOffset;  // this really isn't meaningful if mStartReasonNode!=mStartNode
   521   *outType = mStartReason;
   522 }
   525 void
   526 nsWSRunObject::NextVisibleNode (nsIDOMNode *aNode, 
   527                                 int32_t aOffset, 
   528                                 nsCOMPtr<nsIDOMNode> *outVisNode, 
   529                                 int32_t *outVisOffset,
   530                                 WSType *outType)
   531 {
   532   // Find first visible thing after the point.  position outVisNode/outVisOffset
   533   // just _before_ that thing.  If we don't find anything return end of ws.
   534   MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
   536   WSFragment *run;
   537   FindRun(aNode, aOffset, &run, true);
   539   // is there a visible run there or later?
   540   while (run)
   541   {
   542     if (run->mType == WSType::normalWS) {
   543       WSPoint point = GetCharAfter(aNode, aOffset);
   544       if (point.mTextNode)
   545       {
   546         *outVisNode = do_QueryInterface(point.mTextNode);
   547         *outVisOffset = point.mOffset;
   548         if (nsCRT::IsAsciiSpace(point.mChar) || (point.mChar==nbsp))
   549         {
   550           *outType = WSType::normalWS;
   551         }
   552         else if (!point.mChar)
   553         {
   554           // MOOSE: not possible?
   555           *outType = WSType::none;
   556         }
   557         else
   558         {
   559           *outType = WSType::text;
   560         }
   561         return;
   562       }
   563       // else if no text node then keep looking.  We should eventually fall out of loop
   564     }
   566     run = run->mRight;
   567   }
   569   // if we get here then nothing in ws data to find.  return end reason
   570   *outVisNode = mEndReasonNode;
   571   *outVisOffset = mEndOffset; // this really isn't meaningful if mEndReasonNode!=mEndNode
   572   *outType = mEndReason;
   573 }
   575 nsresult 
   576 nsWSRunObject::AdjustWhitespace()
   577 {
   578   // this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
   579   // replacing them with regualr ascii space if possible.  Keeping things simple
   580   // for now and just trying to fix up the trailing ws in the run.
   581   if (!mLastNBSPNode) {
   582     // nothing to do!
   583     return NS_OK;
   584   }
   585   nsresult res = NS_OK;
   586   WSFragment *curRun = mStartRun;
   587   while (curRun)
   588   {
   589     // look for normal ws run
   590     if (curRun->mType == WSType::normalWS) {
   591       res = CheckTrailingNBSPOfRun(curRun);
   592       break;
   593     }
   594     curRun = curRun->mRight;
   595   }
   596   return res;
   597 }
   600 //--------------------------------------------------------------------------------------------
   601 //   protected methods
   602 //--------------------------------------------------------------------------------------------
   604 already_AddRefed<nsIDOMNode>
   605 nsWSRunObject::GetWSBoundingParent()
   606 {
   607   NS_ENSURE_TRUE(mNode, nullptr);
   608   nsCOMPtr<nsIDOMNode> wsBoundingParent = mNode;
   609   while (!IsBlockNode(wsBoundingParent))
   610   {
   611     nsCOMPtr<nsIDOMNode> parent;
   612     wsBoundingParent->GetParentNode(getter_AddRefs(parent));
   613     if (!parent || !mHTMLEditor->IsEditable(parent))
   614       break;
   615     wsBoundingParent.swap(parent);
   616   }
   617   return wsBoundingParent.forget();
   618 }
   620 nsresult
   621 nsWSRunObject::GetWSNodes()
   622 {
   623   // collect up an array of nodes that are contiguous with the insertion point
   624   // and which contain only whitespace.  Stop if you reach non-ws text or a new 
   625   // block boundary.
   626   nsresult res = NS_OK;
   628   DOMPoint start(mNode, mOffset), end(mNode, mOffset);
   629   nsCOMPtr<nsIDOMNode> wsBoundingParent = GetWSBoundingParent();
   631   // first look backwards to find preceding ws nodes
   632   if (mHTMLEditor->IsTextNode(mNode))
   633   {
   634     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNode));
   635     const nsTextFragment *textFrag = textNode->GetText();
   637     res = PrependNodeToList(mNode);
   638     NS_ENSURE_SUCCESS(res, res);
   639     if (mOffset)
   640     {
   641       int32_t pos;
   642       for (pos=mOffset-1; pos>=0; pos--)
   643       {
   644         // sanity bounds check the char position.  bug 136165
   645         if (uint32_t(pos) >= textFrag->GetLength())
   646         {
   647           NS_NOTREACHED("looking beyond end of text fragment");
   648           continue;
   649         }
   650         char16_t theChar = textFrag->CharAt(pos);
   651         if (!nsCRT::IsAsciiSpace(theChar))
   652         {
   653           if (theChar != nbsp)
   654           {
   655             mStartNode = mNode;
   656             mStartOffset = pos+1;
   657             mStartReason = WSType::text;
   658             mStartReasonNode = mNode;
   659             break;
   660           }
   661           // as we look backwards update our earliest found nbsp
   662           mFirstNBSPNode = mNode;
   663           mFirstNBSPOffset = pos;
   664           // also keep track of latest nbsp so far
   665           if (!mLastNBSPNode)
   666           {
   667             mLastNBSPNode = mNode;
   668             mLastNBSPOffset = pos;
   669           }
   670         }
   671         start.SetPoint(mNode,pos);
   672       }
   673     }
   674   }
   676   nsCOMPtr<nsIDOMNode> priorNode;
   677   while (!mStartNode)
   678   {
   679     // we haven't found the start of ws yet.  Keep looking
   680     res = GetPreviousWSNode(start, wsBoundingParent, address_of(priorNode));
   681     NS_ENSURE_SUCCESS(res, res);
   682     if (priorNode)
   683     {
   684       if (IsBlockNode(priorNode))
   685       {
   686         start.GetPoint(mStartNode, mStartOffset);
   687         mStartReason = WSType::otherBlock;
   688         mStartReasonNode = priorNode;
   689       }
   690       else if (mHTMLEditor->IsTextNode(priorNode))
   691       {
   692         res = PrependNodeToList(priorNode);
   693         NS_ENSURE_SUCCESS(res, res);
   694         nsCOMPtr<nsIContent> textNode(do_QueryInterface(priorNode));
   695         const nsTextFragment *textFrag;
   696         if (!textNode || !(textFrag = textNode->GetText())) {
   697           return NS_ERROR_NULL_POINTER;
   698         }
   699         uint32_t len = textNode->TextLength();
   701         if (len < 1)
   702         {
   703           // Zero length text node. Set start point to it
   704           // so we can get past it!
   705           start.SetPoint(priorNode,0);
   706         }
   707         else
   708         {
   709           int32_t pos;
   710           for (pos=len-1; pos>=0; pos--)
   711           {
   712             // sanity bounds check the char position.  bug 136165
   713             if (uint32_t(pos) >= textFrag->GetLength())
   714             {
   715               NS_NOTREACHED("looking beyond end of text fragment");
   716               continue;
   717             }
   718             char16_t theChar = textFrag->CharAt(pos);
   719             if (!nsCRT::IsAsciiSpace(theChar))
   720             {
   721               if (theChar != nbsp)
   722               {
   723                 mStartNode = priorNode;
   724                 mStartOffset = pos+1;
   725                 mStartReason = WSType::text;
   726                 mStartReasonNode = priorNode;
   727                 break;
   728               }
   729               // as we look backwards update our earliest found nbsp
   730               mFirstNBSPNode = priorNode;
   731               mFirstNBSPOffset = pos;
   732               // also keep track of latest nbsp so far
   733               if (!mLastNBSPNode)
   734               {
   735                 mLastNBSPNode = priorNode;
   736                 mLastNBSPOffset = pos;
   737               }
   738             }
   739             start.SetPoint(priorNode,pos);
   740           }
   741         }
   742       }
   743       else
   744       {
   745         // it's a break or a special node, like <img>, that is not a block and not
   746         // a break but still serves as a terminator to ws runs.
   747         start.GetPoint(mStartNode, mStartOffset);
   748         if (nsTextEditUtils::IsBreak(priorNode))
   749           mStartReason = WSType::br;
   750         else
   751           mStartReason = WSType::special;
   752         mStartReasonNode = priorNode;
   753       }
   754     }
   755     else
   756     {
   757       // no prior node means we exhausted wsBoundingParent
   758       start.GetPoint(mStartNode, mStartOffset);
   759       mStartReason = WSType::thisBlock;
   760       mStartReasonNode = wsBoundingParent;
   761     } 
   762   }
   764   // then look ahead to find following ws nodes
   765   if (mHTMLEditor->IsTextNode(mNode))
   766   {
   767     // don't need to put it on list. it already is from code above
   768     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNode));
   769     const nsTextFragment *textFrag = textNode->GetText();
   771     uint32_t len = textNode->TextLength();
   772     if (uint16_t(mOffset)<len)
   773     {
   774       int32_t pos;
   775       for (pos=mOffset; uint32_t(pos)<len; pos++)
   776       {
   777         // sanity bounds check the char position.  bug 136165
   778         if ((pos<0) || (uint32_t(pos)>=textFrag->GetLength()))
   779         {
   780           NS_NOTREACHED("looking beyond end of text fragment");
   781           continue;
   782         }
   783         char16_t theChar = textFrag->CharAt(pos);
   784         if (!nsCRT::IsAsciiSpace(theChar))
   785         {
   786           if (theChar != nbsp)
   787           {
   788             mEndNode = mNode;
   789             mEndOffset = pos;
   790             mEndReason = WSType::text;
   791             mEndReasonNode = mNode;
   792             break;
   793           }
   794           // as we look forwards update our latest found nbsp
   795           mLastNBSPNode = mNode;
   796           mLastNBSPOffset = pos;
   797           // also keep track of earliest nbsp so far
   798           if (!mFirstNBSPNode)
   799           {
   800             mFirstNBSPNode = mNode;
   801             mFirstNBSPOffset = pos;
   802           }
   803         }
   804         end.SetPoint(mNode,pos+1);
   805       }
   806     }
   807   }
   809   nsCOMPtr<nsIDOMNode> nextNode;
   810   while (!mEndNode)
   811   {
   812     // we haven't found the end of ws yet.  Keep looking
   813     res = GetNextWSNode(end, wsBoundingParent, address_of(nextNode));
   814     NS_ENSURE_SUCCESS(res, res);
   815     if (nextNode)
   816     {
   817       if (IsBlockNode(nextNode))
   818       {
   819         // we encountered a new block.  therefore no more ws.
   820         end.GetPoint(mEndNode, mEndOffset);
   821         mEndReason = WSType::otherBlock;
   822         mEndReasonNode = nextNode;
   823       }
   824       else if (mHTMLEditor->IsTextNode(nextNode))
   825       {
   826         res = AppendNodeToList(nextNode);
   827         NS_ENSURE_SUCCESS(res, res);
   828         nsCOMPtr<nsIContent> textNode(do_QueryInterface(nextNode));
   829         const nsTextFragment *textFrag;
   830         if (!textNode || !(textFrag = textNode->GetText())) {
   831           return NS_ERROR_NULL_POINTER;
   832         }
   833         uint32_t len = textNode->TextLength();
   835         if (len < 1)
   836         {
   837           // Zero length text node. Set end point to it
   838           // so we can get past it!
   839           end.SetPoint(nextNode,0);
   840         }
   841         else
   842         {
   843           int32_t pos;
   844           for (pos=0; uint32_t(pos)<len; pos++)
   845           {
   846             // sanity bounds check the char position.  bug 136165
   847             if (uint32_t(pos) >= textFrag->GetLength())
   848             {
   849               NS_NOTREACHED("looking beyond end of text fragment");
   850               continue;
   851             }
   852             char16_t theChar = textFrag->CharAt(pos);
   853             if (!nsCRT::IsAsciiSpace(theChar))
   854             {
   855               if (theChar != nbsp)
   856               {
   857                 mEndNode = nextNode;
   858                 mEndOffset = pos;
   859                 mEndReason = WSType::text;
   860                 mEndReasonNode = nextNode;
   861                 break;
   862               }
   863               // as we look forwards update our latest found nbsp
   864               mLastNBSPNode = nextNode;
   865               mLastNBSPOffset = pos;
   866               // also keep track of earliest nbsp so far
   867               if (!mFirstNBSPNode)
   868               {
   869                 mFirstNBSPNode = nextNode;
   870                 mFirstNBSPOffset = pos;
   871               }
   872             }
   873             end.SetPoint(nextNode,pos+1);
   874           }
   875         }
   876       }
   877       else
   878       {
   879         // we encountered a break or a special node, like <img>, 
   880         // that is not a block and not a break but still 
   881         // serves as a terminator to ws runs.
   882         end.GetPoint(mEndNode, mEndOffset);
   883         if (nsTextEditUtils::IsBreak(nextNode))
   884           mEndReason = WSType::br;
   885         else
   886           mEndReason = WSType::special;
   887         mEndReasonNode = nextNode;
   888       }
   889     }
   890     else
   891     {
   892       // no next node means we exhausted wsBoundingParent
   893       end.GetPoint(mEndNode, mEndOffset);
   894       mEndReason = WSType::thisBlock;
   895       mEndReasonNode = wsBoundingParent;
   896     } 
   897   }
   899   return NS_OK;
   900 }
   902 void
   903 nsWSRunObject::GetRuns()
   904 {
   905   ClearRuns();
   907   // handle some easy cases first
   908   mHTMLEditor->IsPreformatted(mNode, &mPRE);
   909   // if it's preformatedd, or if we are surrounded by text or special, it's all one
   910   // big normal ws run
   911   if (mPRE ||
   912       ((mStartReason == WSType::text || mStartReason == WSType::special) &&
   913        (mEndReason == WSType::text || mEndReason == WSType::special ||
   914         mEndReason == WSType::br))) {
   915     MakeSingleWSRun(WSType::normalWS);
   916     return;
   917   }
   919   // if we are before or after a block (or after a break), and there are no nbsp's,
   920   // then it's all non-rendering ws.
   921   if (!mFirstNBSPNode && !mLastNBSPNode &&
   922       ((mStartReason & WSType::block) || mStartReason == WSType::br ||
   923        (mEndReason & WSType::block))) {
   924     WSType wstype;
   925     if ((mStartReason & WSType::block) || mStartReason == WSType::br) {
   926       wstype = WSType::leadingWS;
   927     }
   928     if (mEndReason & WSType::block) {
   929       wstype |= WSType::trailingWS;
   930     }
   931     MakeSingleWSRun(wstype);
   932     return;
   933   }
   935   // otherwise a little trickier.  shucks.
   936   mStartRun = new WSFragment();
   937   mStartRun->mStartNode = mStartNode;
   938   mStartRun->mStartOffset = mStartOffset;
   940   if (mStartReason & WSType::block || mStartReason == WSType::br) {
   941     // set up mStartRun
   942     mStartRun->mType = WSType::leadingWS;
   943     mStartRun->mEndNode = mFirstNBSPNode;
   944     mStartRun->mEndOffset = mFirstNBSPOffset;
   945     mStartRun->mLeftType = mStartReason;
   946     mStartRun->mRightType = WSType::normalWS;
   948     // set up next run
   949     WSFragment *normalRun = new WSFragment();
   950     mStartRun->mRight = normalRun;
   951     normalRun->mType = WSType::normalWS;
   952     normalRun->mStartNode = mFirstNBSPNode;
   953     normalRun->mStartOffset = mFirstNBSPOffset;
   954     normalRun->mLeftType = WSType::leadingWS;
   955     normalRun->mLeft = mStartRun;
   956     if (mEndReason != WSType::block) {
   957       // then no trailing ws.  this normal run ends the overall ws run.
   958       normalRun->mRightType = mEndReason;
   959       normalRun->mEndNode   = mEndNode;
   960       normalRun->mEndOffset = mEndOffset;
   961       mEndRun = normalRun;
   962     }
   963     else
   964     {
   965       // we might have trailing ws.
   966       // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
   967       // will point to it, even though in general start/end points not
   968       // guaranteed to be in text nodes.
   969       if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
   970       {
   971         // normal ws runs right up to adjacent block (nbsp next to block)
   972         normalRun->mRightType = mEndReason;
   973         normalRun->mEndNode   = mEndNode;
   974         normalRun->mEndOffset = mEndOffset;
   975         mEndRun = normalRun;
   976       }
   977       else
   978       {
   979         normalRun->mEndNode = mLastNBSPNode;
   980         normalRun->mEndOffset = mLastNBSPOffset+1;
   981         normalRun->mRightType = WSType::trailingWS;
   983         // set up next run
   984         WSFragment *lastRun = new WSFragment();
   985         lastRun->mType = WSType::trailingWS;
   986         lastRun->mStartNode = mLastNBSPNode;
   987         lastRun->mStartOffset = mLastNBSPOffset+1;
   988         lastRun->mEndNode = mEndNode;
   989         lastRun->mEndOffset = mEndOffset;
   990         lastRun->mLeftType = WSType::normalWS;
   991         lastRun->mLeft = normalRun;
   992         lastRun->mRightType = mEndReason;
   993         mEndRun = lastRun;
   994         normalRun->mRight = lastRun;
   995       }
   996     }
   997   } else {
   998     // mStartReason is not WSType::block or WSType::br; set up mStartRun
   999     mStartRun->mType = WSType::normalWS;
  1000     mStartRun->mEndNode = mLastNBSPNode;
  1001     mStartRun->mEndOffset = mLastNBSPOffset+1;
  1002     mStartRun->mLeftType = mStartReason;
  1004     // we might have trailing ws.
  1005     // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
  1006     // will point to it, even though in general start/end points not
  1007     // guaranteed to be in text nodes.
  1008     if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
  1010       mStartRun->mRightType = mEndReason;
  1011       mStartRun->mEndNode   = mEndNode;
  1012       mStartRun->mEndOffset = mEndOffset;
  1013       mEndRun = mStartRun;
  1015     else
  1017       // set up next run
  1018       WSFragment *lastRun = new WSFragment();
  1019       lastRun->mType = WSType::trailingWS;
  1020       lastRun->mStartNode = mLastNBSPNode;
  1021       lastRun->mStartOffset = mLastNBSPOffset+1;
  1022       lastRun->mLeftType = WSType::normalWS;
  1023       lastRun->mLeft = mStartRun;
  1024       lastRun->mRightType = mEndReason;
  1025       mEndRun = lastRun;
  1026       mStartRun->mRight = lastRun;
  1027       mStartRun->mRightType = WSType::trailingWS;
  1032 void
  1033 nsWSRunObject::ClearRuns()
  1035   WSFragment *tmp, *run;
  1036   run = mStartRun;
  1037   while (run)
  1039     tmp = run->mRight;
  1040     delete run;
  1041     run = tmp;
  1043   mStartRun = 0;
  1044   mEndRun = 0;
  1047 void
  1048 nsWSRunObject::MakeSingleWSRun(WSType aType)
  1050   mStartRun = new WSFragment();
  1052   mStartRun->mStartNode   = mStartNode;
  1053   mStartRun->mStartOffset = mStartOffset;
  1054   mStartRun->mType        = aType;
  1055   mStartRun->mEndNode     = mEndNode;
  1056   mStartRun->mEndOffset   = mEndOffset;
  1057   mStartRun->mLeftType    = mStartReason;
  1058   mStartRun->mRightType   = mEndReason;
  1060   mEndRun  = mStartRun;
  1063 nsresult 
  1064 nsWSRunObject::PrependNodeToList(nsIDOMNode *aNode)
  1066   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
  1067   if (!mNodeArray.InsertObjectAt(aNode, 0))
  1068     return NS_ERROR_FAILURE;
  1069   return NS_OK;
  1072 nsresult 
  1073 nsWSRunObject::AppendNodeToList(nsIDOMNode *aNode)
  1075   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
  1076   if (!mNodeArray.AppendObject(aNode))
  1077     return NS_ERROR_FAILURE;
  1078   return NS_OK;
  1081 nsresult 
  1082 nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode, 
  1083                                  nsIDOMNode *aBlockParent, 
  1084                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
  1086   // can't really recycle various getnext/prior routines because we
  1087   // have special needs here.  Need to step into inline containers but
  1088   // not block containers.
  1089   NS_ENSURE_TRUE(aStartNode && aBlockParent && aPriorNode, NS_ERROR_NULL_POINTER);
  1091   nsresult res = aStartNode->GetPreviousSibling(getter_AddRefs(*aPriorNode));
  1092   NS_ENSURE_SUCCESS(res, res);
  1093   nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
  1094   while (!*aPriorNode)
  1096     // we have exhausted nodes in parent of aStartNode.
  1097     res = curNode->GetParentNode(getter_AddRefs(temp));
  1098     NS_ENSURE_SUCCESS(res, res);
  1099     NS_ENSURE_TRUE(temp, NS_ERROR_NULL_POINTER);
  1100     if (temp == aBlockParent)
  1102       // we have exhausted nodes in the block parent.  The convention here is to return null.
  1103       *aPriorNode = nullptr;
  1104       return NS_OK;
  1106     // we have a parent: look for previous sibling
  1107     res = temp->GetPreviousSibling(getter_AddRefs(*aPriorNode));
  1108     NS_ENSURE_SUCCESS(res, res);
  1109     curNode = temp;
  1111   // we have a prior node.  If it's a block, return it.
  1112   if (IsBlockNode(*aPriorNode))
  1113     return NS_OK;
  1114   // else if it's a container, get deep rightmost child
  1115   else if (mHTMLEditor->IsContainer(*aPriorNode))
  1117     temp = mHTMLEditor->GetRightmostChild(*aPriorNode);
  1118     if (temp)
  1119       *aPriorNode = temp;
  1120     return NS_OK;
  1122   // else return the node itself
  1123   return NS_OK;
  1126 nsresult 
  1127 nsWSRunObject::GetPreviousWSNode(DOMPoint aPoint,
  1128                                  nsIDOMNode *aBlockParent, 
  1129                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
  1131   nsCOMPtr<nsIDOMNode> node;
  1132   int32_t offset;
  1133   aPoint.GetPoint(node, offset);
  1134   return GetPreviousWSNode(node,offset,aBlockParent,aPriorNode);
  1137 nsresult 
  1138 nsWSRunObject::GetPreviousWSNode(nsIDOMNode *aStartNode,
  1139                                  int32_t aOffset,
  1140                                  nsIDOMNode *aBlockParent, 
  1141                                  nsCOMPtr<nsIDOMNode> *aPriorNode)
  1143   // can't really recycle various getnext/prior routines because we
  1144   // have special needs here.  Need to step into inline containers but
  1145   // not block containers.
  1146   NS_ENSURE_TRUE(aStartNode && aBlockParent && aPriorNode, NS_ERROR_NULL_POINTER);
  1147   *aPriorNode = 0;
  1149   if (mHTMLEditor->IsTextNode(aStartNode))
  1150     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
  1151   if (!mHTMLEditor->IsContainer(aStartNode))
  1152     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
  1154   if (!aOffset)
  1156     if (aStartNode==aBlockParent)
  1158       // we are at start of the block.
  1159       return NS_OK;
  1162     // we are at start of non-block container
  1163     return GetPreviousWSNode(aStartNode, aBlockParent, aPriorNode);
  1166   nsCOMPtr<nsIContent> startContent( do_QueryInterface(aStartNode) );
  1167   NS_ENSURE_STATE(startContent);
  1168   nsIContent *priorContent = startContent->GetChildAt(aOffset - 1);
  1169   NS_ENSURE_TRUE(priorContent, NS_ERROR_NULL_POINTER);
  1170   *aPriorNode = do_QueryInterface(priorContent);
  1171   // we have a prior node.  If it's a block, return it.
  1172   if (IsBlockNode(*aPriorNode))
  1173     return NS_OK;
  1174   // else if it's a container, get deep rightmost child
  1175   else if (mHTMLEditor->IsContainer(*aPriorNode))
  1177     nsCOMPtr<nsIDOMNode> temp;
  1178     temp = mHTMLEditor->GetRightmostChild(*aPriorNode);
  1179     if (temp)
  1180       *aPriorNode = temp;
  1181     return NS_OK;
  1183   // else return the node itself
  1184   return NS_OK;
  1187 nsresult 
  1188 nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode, 
  1189                              nsIDOMNode *aBlockParent, 
  1190                              nsCOMPtr<nsIDOMNode> *aNextNode)
  1192   // can't really recycle various getnext/prior routines because we
  1193   // have special needs here.  Need to step into inline containers but
  1194   // not block containers.
  1195   NS_ENSURE_TRUE(aStartNode && aBlockParent && aNextNode, NS_ERROR_NULL_POINTER);
  1197   *aNextNode = 0;
  1198   nsresult res = aStartNode->GetNextSibling(getter_AddRefs(*aNextNode));
  1199   NS_ENSURE_SUCCESS(res, res);
  1200   nsCOMPtr<nsIDOMNode> temp, curNode = aStartNode;
  1201   while (!*aNextNode)
  1203     // we have exhausted nodes in parent of aStartNode.
  1204     res = curNode->GetParentNode(getter_AddRefs(temp));
  1205     NS_ENSURE_SUCCESS(res, res);
  1206     NS_ENSURE_TRUE(temp, NS_ERROR_NULL_POINTER);
  1207     if (temp == aBlockParent)
  1209       // we have exhausted nodes in the block parent.  The convention
  1210       // here is to return null.
  1211       *aNextNode = nullptr;
  1212       return NS_OK;
  1214     // we have a parent: look for next sibling
  1215     res = temp->GetNextSibling(getter_AddRefs(*aNextNode));
  1216     NS_ENSURE_SUCCESS(res, res);
  1217     curNode = temp;
  1219   // we have a next node.  If it's a block, return it.
  1220   if (IsBlockNode(*aNextNode))
  1221     return NS_OK;
  1222   // else if it's a container, get deep leftmost child
  1223   else if (mHTMLEditor->IsContainer(*aNextNode))
  1225     temp = mHTMLEditor->GetLeftmostChild(*aNextNode);
  1226     if (temp)
  1227       *aNextNode = temp;
  1228     return NS_OK;
  1230   // else return the node itself
  1231   return NS_OK;
  1234 nsresult 
  1235 nsWSRunObject::GetNextWSNode(DOMPoint aPoint,
  1236                              nsIDOMNode *aBlockParent, 
  1237                              nsCOMPtr<nsIDOMNode> *aNextNode)
  1239   nsCOMPtr<nsIDOMNode> node;
  1240   int32_t offset;
  1241   aPoint.GetPoint(node, offset);
  1242   return GetNextWSNode(node,offset,aBlockParent,aNextNode);
  1245 nsresult 
  1246 nsWSRunObject::GetNextWSNode(nsIDOMNode *aStartNode,
  1247                              int32_t aOffset,
  1248                              nsIDOMNode *aBlockParent, 
  1249                              nsCOMPtr<nsIDOMNode> *aNextNode)
  1251   // can't really recycle various getnext/prior routines because we have special needs
  1252   // here.  Need to step into inline containers but not block containers.
  1253   NS_ENSURE_TRUE(aStartNode && aBlockParent && aNextNode, NS_ERROR_NULL_POINTER);
  1254   *aNextNode = 0;
  1256   if (mHTMLEditor->IsTextNode(aStartNode))
  1257     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
  1258   if (!mHTMLEditor->IsContainer(aStartNode))
  1259     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
  1261   nsCOMPtr<nsIContent> startContent( do_QueryInterface(aStartNode) );
  1262   NS_ENSURE_STATE(startContent);
  1263   nsIContent *nextContent = startContent->GetChildAt(aOffset);
  1264   if (!nextContent)
  1266     if (aStartNode==aBlockParent)
  1268       // we are at end of the block.
  1269       return NS_OK;
  1272     // we are at end of non-block container
  1273     return GetNextWSNode(aStartNode, aBlockParent, aNextNode);
  1276   *aNextNode = do_QueryInterface(nextContent);
  1277   // we have a next node.  If it's a block, return it.
  1278   if (IsBlockNode(*aNextNode))
  1279     return NS_OK;
  1280   // else if it's a container, get deep leftmost child
  1281   else if (mHTMLEditor->IsContainer(*aNextNode))
  1283     nsCOMPtr<nsIDOMNode> temp;
  1284     temp = mHTMLEditor->GetLeftmostChild(*aNextNode);
  1285     if (temp)
  1286       *aNextNode = temp;
  1287     return NS_OK;
  1289   // else return the node itself
  1290   return NS_OK;
  1293 nsresult 
  1294 nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
  1296   // this routine adjust whitespace before *this* and after aEndObject
  1297   // in preperation for the two areas to become adjacent after the 
  1298   // intervening content is deleted.  It's overly agressive right
  1299   // now.  There might be a block boundary remaining between them after
  1300   // the deletion, in which case these adjstments are unneeded (though
  1301   // I don't think they can ever be harmful?)
  1303   NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER);
  1304   nsresult res = NS_OK;
  1306   // get the runs before and after selection
  1307   WSFragment *beforeRun, *afterRun;
  1308   FindRun(mNode, mOffset, &beforeRun, false);
  1309   aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, true);
  1311   // trim after run of any leading ws
  1312   if (afterRun && (afterRun->mType & WSType::leadingWS)) {
  1313     res = aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset, afterRun->mEndNode, afterRun->mEndOffset,
  1314                                   eOutsideUserSelectAll);
  1315     NS_ENSURE_SUCCESS(res, res);
  1317   // adjust normal ws in afterRun if needed
  1318   if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
  1319     if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
  1320         (!beforeRun && ((mStartReason & WSType::block) ||
  1321                         mStartReason == WSType::br))) {
  1322       // make sure leading char of following ws is an nbsp, so that it will show up
  1323       WSPoint point = aEndObject->GetCharAfter(aEndObject->mNode,
  1324                                                aEndObject->mOffset);
  1325       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
  1327         res = aEndObject->ConvertToNBSP(point, eOutsideUserSelectAll);
  1328         NS_ENSURE_SUCCESS(res, res);
  1332   // trim before run of any trailing ws
  1333   if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
  1334     res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset, mNode, mOffset,
  1335                       eOutsideUserSelectAll);
  1336     NS_ENSURE_SUCCESS(res, res);
  1337   } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) {
  1338     if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
  1339         (afterRun && afterRun->mType == WSType::normalWS) ||
  1340         (!afterRun && (aEndObject->mEndReason & WSType::block))) {
  1341       // make sure trailing char of starting ws is an nbsp, so that it will show up
  1342       WSPoint point = GetCharBefore(mNode, mOffset);
  1343       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
  1345         nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
  1346         int32_t wsStartOffset, wsEndOffset;
  1347         GetAsciiWSBounds(eBoth, mNode, mOffset, address_of(wsStartNode),
  1348                          &wsStartOffset, address_of(wsEndNode), &wsEndOffset);
  1349         point.mTextNode = do_QueryInterface(wsStartNode);
  1350         if (!point.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
  1351           // Not sure if this is needed, but it'll maintain the same
  1352           // functionality
  1353           point.mTextNode = nullptr;
  1355         point.mOffset = wsStartOffset;
  1356         res = ConvertToNBSP(point, eOutsideUserSelectAll);
  1357         NS_ENSURE_SUCCESS(res, res);
  1361   return res;
  1364 nsresult 
  1365 nsWSRunObject::PrepareToSplitAcrossBlocksPriv()
  1367   // used to prepare ws to be split across two blocks.  The main issue 
  1368   // here is make sure normalWS doesn't end up becoming non-significant
  1369   // leading or trailing ws after the split.
  1370   nsresult res = NS_OK;
  1372   // get the runs before and after selection
  1373   WSFragment *beforeRun, *afterRun;
  1374   FindRun(mNode, mOffset, &beforeRun, false);
  1375   FindRun(mNode, mOffset, &afterRun, true);
  1377   // adjust normal ws in afterRun if needed
  1378   if (afterRun && afterRun->mType == WSType::normalWS) {
  1379     // make sure leading char of following ws is an nbsp, so that it will show up
  1380     WSPoint point = GetCharAfter(mNode, mOffset);
  1381     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
  1383       res = ConvertToNBSP(point);
  1384       NS_ENSURE_SUCCESS(res, res);
  1388   // adjust normal ws in beforeRun if needed
  1389   if (beforeRun && beforeRun->mType == WSType::normalWS) {
  1390     // make sure trailing char of starting ws is an nbsp, so that it will show up
  1391     WSPoint point = GetCharBefore(mNode, mOffset);
  1392     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
  1394       nsCOMPtr<nsIDOMNode> wsStartNode, wsEndNode;
  1395       int32_t wsStartOffset, wsEndOffset;
  1396       GetAsciiWSBounds(eBoth, mNode, mOffset, address_of(wsStartNode),
  1397                        &wsStartOffset, address_of(wsEndNode), &wsEndOffset);
  1398       point.mTextNode = do_QueryInterface(wsStartNode);
  1399       if (!point.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
  1400         // Not sure if this is needed, but it'll maintain the same
  1401         // functionality
  1402         point.mTextNode = nullptr;
  1404       point.mOffset = wsStartOffset;
  1405       res = ConvertToNBSP(point);
  1406       NS_ENSURE_SUCCESS(res, res);
  1409   return res;
  1412 nsresult 
  1413 nsWSRunObject::DeleteChars(nsIDOMNode *aStartNode, int32_t aStartOffset, 
  1414                            nsIDOMNode *aEndNode, int32_t aEndOffset,
  1415                            AreaRestriction aAR)
  1417   // MOOSE: this routine needs to be modified to preserve the integrity of the
  1418   // wsFragment info.
  1419   NS_ENSURE_TRUE(aStartNode && aEndNode, NS_ERROR_NULL_POINTER);
  1421   if (aAR == eOutsideUserSelectAll)
  1423     nsCOMPtr<nsIDOMNode> san = mHTMLEditor->FindUserSelectAllNode(aStartNode);
  1424     if (san)
  1425       return NS_OK;
  1427     if (aStartNode != aEndNode)
  1429       san = mHTMLEditor->FindUserSelectAllNode(aEndNode);
  1430       if (san)
  1431         return NS_OK;
  1435   if ((aStartNode == aEndNode) && (aStartOffset == aEndOffset))
  1436     return NS_OK;  // nothing to delete
  1438   nsresult res = NS_OK;
  1439   int32_t idx = mNodeArray.IndexOf(aStartNode);
  1440   if (idx==-1) idx = 0; // if our strarting point wasn't one of our ws text nodes,
  1441                         // then just go through them from the beginning.
  1442   nsCOMPtr<nsIDOMNode> node;
  1443   nsCOMPtr<nsIDOMCharacterData> textnode;
  1444   nsRefPtr<nsRange> range;
  1446   if (aStartNode == aEndNode)
  1448     textnode = do_QueryInterface(aStartNode);
  1449     if (textnode)
  1451       return mHTMLEditor->DeleteText(textnode, (uint32_t)aStartOffset, 
  1452                                      (uint32_t)(aEndOffset-aStartOffset));
  1456   int32_t count = mNodeArray.Count();
  1457   while (idx < count)
  1459     node = mNodeArray[idx];
  1460     if (!node)
  1461       break;  // we ran out of ws nodes; must have been deleting to end
  1462     if (node == aStartNode)
  1464       textnode = do_QueryInterface(node);
  1465       uint32_t len;
  1466       textnode->GetLength(&len);
  1467       if (uint32_t(aStartOffset)<len)
  1469         res = mHTMLEditor->DeleteText(textnode, (uint32_t)aStartOffset, len-aStartOffset);
  1470         NS_ENSURE_SUCCESS(res, res);
  1473     else if (node == aEndNode)
  1475       if (aEndOffset)
  1477         textnode = do_QueryInterface(node);
  1478         res = mHTMLEditor->DeleteText(textnode, 0, (uint32_t)aEndOffset);
  1479         NS_ENSURE_SUCCESS(res, res);
  1481       break;
  1483     else
  1485       if (!range)
  1487         nsCOMPtr<nsINode> startNode = do_QueryInterface(aStartNode);
  1488         NS_ENSURE_STATE(startNode);
  1489         range = new nsRange(startNode);
  1490         res = range->SetStart(startNode, aStartOffset);
  1491         NS_ENSURE_SUCCESS(res, res);
  1492         res = range->SetEnd(aEndNode, aEndOffset);
  1493         NS_ENSURE_SUCCESS(res, res);
  1495       bool nodeBefore, nodeAfter;
  1496       nsCOMPtr<nsIContent> content (do_QueryInterface(node));
  1497       res = nsRange::CompareNodeToRange(content, range, &nodeBefore, &nodeAfter);
  1498       NS_ENSURE_SUCCESS(res, res);
  1499       if (nodeAfter)
  1501         break;
  1503       if (!nodeBefore)
  1505         res = mHTMLEditor->DeleteNode(node);
  1506         NS_ENSURE_SUCCESS(res, res);
  1507         mNodeArray.RemoveObject(node);
  1508         --count;
  1509         --idx;
  1512     idx++;
  1514   return res;
  1517 nsWSRunObject::WSPoint
  1518 nsWSRunObject::GetCharAfter(nsIDOMNode *aNode, int32_t aOffset)
  1520   MOZ_ASSERT(aNode);
  1522   int32_t idx = mNodeArray.IndexOf(aNode);
  1523   if (idx == -1) 
  1525     // use range comparisons to get right ws node
  1526     return GetWSPointAfter(aNode, aOffset);
  1528   else
  1530     // use wspoint version of GetCharAfter()
  1531     WSPoint point(aNode,aOffset,0);
  1532     return GetCharAfter(point);
  1536 nsWSRunObject::WSPoint
  1537 nsWSRunObject::GetCharBefore(nsIDOMNode *aNode, int32_t aOffset)
  1539   MOZ_ASSERT(aNode);
  1541   int32_t idx = mNodeArray.IndexOf(aNode);
  1542   if (idx == -1) 
  1544     // use range comparisons to get right ws node
  1545     return GetWSPointBefore(aNode, aOffset);
  1547   else
  1549     // use wspoint version of GetCharBefore()
  1550     WSPoint point(aNode,aOffset,0);
  1551     return GetCharBefore(point);
  1555 nsWSRunObject::WSPoint
  1556 nsWSRunObject::GetCharAfter(const WSPoint &aPoint)
  1558   MOZ_ASSERT(aPoint.mTextNode);
  1560   WSPoint outPoint;
  1561   outPoint.mTextNode = nullptr;
  1562   outPoint.mOffset = 0;
  1563   outPoint.mChar = 0;
  1565   nsCOMPtr<nsIDOMNode> pointTextNode(do_QueryInterface(aPoint.mTextNode));
  1566   int32_t idx = mNodeArray.IndexOf(pointTextNode);
  1567   if (idx == -1) {
  1568     // can't find point, but it's not an error
  1569     return outPoint;
  1571   int32_t numNodes = mNodeArray.Count();
  1573   if (uint16_t(aPoint.mOffset) < aPoint.mTextNode->TextLength())
  1575     outPoint = aPoint;
  1576     outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
  1577     return outPoint;
  1578   } else if (idx + 1 < (int32_t)numNodes) {
  1579     nsIDOMNode* node = mNodeArray[idx+1];
  1580     MOZ_ASSERT(node);
  1581     outPoint.mTextNode = do_QueryInterface(node);
  1582     if (!outPoint.mTextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
  1583       // Not sure if this is needed, but it'll maintain the same
  1584       // functionality
  1585       outPoint.mTextNode = nullptr;
  1587     outPoint.mOffset = 0;
  1588     outPoint.mChar = GetCharAt(outPoint.mTextNode, 0);
  1590   return outPoint;
  1593 nsWSRunObject::WSPoint
  1594 nsWSRunObject::GetCharBefore(const WSPoint &aPoint)
  1596   MOZ_ASSERT(aPoint.mTextNode);
  1598   WSPoint outPoint;
  1599   outPoint.mTextNode = nullptr;
  1600   outPoint.mOffset = 0;
  1601   outPoint.mChar = 0;
  1603   nsCOMPtr<nsIDOMNode> pointTextNode(do_QueryInterface(aPoint.mTextNode));
  1604   int32_t idx = mNodeArray.IndexOf(pointTextNode);
  1605   if (idx == -1) {
  1606     // can't find point, but it's not an error
  1607     return outPoint;
  1610   if (aPoint.mOffset != 0)
  1612     outPoint = aPoint;
  1613     outPoint.mOffset--;
  1614     outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset-1);
  1615     return outPoint;
  1617   else if (idx)
  1619     nsIDOMNode* node = mNodeArray[idx-1];
  1620     MOZ_ASSERT(node);
  1621     outPoint.mTextNode = do_QueryInterface(node);
  1623     uint32_t len = outPoint.mTextNode->TextLength();
  1625     if (len)
  1627       outPoint.mOffset = len-1;
  1628       outPoint.mChar = GetCharAt(outPoint.mTextNode, len-1);
  1631   return outPoint;
  1634 nsresult 
  1635 nsWSRunObject::ConvertToNBSP(WSPoint aPoint, AreaRestriction aAR)
  1637   // MOOSE: this routine needs to be modified to preserve the integrity of the
  1638   // wsFragment info.
  1639   NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
  1641   if (aAR == eOutsideUserSelectAll)
  1643     nsCOMPtr<nsIDOMNode> domnode = do_QueryInterface(aPoint.mTextNode);
  1644     if (domnode)
  1646       nsCOMPtr<nsIDOMNode> san = mHTMLEditor->FindUserSelectAllNode(domnode);
  1647       if (san)
  1648         return NS_OK;
  1652   nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(aPoint.mTextNode));
  1653   NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
  1654   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(textNode));
  1656   // first, insert an nbsp
  1657   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
  1658   nsAutoString nbspStr(nbsp);
  1659   nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, aPoint.mOffset, true);
  1660   NS_ENSURE_SUCCESS(res, res);
  1662   // next, find range of ws it will replace
  1663   nsCOMPtr<nsIDOMNode> startNode, endNode;
  1664   int32_t startOffset=0, endOffset=0;
  1666   GetAsciiWSBounds(eAfter, node, aPoint.mOffset+1, address_of(startNode),
  1667                    &startOffset, address_of(endNode), &endOffset);
  1669   // finally, delete that replaced ws, if any
  1670   if (startNode)
  1672     res = DeleteChars(startNode, startOffset, endNode, endOffset);
  1675   return res;
  1678 void
  1679 nsWSRunObject::GetAsciiWSBounds(int16_t aDir, nsIDOMNode *aNode, int32_t aOffset,
  1680                                 nsCOMPtr<nsIDOMNode> *outStartNode, int32_t *outStartOffset,
  1681                                 nsCOMPtr<nsIDOMNode> *outEndNode, int32_t *outEndOffset)
  1683   MOZ_ASSERT(aNode && outStartNode && outEndNode);
  1685   nsCOMPtr<nsIDOMNode> startNode, endNode;
  1686   int32_t startOffset=0, endOffset=0;
  1688   if (aDir & eAfter)
  1690     WSPoint point = GetCharAfter(aNode, aOffset);
  1691     if (point.mTextNode) {
  1692       // we found a text node, at least
  1693       endNode = do_QueryInterface(point.mTextNode);
  1694       endOffset = point.mOffset;
  1695       startNode = endNode;
  1696       startOffset = endOffset;
  1698       // scan ahead to end of ascii ws
  1699       while (nsCRT::IsAsciiSpace(point.mChar))
  1701         endNode = do_QueryInterface(point.mTextNode);
  1702         point.mOffset++;  // endOffset is _after_ ws
  1703         endOffset = point.mOffset;
  1704         point = GetCharAfter(point);
  1705         if (!point.mTextNode) {
  1706           break;
  1712   if (aDir & eBefore)
  1714     WSPoint point = GetCharBefore(aNode, aOffset);
  1715     if (point.mTextNode) {
  1716       // we found a text node, at least
  1717       startNode = do_QueryInterface(point.mTextNode);
  1718       startOffset = point.mOffset+1;
  1719       if (!endNode)
  1721         endNode = startNode;
  1722         endOffset = startOffset;
  1725       // scan back to start of ascii ws
  1726       while (nsCRT::IsAsciiSpace(point.mChar))
  1728         startNode = do_QueryInterface(point.mTextNode);
  1729         startOffset = point.mOffset;
  1730         point = GetCharBefore(point);
  1731         if (!point.mTextNode) {
  1732           break;
  1738   *outStartNode = startNode;
  1739   *outStartOffset = startOffset;
  1740   *outEndNode = endNode;
  1741   *outEndOffset = endOffset;
  1744 void
  1745 nsWSRunObject::FindRun(nsIDOMNode *aNode, int32_t aOffset, WSFragment **outRun, bool after)
  1747   *outRun = nullptr;
  1748   // given a dompoint, find the ws run that is before or after it, as caller needs
  1749   MOZ_ASSERT(aNode && outRun);
  1751   WSFragment *run = mStartRun;
  1752   while (run)
  1754     int16_t comp = nsContentUtils::ComparePoints(aNode, aOffset, run->mStartNode,
  1755                                                  run->mStartOffset);
  1756     if (comp <= 0)
  1758       if (after)
  1760         *outRun = run;
  1762       else // before
  1764         *outRun = nullptr;
  1766       return;
  1768     comp = nsContentUtils::ComparePoints(aNode, aOffset,
  1769                                          run->mEndNode, run->mEndOffset);
  1770     if (comp < 0)
  1772       *outRun = run;
  1773       return;
  1775     else if (comp == 0)
  1777       if (after)
  1779         *outRun = run->mRight;
  1781       else // before
  1783         *outRun = run;
  1785       return;
  1787     if (!run->mRight)
  1789       if (after)
  1791         *outRun = nullptr;
  1793       else // before
  1795         *outRun = run;
  1797       return;
  1799     run = run->mRight;
  1803 char16_t 
  1804 nsWSRunObject::GetCharAt(nsIContent *aTextNode, int32_t aOffset)
  1806   // return 0 if we can't get a char, for whatever reason
  1807   NS_ENSURE_TRUE(aTextNode, 0);
  1809   int32_t len = int32_t(aTextNode->TextLength());
  1810   if (aOffset < 0 || aOffset >= len)
  1811     return 0;
  1813   return aTextNode->GetText()->CharAt(aOffset);
  1816 nsWSRunObject::WSPoint
  1817 nsWSRunObject::GetWSPointAfter(nsIDOMNode *aNode, int32_t aOffset)
  1819   // Note: only to be called if aNode is not a ws node.  
  1821   // binary search on wsnodes
  1822   int32_t numNodes, firstNum, curNum, lastNum;
  1823   numNodes = mNodeArray.Count();
  1825   if (!numNodes) {
  1826     // do nothing if there are no nodes to search
  1827     WSPoint outPoint;
  1828     return outPoint;
  1831   firstNum = 0;
  1832   curNum = numNodes/2;
  1833   lastNum = numNodes;
  1834   int16_t cmp=0;
  1835   nsCOMPtr<nsIDOMNode>  curNode;
  1837   // begin binary search
  1838   // we do this because we need to minimize calls to ComparePoints(),
  1839   // which is mongo expensive
  1840   while (curNum != lastNum)
  1842     curNode = mNodeArray[curNum];
  1843     cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
  1844     if (cmp < 0)
  1845       lastNum = curNum;
  1846     else
  1847       firstNum = curNum + 1;
  1848     curNum = (lastNum - firstNum) / 2 + firstNum;
  1849     NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
  1852   // When the binary search is complete, we always know that the current node
  1853   // is the same as the end node, which is always past our range. Therefore,
  1854   // we've found the node immediately after the point of interest.
  1855   if (curNum == mNodeArray.Count()) {
  1856     // they asked for past our range (it's after the last node). GetCharAfter
  1857     // will do the work for us when we pass it the last index of the last node.
  1858     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNodeArray[curNum-1]));
  1859     WSPoint point(textNode, textNode->TextLength(), 0);
  1860     return GetCharAfter(point);
  1861   } else {
  1862     // The char after the point of interest is the first character of our range.
  1863     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNodeArray[curNum]));
  1864     WSPoint point(textNode, 0, 0);
  1865     return GetCharAfter(point);
  1869 nsWSRunObject::WSPoint
  1870 nsWSRunObject::GetWSPointBefore(nsIDOMNode *aNode, int32_t aOffset)
  1872   // Note: only to be called if aNode is not a ws node.  
  1874   // binary search on wsnodes
  1875   int32_t numNodes, firstNum, curNum, lastNum;
  1876   numNodes = mNodeArray.Count();
  1878   if (!numNodes) {
  1879     // do nothing if there are no nodes to search
  1880     WSPoint outPoint;
  1881     return outPoint;
  1884   firstNum = 0;
  1885   curNum = numNodes/2;
  1886   lastNum = numNodes;
  1887   int16_t cmp=0;
  1888   nsCOMPtr<nsIDOMNode>  curNode;
  1890   // begin binary search
  1891   // we do this because we need to minimize calls to ComparePoints(),
  1892   // which is mongo expensive
  1893   while (curNum != lastNum)
  1895     curNode = mNodeArray[curNum];
  1896     cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
  1897     if (cmp < 0)
  1898       lastNum = curNum;
  1899     else
  1900       firstNum = curNum + 1;
  1901     curNum = (lastNum - firstNum) / 2 + firstNum;
  1902     NS_ASSERTION(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
  1905   // When the binary search is complete, we always know that the current node
  1906   // is the same as the end node, which is always past our range. Therefore,
  1907   // we've found the node immediately after the point of interest.
  1908   if (curNum == mNodeArray.Count()) {
  1909     // get the point before the end of the last node, we can pass the length
  1910     // of the node into GetCharBefore, and it will return the last character.
  1911     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNodeArray[curNum - 1]));
  1912     WSPoint point(textNode, textNode->TextLength(), 0);
  1913     return GetCharBefore(point);
  1914   } else {
  1915     // we can just ask the current node for the point immediately before it,
  1916     // it will handle moving to the previous node (if any) and returning the
  1917     // appropriate character
  1918     nsCOMPtr<nsIContent> textNode(do_QueryInterface(mNodeArray[curNum]));
  1919     WSPoint point(textNode, 0, 0);
  1920     return GetCharBefore(point);
  1924 nsresult
  1925 nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
  1927   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. 
  1928   // examine what is before and after the trailing nbsp, if any.
  1929   NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER);
  1930   nsresult res;
  1931   bool leftCheck = false;
  1932   bool spaceNBSP = false;
  1933   bool rightCheck = false;
  1935   // confirm run is normalWS
  1936   if (aRun->mType != WSType::normalWS) {
  1937     return NS_ERROR_FAILURE;
  1940   // first check for trailing nbsp
  1941   WSPoint thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
  1942   if (thePoint.mTextNode && thePoint.mChar == nbsp) {
  1943     // now check that what is to the left of it is compatible with replacing nbsp with space
  1944     WSPoint prevPoint = GetCharBefore(thePoint);
  1945     if (prevPoint.mTextNode) {
  1946       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) leftCheck = true;
  1947       else spaceNBSP = true;
  1948     } else if (aRun->mLeftType == WSType::text) {
  1949       leftCheck = true;
  1950     } else if (aRun->mLeftType == WSType::special) {
  1951       leftCheck = true;
  1953     if (leftCheck || spaceNBSP)
  1955       // now check that what is to the right of it is compatible with replacing nbsp with space
  1956       if (aRun->mRightType == WSType::text) {
  1957         rightCheck = true;
  1959       if (aRun->mRightType == WSType::special) {
  1960         rightCheck = true;
  1962       if (aRun->mRightType == WSType::br) {
  1963         rightCheck = true;
  1965       if ((aRun->mRightType & WSType::block) &&
  1966           IsBlockNode(nsCOMPtr<nsIDOMNode>(GetWSBoundingParent()))) {
  1967         // we are at a block boundary.  Insert a <br>.  Why?  Well, first note that
  1968         // the br will have no visible effect since it is up against a block boundary.
  1969         // |foo<br><p>bar|  renders like |foo<p>bar| and similarly
  1970         // |<p>foo<br></p>bar| renders like |<p>foo</p>bar|.  What this <br> addition
  1971         // gets us is the ability to convert a trailing nbsp to a space.  Consider:
  1972         // |<body>foo. '</body>|, where ' represents selection.  User types space attempting
  1973         // to put 2 spaces after the end of their sentence.  We used to do this as:
  1974         // |<body>foo. &nbsp</body>|  This caused problems with soft wrapping: the nbsp
  1975         // would wrap to the next line, which looked attrocious.  If you try to do:
  1976         // |<body>foo.&nbsp </body>| instead, the trailing space is invisible because it 
  1977         // is against a block boundary.  If you do: |<body>foo.&nbsp&nbsp</body>| then
  1978         // you get an even uglier soft wrapping problem, where foo is on one line until
  1979         // you type the final space, and then "foo  " jumps down to the next line.  Ugh.
  1980         // The best way I can find out of this is to throw in a harmless <br>
  1981         // here, which allows us to do: |<body>foo.&nbsp <br></body>|, which doesn't
  1982         // cause foo to jump lines, doesn't cause spaces to show up at the beginning of 
  1983         // soft wrapped lines, and lets the user see 2 spaces when they type 2 spaces.
  1985         nsCOMPtr<nsIDOMNode> brNode;
  1986         res = mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset, address_of(brNode));
  1987         NS_ENSURE_SUCCESS(res, res);
  1989         // refresh thePoint, prevPoint
  1990         thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
  1991         prevPoint = GetCharBefore(thePoint);
  1992         rightCheck = true;
  1995     if (leftCheck && rightCheck)
  1997       // now replace nbsp with space
  1998       // first, insert a space
  1999       nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
  2000       NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
  2001       nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
  2002       nsAutoString spaceStr(char16_t(32));
  2003       res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode, thePoint.mOffset, true);
  2004       NS_ENSURE_SUCCESS(res, res);
  2006       // finally, delete that nbsp
  2007       nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
  2008       res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
  2009       NS_ENSURE_SUCCESS(res, res);
  2011     else if (!mPRE && spaceNBSP && rightCheck)  // don't mess with this preformatted for now.
  2013       // we have a run of ascii whitespace (which will render as one space)
  2014       // followed by an nbsp (which is at the end of the whitespace run).  Let's
  2015       // switch their order.  This will insure that if someone types two spaces
  2016       // after a sentence, and the editor softwraps at this point, the spaces wont
  2017       // be split across lines, which looks ugly and is bad for the moose.
  2019       nsCOMPtr<nsIDOMNode> startNode, endNode, thenode(do_QueryInterface(prevPoint.mTextNode));
  2020       int32_t startOffset, endOffset;
  2021       GetAsciiWSBounds(eBoth, thenode, prevPoint.mOffset+1, address_of(startNode),
  2022                        &startOffset, address_of(endNode), &endOffset);
  2024       //  delete that nbsp
  2025       nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
  2026       res = DeleteChars(delNode, thePoint.mOffset, delNode, thePoint.mOffset+1);
  2027       NS_ENSURE_SUCCESS(res, res);
  2029       // finally, insert that nbsp before the ascii ws run
  2030       nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
  2031       nsAutoString nbspStr(nbsp);
  2032       nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(startNode));
  2033       res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, textNode, startOffset, true);
  2034       NS_ENSURE_SUCCESS(res, res);
  2037   return NS_OK;
  2040 nsresult
  2041 nsWSRunObject::CheckTrailingNBSP(WSFragment *aRun, nsIDOMNode *aNode, int32_t aOffset)
  2043   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation. 
  2044   // this routine is called when we about to make this point in the ws abut an inserted break
  2045   // or text, so we don't have to worry about what is after it.  What is after it now will 
  2046   // end up after the inserted object.   
  2047   NS_ENSURE_TRUE(aRun && aNode, NS_ERROR_NULL_POINTER);
  2048   bool canConvert = false;
  2049   WSPoint thePoint = GetCharBefore(aNode, aOffset);
  2050   if (thePoint.mTextNode && thePoint.mChar == nbsp) {
  2051     WSPoint prevPoint = GetCharBefore(thePoint);
  2052     if (prevPoint.mTextNode) {
  2053       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) canConvert = true;
  2054     } else if (aRun->mLeftType == WSType::text) {
  2055       canConvert = true;
  2056     } else if (aRun->mLeftType == WSType::special) {
  2057       canConvert = true;
  2060   if (canConvert)
  2062     // first, insert a space
  2063     nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
  2064     NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
  2065     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
  2066     nsAutoString spaceStr(char16_t(32));
  2067     nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode,
  2068                                                            thePoint.mOffset,
  2069                                                            true);
  2070     NS_ENSURE_SUCCESS(res, res);
  2072     // finally, delete that nbsp
  2073     nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
  2074     res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
  2075     NS_ENSURE_SUCCESS(res, res);
  2077   return NS_OK;
  2080 nsresult
  2081 nsWSRunObject::CheckLeadingNBSP(WSFragment *aRun, nsIDOMNode *aNode, int32_t aOffset)
  2083   // try to change an nbsp to a space, if possible, just to prevent nbsp proliferation    
  2084   // this routine is called when we about to make this point in the ws abut an inserted
  2085   // text, so we don't have to worry about what is before it.  What is before it now will 
  2086   // end up before the inserted text.   
  2087   bool canConvert = false;
  2088   WSPoint thePoint = GetCharAfter(aNode, aOffset);
  2089   if (thePoint.mChar == nbsp) {
  2090     WSPoint tmp = thePoint;
  2091     tmp.mOffset++; // we want to be after thePoint
  2092     WSPoint nextPoint = GetCharAfter(tmp);
  2093     if (nextPoint.mTextNode) {
  2094       if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) canConvert = true;
  2095     } else if (aRun->mRightType == WSType::text) {
  2096       canConvert = true;
  2097     } else if (aRun->mRightType == WSType::special) {
  2098       canConvert = true;
  2099     } else if (aRun->mRightType == WSType::br) {
  2100       canConvert = true;
  2103   if (canConvert)
  2105     // first, insert a space
  2106     nsCOMPtr<nsIDOMCharacterData> textNode(do_QueryInterface(thePoint.mTextNode));
  2107     NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
  2108     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
  2109     nsAutoString spaceStr(char16_t(32));
  2110     nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, textNode,
  2111                                                            thePoint.mOffset,
  2112                                                            true);
  2113     NS_ENSURE_SUCCESS(res, res);
  2115     // finally, delete that nbsp
  2116     nsCOMPtr<nsIDOMNode> delNode(do_QueryInterface(thePoint.mTextNode));
  2117     res = DeleteChars(delNode, thePoint.mOffset+1, delNode, thePoint.mOffset+2);
  2118     NS_ENSURE_SUCCESS(res, res);
  2120   return NS_OK;
  2124 nsresult
  2125 nsWSRunObject::ScrubBlockBoundaryInner(nsHTMLEditor *aHTMLEd, 
  2126                                        nsCOMPtr<nsIDOMNode> *aBlock,
  2127                                        BlockBoundary aBoundary)
  2129   NS_ENSURE_TRUE(aBlock && aHTMLEd, NS_ERROR_NULL_POINTER);
  2130   int32_t offset=0;
  2131   if (aBoundary == kBlockEnd)
  2133     uint32_t uOffset;
  2134     aHTMLEd->GetLengthOfDOMNode(*aBlock, uOffset); 
  2135     offset = uOffset;
  2137   nsWSRunObject theWSObj(aHTMLEd, *aBlock, offset);
  2138   return theWSObj.Scrub();    
  2142 nsresult
  2143 nsWSRunObject::Scrub()
  2145   WSFragment *run = mStartRun;
  2146   while (run)
  2148     if (run->mType & (WSType::leadingWS | WSType::trailingWS)) {
  2149       nsresult res = DeleteChars(run->mStartNode, run->mStartOffset, run->mEndNode, run->mEndOffset);
  2150       NS_ENSURE_SUCCESS(res, res);
  2152     run = run->mRight;
  2154   return NS_OK;

mercurial