LCOV - code coverage report
Current view: top level - editor/libeditor - WSRunObject.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 930 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 42 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       5             : 
       6             : #include "WSRunObject.h"
       7             : 
       8             : #include "TextEditUtils.h"
       9             : 
      10             : #include "mozilla/Assertions.h"
      11             : #include "mozilla/Casting.h"
      12             : #include "mozilla/EditorUtils.h"
      13             : #include "mozilla/HTMLEditor.h"
      14             : #include "mozilla/mozalloc.h"
      15             : #include "mozilla/OwningNonNull.h"
      16             : #include "mozilla/SelectionState.h"
      17             : 
      18             : #include "nsAString.h"
      19             : #include "nsCRT.h"
      20             : #include "nsContentUtils.h"
      21             : #include "nsDebug.h"
      22             : #include "nsError.h"
      23             : #include "nsIContent.h"
      24             : #include "nsIDOMDocument.h"
      25             : #include "nsIDOMNode.h"
      26             : #include "nsISupportsImpl.h"
      27             : #include "nsRange.h"
      28             : #include "nsString.h"
      29             : #include "nsTextFragment.h"
      30             : 
      31             : namespace mozilla {
      32             : 
      33             : using namespace dom;
      34             : 
      35             : const char16_t nbsp = 160;
      36             : 
      37           0 : WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
      38             :                          nsINode* aNode,
      39           0 :                          int32_t aOffset)
      40             :   : mNode(aNode)
      41             :   , mOffset(aOffset)
      42             :   , mPRE(false)
      43             :   , mStartOffset(0)
      44             :   , mEndOffset(0)
      45             :   , mFirstNBSPOffset(0)
      46             :   , mLastNBSPOffset(0)
      47             :   , mStartRun(nullptr)
      48             :   , mEndRun(nullptr)
      49           0 :   , mHTMLEditor(aHTMLEditor)
      50             : {
      51           0 :   GetWSNodes();
      52           0 :   GetRuns();
      53           0 : }
      54             : 
      55           0 : WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
      56             :                          nsIDOMNode* aNode,
      57           0 :                          int32_t aOffset)
      58             :   : mNode(do_QueryInterface(aNode))
      59             :   , mOffset(aOffset)
      60             :   , mPRE(false)
      61             :   , mStartOffset(0)
      62             :   , mEndOffset(0)
      63             :   , mFirstNBSPOffset(0)
      64             :   , mLastNBSPOffset(0)
      65             :   , mStartRun(nullptr)
      66             :   , mEndRun(nullptr)
      67           0 :   , mHTMLEditor(aHTMLEditor)
      68             : {
      69           0 :   GetWSNodes();
      70           0 :   GetRuns();
      71           0 : }
      72             : 
      73           0 : WSRunObject::~WSRunObject()
      74             : {
      75           0 :   ClearRuns();
      76           0 : }
      77             : 
      78             : nsresult
      79           0 : WSRunObject::ScrubBlockBoundary(HTMLEditor* aHTMLEditor,
      80             :                                 BlockBoundary aBoundary,
      81             :                                 nsINode* aBlock,
      82             :                                 int32_t aOffset)
      83             : {
      84           0 :   NS_ENSURE_TRUE(aHTMLEditor && aBlock, NS_ERROR_NULL_POINTER);
      85             : 
      86             :   int32_t offset;
      87           0 :   if (aBoundary == kBlockStart) {
      88           0 :     offset = 0;
      89           0 :   } else if (aBoundary == kBlockEnd) {
      90           0 :     offset = aBlock->Length();
      91             :   } else {
      92             :     // Else we are scrubbing an outer boundary - just before or after a block
      93             :     // element.
      94           0 :     NS_ENSURE_STATE(aOffset >= 0);
      95           0 :     offset = aOffset;
      96             :   }
      97             : 
      98           0 :   WSRunObject theWSObj(aHTMLEditor, aBlock, offset);
      99           0 :   return theWSObj.Scrub();
     100             : }
     101             : 
     102             : nsresult
     103           0 : WSRunObject::PrepareToJoinBlocks(HTMLEditor* aHTMLEditor,
     104             :                                  Element* aLeftBlock,
     105             :                                  Element* aRightBlock)
     106             : {
     107           0 :   NS_ENSURE_TRUE(aLeftBlock && aRightBlock && aHTMLEditor,
     108             :                  NS_ERROR_NULL_POINTER);
     109             : 
     110           0 :   WSRunObject leftWSObj(aHTMLEditor, aLeftBlock, aLeftBlock->Length());
     111           0 :   WSRunObject rightWSObj(aHTMLEditor, aRightBlock, 0);
     112             : 
     113           0 :   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
     114             : }
     115             : 
     116             : nsresult
     117           0 : WSRunObject::PrepareToDeleteRange(HTMLEditor* aHTMLEditor,
     118             :                                   nsCOMPtr<nsINode>* aStartNode,
     119             :                                   int32_t* aStartOffset,
     120             :                                   nsCOMPtr<nsINode>* aEndNode,
     121             :                                   int32_t* aEndOffset)
     122             : {
     123           0 :   NS_ENSURE_TRUE(aHTMLEditor && aStartNode && *aStartNode && aStartOffset &&
     124             :                  aEndNode && *aEndNode && aEndOffset, NS_ERROR_NULL_POINTER);
     125             : 
     126             :   AutoTrackDOMPoint trackerStart(aHTMLEditor->mRangeUpdater,
     127           0 :                                  aStartNode, aStartOffset);
     128             :   AutoTrackDOMPoint trackerEnd(aHTMLEditor->mRangeUpdater,
     129           0 :                                aEndNode, aEndOffset);
     130             : 
     131           0 :   WSRunObject leftWSObj(aHTMLEditor, *aStartNode, *aStartOffset);
     132           0 :   WSRunObject rightWSObj(aHTMLEditor, *aEndNode, *aEndOffset);
     133             : 
     134           0 :   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
     135             : }
     136             : 
     137             : nsresult
     138           0 : WSRunObject::PrepareToDeleteNode(HTMLEditor* aHTMLEditor,
     139             :                                  nsIContent* aContent)
     140             : {
     141           0 :   NS_ENSURE_TRUE(aContent && aHTMLEditor, NS_ERROR_NULL_POINTER);
     142             : 
     143           0 :   nsCOMPtr<nsINode> parent = aContent->GetParentNode();
     144           0 :   NS_ENSURE_STATE(parent);
     145           0 :   int32_t offset = parent->IndexOf(aContent);
     146             : 
     147           0 :   WSRunObject leftWSObj(aHTMLEditor, parent, offset);
     148           0 :   WSRunObject rightWSObj(aHTMLEditor, parent, offset + 1);
     149             : 
     150           0 :   return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
     151             : }
     152             : 
     153             : nsresult
     154           0 : WSRunObject::PrepareToSplitAcrossBlocks(HTMLEditor* aHTMLEditor,
     155             :                                         nsCOMPtr<nsINode>* aSplitNode,
     156             :                                         int32_t* aSplitOffset)
     157             : {
     158           0 :   NS_ENSURE_TRUE(aHTMLEditor && aSplitNode && *aSplitNode && aSplitOffset,
     159             :                  NS_ERROR_NULL_POINTER);
     160             : 
     161             :   AutoTrackDOMPoint tracker(aHTMLEditor->mRangeUpdater,
     162           0 :                             aSplitNode, aSplitOffset);
     163             : 
     164           0 :   WSRunObject wsObj(aHTMLEditor, *aSplitNode, *aSplitOffset);
     165             : 
     166           0 :   return wsObj.PrepareToSplitAcrossBlocksPriv();
     167             : }
     168             : 
     169             : already_AddRefed<Element>
     170           0 : WSRunObject::InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
     171             :                          int32_t* aInOutOffset,
     172             :                          nsIEditor::EDirection aSelect)
     173             : {
     174             :   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
     175             :   // meanwhile, the pre case is handled in WillInsertText in
     176             :   // HTMLEditRules.cpp
     177           0 :   NS_ENSURE_TRUE(aInOutParent && aInOutOffset, nullptr);
     178             : 
     179             :   WSFragment *beforeRun, *afterRun;
     180           0 :   FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
     181           0 :   FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
     182             : 
     183             :   {
     184             :     // Some scoping for AutoTrackDOMPoint.  This will track our insertion
     185             :     // point while we tweak any surrounding whitespace
     186           0 :     AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
     187           0 :                               aInOutOffset);
     188             : 
     189             :     // Handle any changes needed to ws run after inserted br
     190           0 :     if (!afterRun || (afterRun->mType & WSType::trailingWS)) {
     191             :       // Don't need to do anything.  Just insert break.  ws won't change.
     192           0 :     } else if (afterRun->mType & WSType::leadingWS) {
     193             :       // Delete the leading ws that is after insertion point.  We don't
     194             :       // have to (it would still not be significant after br), but it's
     195             :       // just more aesthetically pleasing to.
     196           0 :       nsresult rv = DeleteChars(*aInOutParent, *aInOutOffset,
     197           0 :                                 afterRun->mEndNode, afterRun->mEndOffset);
     198           0 :       NS_ENSURE_SUCCESS(rv, nullptr);
     199           0 :     } else if (afterRun->mType == WSType::normalWS) {
     200             :       // Need to determine if break at front of non-nbsp run.  If so, convert
     201             :       // run to nbsp.
     202           0 :       WSPoint thePoint = GetCharAfter(*aInOutParent, *aInOutOffset);
     203           0 :       if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
     204           0 :         WSPoint prevPoint = GetCharBefore(thePoint);
     205           0 :         if (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) {
     206             :           // We are at start of non-nbsps.  Convert to a single nbsp.
     207           0 :           nsresult rv = ConvertToNBSP(thePoint);
     208           0 :           NS_ENSURE_SUCCESS(rv, nullptr);
     209             :         }
     210             :       }
     211             :     }
     212             : 
     213             :     // Handle any changes needed to ws run before inserted br
     214           0 :     if (!beforeRun || (beforeRun->mType & WSType::leadingWS)) {
     215             :       // Don't need to do anything.  Just insert break.  ws won't change.
     216           0 :     } else if (beforeRun->mType & WSType::trailingWS) {
     217             :       // Need to delete the trailing ws that is before insertion point, because it
     218             :       // would become significant after break inserted.
     219           0 :       nsresult rv = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
     220           0 :                                 *aInOutParent, *aInOutOffset);
     221           0 :       NS_ENSURE_SUCCESS(rv, nullptr);
     222           0 :     } else if (beforeRun->mType == WSType::normalWS) {
     223             :       // Try to change an nbsp to a space, just to prevent nbsp proliferation
     224           0 :       nsresult rv = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
     225           0 :       NS_ENSURE_SUCCESS(rv, nullptr);
     226             :     }
     227             :   }
     228             : 
     229             :   // ready, aim, fire!
     230           0 :   return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, aSelect);
     231             : }
     232             : 
     233             : nsresult
     234           0 : WSRunObject::InsertText(const nsAString& aStringToInsert,
     235             :                         nsCOMPtr<nsINode>* aInOutParent,
     236             :                         int32_t* aInOutOffset,
     237             :                         nsIDocument* aDoc)
     238             : {
     239             :   // MOOSE: for now, we always assume non-PRE formatting.  Fix this later.
     240             :   // meanwhile, the pre case is handled in WillInsertText in
     241             :   // HTMLEditRules.cpp
     242             : 
     243             :   // MOOSE: for now, just getting the ws logic straight.  This implementation
     244             :   // is very slow.  Will need to replace edit rules impl with a more efficient
     245             :   // text sink here that does the minimal amount of searching/replacing/copying
     246             : 
     247           0 :   NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
     248             : 
     249           0 :   if (aStringToInsert.IsEmpty()) {
     250           0 :     return NS_OK;
     251             :   }
     252             : 
     253           0 :   nsAutoString theString(aStringToInsert);
     254             : 
     255             :   WSFragment *beforeRun, *afterRun;
     256           0 :   FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
     257           0 :   FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
     258             : 
     259             :   {
     260             :     // Some scoping for AutoTrackDOMPoint.  This will track our insertion
     261             :     // point while we tweak any surrounding whitespace
     262           0 :     AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
     263           0 :                               aInOutOffset);
     264             : 
     265             :     // Handle any changes needed to ws run after inserted text
     266           0 :     if (!afterRun || afterRun->mType & WSType::trailingWS) {
     267             :       // Don't need to do anything.  Just insert text.  ws won't change.
     268           0 :     } else if (afterRun->mType & WSType::leadingWS) {
     269             :       // Delete the leading ws that is after insertion point, because it
     270             :       // would become significant after text inserted.
     271             :       nsresult rv =
     272           0 :         DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode,
     273           0 :                     afterRun->mEndOffset);
     274           0 :       NS_ENSURE_SUCCESS(rv, rv);
     275           0 :     } else if (afterRun->mType == WSType::normalWS) {
     276             :       // Try to change an nbsp to a space, if possible, just to prevent nbsp
     277             :       // proliferation
     278           0 :       nsresult rv = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
     279           0 :       NS_ENSURE_SUCCESS(rv, rv);
     280             :     }
     281             : 
     282             :     // Handle any changes needed to ws run before inserted text
     283           0 :     if (!beforeRun || beforeRun->mType & WSType::leadingWS) {
     284             :       // Don't need to do anything.  Just insert text.  ws won't change.
     285           0 :     } else if (beforeRun->mType & WSType::trailingWS) {
     286             :       // Need to delete the trailing ws that is before insertion point, because
     287             :       // it would become significant after text inserted.
     288             :       nsresult rv =
     289           0 :         DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
     290           0 :                     *aInOutParent, *aInOutOffset);
     291           0 :       NS_ENSURE_SUCCESS(rv, rv);
     292           0 :     } else if (beforeRun->mType == WSType::normalWS) {
     293             :       // Try to change an nbsp to a space, if possible, just to prevent nbsp
     294             :       // proliferation
     295           0 :       nsresult rv = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
     296           0 :       NS_ENSURE_SUCCESS(rv, rv);
     297             :     }
     298             :   }
     299             : 
     300             :   // Next up, tweak head and tail of string as needed.  First the head: there
     301             :   // are a variety of circumstances that would require us to convert a leading
     302             :   // ws char into an nbsp:
     303             : 
     304           0 :   if (nsCRT::IsAsciiSpace(theString[0])) {
     305             :     // We have a leading space
     306           0 :     if (beforeRun) {
     307           0 :       if (beforeRun->mType & WSType::leadingWS) {
     308           0 :         theString.SetCharAt(nbsp, 0);
     309           0 :       } else if (beforeRun->mType & WSType::normalWS) {
     310           0 :         WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset);
     311           0 :         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
     312           0 :           theString.SetCharAt(nbsp, 0);
     313             :         }
     314             :       }
     315           0 :     } else if (mStartReason & WSType::block || mStartReason == WSType::br) {
     316           0 :       theString.SetCharAt(nbsp, 0);
     317             :     }
     318             :   }
     319             : 
     320             :   // Then the tail
     321           0 :   uint32_t lastCharIndex = theString.Length() - 1;
     322             : 
     323           0 :   if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
     324             :     // We have a leading space
     325           0 :     if (afterRun) {
     326           0 :       if (afterRun->mType & WSType::trailingWS) {
     327           0 :         theString.SetCharAt(nbsp, lastCharIndex);
     328           0 :       } else if (afterRun->mType & WSType::normalWS) {
     329           0 :         WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset);
     330           0 :         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
     331           0 :           theString.SetCharAt(nbsp, lastCharIndex);
     332             :         }
     333             :       }
     334           0 :     } else if (mEndReason & WSType::block) {
     335           0 :       theString.SetCharAt(nbsp, lastCharIndex);
     336             :     }
     337             :   }
     338             : 
     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
     341             :   // WillInsertText() before we are called.  Eventually, all that logic will be
     342             :   // pushed down into here and made more efficient.
     343           0 :   bool prevWS = false;
     344           0 :   for (uint32_t i = 0; i <= lastCharIndex; i++) {
     345           0 :     if (nsCRT::IsAsciiSpace(theString[i])) {
     346           0 :       if (prevWS) {
     347             :         // i - 1 can't be negative because prevWS starts out false
     348           0 :         theString.SetCharAt(nbsp, i - 1);
     349             :       } else {
     350           0 :         prevWS = true;
     351             :       }
     352             :     } else {
     353           0 :       prevWS = false;
     354             :     }
     355             :   }
     356             : 
     357             :   // Ready, aim, fire!
     358           0 :   mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset, aDoc);
     359           0 :   return NS_OK;
     360             : }
     361             : 
     362             : nsresult
     363           0 : WSRunObject::DeleteWSBackward()
     364             : {
     365           0 :   WSPoint point = GetCharBefore(mNode, mOffset);
     366           0 :   NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete
     367             : 
     368             :   // Easy case, preformatted ws.
     369           0 :   if (mPRE &&  (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
     370           0 :     return DeleteChars(point.mTextNode, point.mOffset,
     371           0 :                        point.mTextNode, point.mOffset + 1);
     372             :   }
     373             : 
     374             :   // Caller's job to ensure that previous char is really ws.  If it is normal
     375             :   // ws, we need to delete the whole run.
     376           0 :   if (nsCRT::IsAsciiSpace(point.mChar)) {
     377           0 :     RefPtr<Text> startNodeText, endNodeText;
     378             :     int32_t startOffset, endOffset;
     379           0 :     GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
     380           0 :                      getter_AddRefs(startNodeText), &startOffset,
     381           0 :                      getter_AddRefs(endNodeText), &endOffset);
     382             : 
     383             :     // adjust surrounding ws
     384           0 :     nsCOMPtr<nsINode> startNode = startNodeText.get();
     385           0 :     nsCOMPtr<nsINode> endNode = endNodeText.get();
     386             :     nsresult rv =
     387           0 :       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
     388             :                                         address_of(startNode), &startOffset,
     389           0 :                                         address_of(endNode), &endOffset);
     390           0 :     NS_ENSURE_SUCCESS(rv, rv);
     391             : 
     392             :     // finally, delete that ws
     393           0 :     return DeleteChars(startNode, startOffset, endNode, endOffset);
     394             :   }
     395             : 
     396           0 :   if (point.mChar == nbsp) {
     397           0 :     nsCOMPtr<nsINode> node(point.mTextNode);
     398             :     // adjust surrounding ws
     399           0 :     int32_t startOffset = point.mOffset;
     400           0 :     int32_t endOffset = point.mOffset + 1;
     401             :     nsresult rv =
     402           0 :       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
     403             :                                         address_of(node), &startOffset,
     404           0 :                                         address_of(node), &endOffset);
     405           0 :     NS_ENSURE_SUCCESS(rv, rv);
     406             : 
     407             :     // finally, delete that ws
     408           0 :     return DeleteChars(node, startOffset, node, endOffset);
     409             :   }
     410             : 
     411           0 :   return NS_OK;
     412             : }
     413             : 
     414             : nsresult
     415           0 : WSRunObject::DeleteWSForward()
     416             : {
     417           0 :   WSPoint point = GetCharAfter(mNode, mOffset);
     418           0 :   NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
     419             : 
     420             :   // Easy case, preformatted ws.
     421           0 :   if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
     422           0 :     return DeleteChars(point.mTextNode, point.mOffset,
     423           0 :                        point.mTextNode, point.mOffset + 1);
     424             :   }
     425             : 
     426             :   // Caller's job to ensure that next char is really ws.  If it is normal ws,
     427             :   // we need to delete the whole run.
     428           0 :   if (nsCRT::IsAsciiSpace(point.mChar)) {
     429           0 :     RefPtr<Text> startNodeText, endNodeText;
     430             :     int32_t startOffset, endOffset;
     431           0 :     GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
     432           0 :                      getter_AddRefs(startNodeText), &startOffset,
     433           0 :                      getter_AddRefs(endNodeText), &endOffset);
     434             : 
     435             :     // Adjust surrounding ws
     436           0 :     nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
     437             :     nsresult rv =
     438           0 :       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
     439             :                                         address_of(startNode), &startOffset,
     440           0 :                                         address_of(endNode), &endOffset);
     441           0 :     NS_ENSURE_SUCCESS(rv, rv);
     442             : 
     443             :     // Finally, delete that ws
     444           0 :     return DeleteChars(startNode, startOffset, endNode, endOffset);
     445             :   }
     446             : 
     447           0 :   if (point.mChar == nbsp) {
     448           0 :     nsCOMPtr<nsINode> node(point.mTextNode);
     449             :     // Adjust surrounding ws
     450           0 :     int32_t startOffset = point.mOffset;
     451           0 :     int32_t endOffset = point.mOffset+1;
     452             :     nsresult rv =
     453           0 :       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
     454             :                                         address_of(node), &startOffset,
     455           0 :                                         address_of(node), &endOffset);
     456           0 :     NS_ENSURE_SUCCESS(rv, rv);
     457             : 
     458             :     // Finally, delete that ws
     459           0 :     return DeleteChars(node, startOffset, node, endOffset);
     460             :   }
     461             : 
     462           0 :   return NS_OK;
     463             : }
     464             : 
     465             : void
     466           0 : WSRunObject::PriorVisibleNode(nsINode* aNode,
     467             :                               int32_t aOffset,
     468             :                               nsCOMPtr<nsINode>* outVisNode,
     469             :                               int32_t* outVisOffset,
     470             :                               WSType* outType)
     471             : {
     472             :   // Find first visible thing before the point.  Position
     473             :   // outVisNode/outVisOffset just _after_ that thing.  If we don't find
     474             :   // anything return start of ws.
     475           0 :   MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
     476             : 
     477             :   WSFragment* run;
     478           0 :   FindRun(aNode, aOffset, &run, false);
     479             : 
     480             :   // Is there a visible run there or earlier?
     481           0 :   for (; run; run = run->mLeft) {
     482           0 :     if (run->mType == WSType::normalWS) {
     483           0 :       WSPoint point = GetCharBefore(aNode, aOffset);
     484             :       // When it's a non-empty text node, return it.
     485           0 :       if (point.mTextNode && point.mTextNode->Length()) {
     486           0 :         *outVisNode = point.mTextNode;
     487           0 :         *outVisOffset = point.mOffset + 1;
     488           0 :         if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
     489           0 :           *outType = WSType::normalWS;
     490             :         } else {
     491           0 :           *outType = WSType::text;
     492             :         }
     493           0 :         return;
     494             :       }
     495             :       // If no text node, keep looking.  We should eventually fall out of loop
     496             :     }
     497             :   }
     498             : 
     499             :   // If we get here, then nothing in ws data to find.  Return start reason.
     500           0 :   *outVisNode = mStartReasonNode;
     501             :   // This really isn't meaningful if mStartReasonNode != mStartNode
     502           0 :   *outVisOffset = mStartOffset;
     503           0 :   *outType = mStartReason;
     504             : }
     505             : 
     506             : 
     507             : void
     508           0 : WSRunObject::NextVisibleNode(nsINode* aNode,
     509             :                              int32_t aOffset,
     510             :                              nsCOMPtr<nsINode>* outVisNode,
     511             :                              int32_t* outVisOffset,
     512             :                              WSType* outType)
     513             : {
     514             :   // Find first visible thing after the point.  Position
     515             :   // outVisNode/outVisOffset just _before_ that thing.  If we don't find
     516             :   // anything return end of ws.
     517           0 :   MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
     518             : 
     519             :   WSFragment* run;
     520           0 :   FindRun(aNode, aOffset, &run, true);
     521             : 
     522             :   // Is there a visible run there or later?
     523           0 :   for (; run; run = run->mRight) {
     524           0 :     if (run->mType == WSType::normalWS) {
     525           0 :       WSPoint point = GetCharAfter(aNode, aOffset);
     526             :       // When it's a non-empty text node, return it.
     527           0 :       if (point.mTextNode && point.mTextNode->Length()) {
     528           0 :         *outVisNode = point.mTextNode;
     529           0 :         *outVisOffset = point.mOffset;
     530           0 :         if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
     531           0 :           *outType = WSType::normalWS;
     532             :         } else {
     533           0 :           *outType = WSType::text;
     534             :         }
     535           0 :         return;
     536             :       }
     537             :       // If no text node, keep looking.  We should eventually fall out of loop
     538             :     }
     539             :   }
     540             : 
     541             :   // If we get here, then nothing in ws data to find.  Return end reason
     542           0 :   *outVisNode = mEndReasonNode;
     543             :   // This really isn't meaningful if mEndReasonNode != mEndNode
     544           0 :   *outVisOffset = mEndOffset;
     545           0 :   *outType = mEndReason;
     546             : }
     547             : 
     548             : nsresult
     549           0 : WSRunObject::AdjustWhitespace()
     550             : {
     551             :   // this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
     552             :   // replacing them with regualr ascii space if possible.  Keeping things simple
     553             :   // for now and just trying to fix up the trailing ws in the run.
     554           0 :   if (!mLastNBSPNode) {
     555             :     // nothing to do!
     556           0 :     return NS_OK;
     557             :   }
     558           0 :   WSFragment *curRun = mStartRun;
     559           0 :   while (curRun) {
     560             :     // look for normal ws run
     561           0 :     if (curRun->mType == WSType::normalWS) {
     562           0 :       nsresult rv = CheckTrailingNBSPOfRun(curRun);
     563           0 :       if (NS_FAILED(rv)) {
     564           0 :         return rv;
     565             :       }
     566             :     }
     567           0 :     curRun = curRun->mRight;
     568             :   }
     569           0 :   return NS_OK;
     570             : }
     571             : 
     572             : 
     573             : //--------------------------------------------------------------------------------------------
     574             : //   protected methods
     575             : //--------------------------------------------------------------------------------------------
     576             : 
     577             : nsINode*
     578           0 : WSRunObject::GetWSBoundingParent()
     579             : {
     580           0 :   NS_ENSURE_TRUE(mNode, nullptr);
     581           0 :   OwningNonNull<nsINode> wsBoundingParent = *mNode;
     582           0 :   while (!IsBlockNode(wsBoundingParent)) {
     583           0 :     nsCOMPtr<nsINode> parent = wsBoundingParent->GetParentNode();
     584           0 :     if (!parent || !mHTMLEditor->IsEditable(parent)) {
     585           0 :       break;
     586             :     }
     587           0 :     wsBoundingParent = parent;
     588             :   }
     589           0 :   return wsBoundingParent;
     590             : }
     591             : 
     592             : nsresult
     593           0 : WSRunObject::GetWSNodes()
     594             : {
     595             :   // collect up an array of nodes that are contiguous with the insertion point
     596             :   // and which contain only whitespace.  Stop if you reach non-ws text or a new
     597             :   // block boundary.
     598           0 :   EditorDOMPoint start(mNode, mOffset), end(mNode, mOffset);
     599           0 :   nsCOMPtr<nsINode> wsBoundingParent = GetWSBoundingParent();
     600             : 
     601             :   // first look backwards to find preceding ws nodes
     602           0 :   if (RefPtr<Text> textNode = mNode->GetAsText()) {
     603           0 :     const nsTextFragment* textFrag = textNode->GetText();
     604             : 
     605           0 :     mNodeArray.InsertElementAt(0, textNode);
     606           0 :     if (mOffset) {
     607           0 :       for (int32_t pos = mOffset - 1; pos >= 0; pos--) {
     608             :         // sanity bounds check the char position.  bug 136165
     609           0 :         if (uint32_t(pos) >= textFrag->GetLength()) {
     610           0 :           NS_NOTREACHED("looking beyond end of text fragment");
     611           0 :           continue;
     612             :         }
     613           0 :         char16_t theChar = textFrag->CharAt(pos);
     614           0 :         if (!nsCRT::IsAsciiSpace(theChar)) {
     615           0 :           if (theChar != nbsp) {
     616           0 :             mStartNode = textNode;
     617           0 :             mStartOffset = pos + 1;
     618           0 :             mStartReason = WSType::text;
     619           0 :             mStartReasonNode = textNode;
     620           0 :             break;
     621             :           }
     622             :           // as we look backwards update our earliest found nbsp
     623           0 :           mFirstNBSPNode = textNode;
     624           0 :           mFirstNBSPOffset = pos;
     625             :           // also keep track of latest nbsp so far
     626           0 :           if (!mLastNBSPNode) {
     627           0 :             mLastNBSPNode = textNode;
     628           0 :             mLastNBSPOffset = pos;
     629             :           }
     630             :         }
     631           0 :         start.node = textNode;
     632           0 :         start.offset = pos;
     633             :       }
     634             :     }
     635             :   }
     636             : 
     637           0 :   while (!mStartNode) {
     638             :     // we haven't found the start of ws yet.  Keep looking
     639           0 :     nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(start, wsBoundingParent);
     640           0 :     if (priorNode) {
     641           0 :       if (IsBlockNode(priorNode)) {
     642           0 :         mStartNode = start.node;
     643           0 :         mStartOffset = start.offset;
     644           0 :         mStartReason = WSType::otherBlock;
     645           0 :         mStartReasonNode = priorNode;
     646           0 :       } else if (RefPtr<Text> textNode = priorNode->GetAsText()) {
     647           0 :         mNodeArray.InsertElementAt(0, textNode);
     648             :         const nsTextFragment *textFrag;
     649           0 :         if (!textNode || !(textFrag = textNode->GetText())) {
     650           0 :           return NS_ERROR_NULL_POINTER;
     651             :         }
     652           0 :         uint32_t len = textNode->TextLength();
     653             : 
     654           0 :         if (len < 1) {
     655             :           // Zero length text node. Set start point to it
     656             :           // so we can get past it!
     657           0 :           start.SetPoint(priorNode, 0);
     658             :         } else {
     659           0 :           for (int32_t pos = len - 1; pos >= 0; pos--) {
     660             :             // sanity bounds check the char position.  bug 136165
     661           0 :             if (uint32_t(pos) >= textFrag->GetLength()) {
     662           0 :               NS_NOTREACHED("looking beyond end of text fragment");
     663           0 :               continue;
     664             :             }
     665           0 :             char16_t theChar = textFrag->CharAt(pos);
     666           0 :             if (!nsCRT::IsAsciiSpace(theChar)) {
     667           0 :               if (theChar != nbsp) {
     668           0 :                 mStartNode = textNode;
     669           0 :                 mStartOffset = pos + 1;
     670           0 :                 mStartReason = WSType::text;
     671           0 :                 mStartReasonNode = textNode;
     672           0 :                 break;
     673             :               }
     674             :               // as we look backwards update our earliest found nbsp
     675           0 :               mFirstNBSPNode = textNode;
     676           0 :               mFirstNBSPOffset = pos;
     677             :               // also keep track of latest nbsp so far
     678           0 :               if (!mLastNBSPNode) {
     679           0 :                 mLastNBSPNode = textNode;
     680           0 :                 mLastNBSPOffset = pos;
     681             :               }
     682             :             }
     683           0 :             start.SetPoint(textNode, pos);
     684             :           }
     685             :         }
     686             :       } else {
     687             :         // it's a break or a special node, like <img>, that is not a block and not
     688             :         // a break but still serves as a terminator to ws runs.
     689           0 :         mStartNode = start.node;
     690           0 :         mStartOffset = start.offset;
     691           0 :         if (TextEditUtils::IsBreak(priorNode)) {
     692           0 :           mStartReason = WSType::br;
     693             :         } else {
     694           0 :           mStartReason = WSType::special;
     695             :         }
     696           0 :         mStartReasonNode = priorNode;
     697             :       }
     698             :     } else {
     699             :       // no prior node means we exhausted wsBoundingParent
     700           0 :       mStartNode = start.node;
     701           0 :       mStartOffset = start.offset;
     702           0 :       mStartReason = WSType::thisBlock;
     703           0 :       mStartReasonNode = wsBoundingParent;
     704             :     }
     705             :   }
     706             : 
     707             :   // then look ahead to find following ws nodes
     708           0 :   if (RefPtr<Text> textNode = mNode->GetAsText()) {
     709             :     // don't need to put it on list. it already is from code above
     710           0 :     const nsTextFragment *textFrag = textNode->GetText();
     711             : 
     712           0 :     uint32_t len = textNode->TextLength();
     713           0 :     if (uint16_t(mOffset)<len) {
     714           0 :       for (uint32_t pos = mOffset; pos < len; pos++) {
     715             :         // sanity bounds check the char position.  bug 136165
     716           0 :         if (pos >= textFrag->GetLength()) {
     717           0 :           NS_NOTREACHED("looking beyond end of text fragment");
     718           0 :           continue;
     719             :         }
     720           0 :         char16_t theChar = textFrag->CharAt(pos);
     721           0 :         if (!nsCRT::IsAsciiSpace(theChar)) {
     722           0 :           if (theChar != nbsp) {
     723           0 :             mEndNode = textNode;
     724           0 :             mEndOffset = pos;
     725           0 :             mEndReason = WSType::text;
     726           0 :             mEndReasonNode = textNode;
     727           0 :             break;
     728             :           }
     729             :           // as we look forwards update our latest found nbsp
     730           0 :           mLastNBSPNode = textNode;
     731           0 :           mLastNBSPOffset = pos;
     732             :           // also keep track of earliest nbsp so far
     733           0 :           if (!mFirstNBSPNode) {
     734           0 :             mFirstNBSPNode = textNode;
     735           0 :             mFirstNBSPOffset = pos;
     736             :           }
     737             :         }
     738           0 :         end.SetPoint(textNode, pos + 1);
     739             :       }
     740             :     }
     741             :   }
     742             : 
     743           0 :   while (!mEndNode) {
     744             :     // we haven't found the end of ws yet.  Keep looking
     745           0 :     nsCOMPtr<nsIContent> nextNode = GetNextWSNode(end, wsBoundingParent);
     746           0 :     if (nextNode) {
     747           0 :       if (IsBlockNode(nextNode)) {
     748             :         // we encountered a new block.  therefore no more ws.
     749           0 :         mEndNode = end.node;
     750           0 :         mEndOffset = end.offset;
     751           0 :         mEndReason = WSType::otherBlock;
     752           0 :         mEndReasonNode = nextNode;
     753           0 :       } else if (RefPtr<Text> textNode = nextNode->GetAsText()) {
     754           0 :         mNodeArray.AppendElement(textNode);
     755             :         const nsTextFragment *textFrag;
     756           0 :         if (!textNode || !(textFrag = textNode->GetText())) {
     757           0 :           return NS_ERROR_NULL_POINTER;
     758             :         }
     759           0 :         uint32_t len = textNode->TextLength();
     760             : 
     761           0 :         if (len < 1) {
     762             :           // Zero length text node. Set end point to it
     763             :           // so we can get past it!
     764           0 :           end.SetPoint(textNode, 0);
     765             :         } else {
     766           0 :           for (uint32_t pos = 0; pos < len; pos++) {
     767             :             // sanity bounds check the char position.  bug 136165
     768           0 :             if (pos >= textFrag->GetLength()) {
     769           0 :               NS_NOTREACHED("looking beyond end of text fragment");
     770           0 :               continue;
     771             :             }
     772           0 :             char16_t theChar = textFrag->CharAt(pos);
     773           0 :             if (!nsCRT::IsAsciiSpace(theChar)) {
     774           0 :               if (theChar != nbsp) {
     775           0 :                 mEndNode = textNode;
     776           0 :                 mEndOffset = pos;
     777           0 :                 mEndReason = WSType::text;
     778           0 :                 mEndReasonNode = textNode;
     779           0 :                 break;
     780             :               }
     781             :               // as we look forwards update our latest found nbsp
     782           0 :               mLastNBSPNode = textNode;
     783           0 :               mLastNBSPOffset = pos;
     784             :               // also keep track of earliest nbsp so far
     785           0 :               if (!mFirstNBSPNode) {
     786           0 :                 mFirstNBSPNode = textNode;
     787           0 :                 mFirstNBSPOffset = pos;
     788             :               }
     789             :             }
     790           0 :             end.SetPoint(textNode, pos + 1);
     791             :           }
     792             :         }
     793             :       } else {
     794             :         // we encountered a break or a special node, like <img>,
     795             :         // that is not a block and not a break but still
     796             :         // serves as a terminator to ws runs.
     797           0 :         mEndNode = end.node;
     798           0 :         mEndOffset = end.offset;
     799           0 :         if (TextEditUtils::IsBreak(nextNode)) {
     800           0 :           mEndReason = WSType::br;
     801             :         } else {
     802           0 :           mEndReason = WSType::special;
     803             :         }
     804           0 :         mEndReasonNode = nextNode;
     805             :       }
     806             :     } else {
     807             :       // no next node means we exhausted wsBoundingParent
     808           0 :       mEndNode = end.node;
     809           0 :       mEndOffset = end.offset;
     810           0 :       mEndReason = WSType::thisBlock;
     811           0 :       mEndReasonNode = wsBoundingParent;
     812             :     }
     813             :   }
     814             : 
     815           0 :   return NS_OK;
     816             : }
     817             : 
     818             : void
     819           0 : WSRunObject::GetRuns()
     820             : {
     821           0 :   ClearRuns();
     822             : 
     823             :   // handle some easy cases first
     824           0 :   mHTMLEditor->IsPreformatted(GetAsDOMNode(mNode), &mPRE);
     825             :   // if it's preformatedd, or if we are surrounded by text or special, it's all one
     826             :   // big normal ws run
     827           0 :   if (mPRE ||
     828           0 :       ((mStartReason == WSType::text || mStartReason == WSType::special) &&
     829           0 :        (mEndReason == WSType::text || mEndReason == WSType::special ||
     830           0 :         mEndReason == WSType::br))) {
     831           0 :     MakeSingleWSRun(WSType::normalWS);
     832           0 :     return;
     833             :   }
     834             : 
     835             :   // if we are before or after a block (or after a break), and there are no nbsp's,
     836             :   // then it's all non-rendering ws.
     837           0 :   if (!mFirstNBSPNode && !mLastNBSPNode &&
     838           0 :       ((mStartReason & WSType::block) || mStartReason == WSType::br ||
     839           0 :        (mEndReason & WSType::block))) {
     840           0 :     WSType wstype;
     841           0 :     if ((mStartReason & WSType::block) || mStartReason == WSType::br) {
     842           0 :       wstype = WSType::leadingWS;
     843             :     }
     844           0 :     if (mEndReason & WSType::block) {
     845           0 :       wstype |= WSType::trailingWS;
     846             :     }
     847           0 :     MakeSingleWSRun(wstype);
     848           0 :     return;
     849             :   }
     850             : 
     851             :   // otherwise a little trickier.  shucks.
     852           0 :   mStartRun = new WSFragment();
     853           0 :   mStartRun->mStartNode = mStartNode;
     854           0 :   mStartRun->mStartOffset = mStartOffset;
     855             : 
     856           0 :   if (mStartReason & WSType::block || mStartReason == WSType::br) {
     857             :     // set up mStartRun
     858           0 :     mStartRun->mType = WSType::leadingWS;
     859           0 :     mStartRun->mEndNode = mFirstNBSPNode;
     860           0 :     mStartRun->mEndOffset = mFirstNBSPOffset;
     861           0 :     mStartRun->mLeftType = mStartReason;
     862           0 :     mStartRun->mRightType = WSType::normalWS;
     863             : 
     864             :     // set up next run
     865           0 :     WSFragment *normalRun = new WSFragment();
     866           0 :     mStartRun->mRight = normalRun;
     867           0 :     normalRun->mType = WSType::normalWS;
     868           0 :     normalRun->mStartNode = mFirstNBSPNode;
     869           0 :     normalRun->mStartOffset = mFirstNBSPOffset;
     870           0 :     normalRun->mLeftType = WSType::leadingWS;
     871           0 :     normalRun->mLeft = mStartRun;
     872           0 :     if (mEndReason != WSType::block) {
     873             :       // then no trailing ws.  this normal run ends the overall ws run.
     874           0 :       normalRun->mRightType = mEndReason;
     875           0 :       normalRun->mEndNode   = mEndNode;
     876           0 :       normalRun->mEndOffset = mEndOffset;
     877           0 :       mEndRun = normalRun;
     878             :     } else {
     879             :       // we might have trailing ws.
     880             :       // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
     881             :       // will point to it, even though in general start/end points not
     882             :       // guaranteed to be in text nodes.
     883           0 :       if (mLastNBSPNode == mEndNode && mLastNBSPOffset == mEndOffset - 1) {
     884             :         // normal ws runs right up to adjacent block (nbsp next to block)
     885           0 :         normalRun->mRightType = mEndReason;
     886           0 :         normalRun->mEndNode   = mEndNode;
     887           0 :         normalRun->mEndOffset = mEndOffset;
     888           0 :         mEndRun = normalRun;
     889             :       } else {
     890           0 :         normalRun->mEndNode = mLastNBSPNode;
     891           0 :         normalRun->mEndOffset = mLastNBSPOffset+1;
     892           0 :         normalRun->mRightType = WSType::trailingWS;
     893             : 
     894             :         // set up next run
     895           0 :         WSFragment *lastRun = new WSFragment();
     896           0 :         lastRun->mType = WSType::trailingWS;
     897           0 :         lastRun->mStartNode = mLastNBSPNode;
     898           0 :         lastRun->mStartOffset = mLastNBSPOffset+1;
     899           0 :         lastRun->mEndNode = mEndNode;
     900           0 :         lastRun->mEndOffset = mEndOffset;
     901           0 :         lastRun->mLeftType = WSType::normalWS;
     902           0 :         lastRun->mLeft = normalRun;
     903           0 :         lastRun->mRightType = mEndReason;
     904           0 :         mEndRun = lastRun;
     905           0 :         normalRun->mRight = lastRun;
     906             :       }
     907             :     }
     908             :   } else {
     909             :     // mStartReason is not WSType::block or WSType::br; set up mStartRun
     910           0 :     mStartRun->mType = WSType::normalWS;
     911           0 :     mStartRun->mEndNode = mLastNBSPNode;
     912           0 :     mStartRun->mEndOffset = mLastNBSPOffset+1;
     913           0 :     mStartRun->mLeftType = mStartReason;
     914             : 
     915             :     // we might have trailing ws.
     916             :     // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
     917             :     // will point to it, even though in general start/end points not
     918             :     // guaranteed to be in text nodes.
     919           0 :     if (mLastNBSPNode == mEndNode && mLastNBSPOffset == (mEndOffset - 1)) {
     920           0 :       mStartRun->mRightType = mEndReason;
     921           0 :       mStartRun->mEndNode   = mEndNode;
     922           0 :       mStartRun->mEndOffset = mEndOffset;
     923           0 :       mEndRun = mStartRun;
     924             :     } else {
     925             :       // set up next run
     926           0 :       WSFragment *lastRun = new WSFragment();
     927           0 :       lastRun->mType = WSType::trailingWS;
     928           0 :       lastRun->mStartNode = mLastNBSPNode;
     929           0 :       lastRun->mStartOffset = mLastNBSPOffset+1;
     930           0 :       lastRun->mLeftType = WSType::normalWS;
     931           0 :       lastRun->mLeft = mStartRun;
     932           0 :       lastRun->mRightType = mEndReason;
     933           0 :       mEndRun = lastRun;
     934           0 :       mStartRun->mRight = lastRun;
     935           0 :       mStartRun->mRightType = WSType::trailingWS;
     936             :     }
     937             :   }
     938             : }
     939             : 
     940             : void
     941           0 : WSRunObject::ClearRuns()
     942             : {
     943             :   WSFragment *tmp, *run;
     944           0 :   run = mStartRun;
     945           0 :   while (run) {
     946           0 :     tmp = run->mRight;
     947           0 :     delete run;
     948           0 :     run = tmp;
     949             :   }
     950           0 :   mStartRun = 0;
     951           0 :   mEndRun = 0;
     952           0 : }
     953             : 
     954             : void
     955           0 : WSRunObject::MakeSingleWSRun(WSType aType)
     956             : {
     957           0 :   mStartRun = new WSFragment();
     958             : 
     959           0 :   mStartRun->mStartNode   = mStartNode;
     960           0 :   mStartRun->mStartOffset = mStartOffset;
     961           0 :   mStartRun->mType        = aType;
     962           0 :   mStartRun->mEndNode     = mEndNode;
     963           0 :   mStartRun->mEndOffset   = mEndOffset;
     964           0 :   mStartRun->mLeftType    = mStartReason;
     965           0 :   mStartRun->mRightType   = mEndReason;
     966             : 
     967           0 :   mEndRun  = mStartRun;
     968           0 : }
     969             : 
     970             : nsIContent*
     971           0 : WSRunObject::GetPreviousWSNodeInner(nsINode* aStartNode,
     972             :                                     nsINode* aBlockParent)
     973             : {
     974             :   // Can't really recycle various getnext/prior routines because we have
     975             :   // special needs here.  Need to step into inline containers but not block
     976             :   // containers.
     977           0 :   MOZ_ASSERT(aStartNode && aBlockParent);
     978             : 
     979           0 :   nsCOMPtr<nsIContent> priorNode = aStartNode->GetPreviousSibling();
     980           0 :   OwningNonNull<nsINode> curNode = *aStartNode;
     981           0 :   while (!priorNode) {
     982             :     // We have exhausted nodes in parent of aStartNode.
     983           0 :     nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
     984           0 :     NS_ENSURE_TRUE(curParent, nullptr);
     985           0 :     if (curParent == aBlockParent) {
     986             :       // We have exhausted nodes in the block parent.  The convention here is
     987             :       // to return null.
     988           0 :       return nullptr;
     989             :     }
     990             :     // We have a parent: look for previous sibling
     991           0 :     priorNode = curParent->GetPreviousSibling();
     992           0 :     curNode = curParent;
     993             :   }
     994             :   // We have a prior node.  If it's a block, return it.
     995           0 :   if (IsBlockNode(priorNode)) {
     996           0 :     return priorNode;
     997             :   }
     998           0 :   if (mHTMLEditor->IsContainer(priorNode)) {
     999             :     // Else if it's a container, get deep rightmost child
    1000           0 :     nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
    1001           0 :     if (child) {
    1002           0 :       return child;
    1003             :     }
    1004             :   }
    1005             :   // Else return the node itself
    1006           0 :   return priorNode;
    1007             : }
    1008             : 
    1009             : nsIContent*
    1010           0 : WSRunObject::GetPreviousWSNode(EditorDOMPoint aPoint,
    1011             :                                nsINode* aBlockParent)
    1012             : {
    1013             :   // Can't really recycle various getnext/prior routines because we
    1014             :   // have special needs here.  Need to step into inline containers but
    1015             :   // not block containers.
    1016           0 :   MOZ_ASSERT(aPoint.node && aBlockParent);
    1017             : 
    1018           0 :   if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
    1019           0 :     return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
    1020             :   }
    1021           0 :   if (!mHTMLEditor->IsContainer(aPoint.node)) {
    1022           0 :     return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
    1023             :   }
    1024             : 
    1025           0 :   if (!aPoint.offset) {
    1026           0 :     if (aPoint.node == aBlockParent) {
    1027             :       // We are at start of the block.
    1028           0 :       return nullptr;
    1029             :     }
    1030             : 
    1031             :     // We are at start of non-block container
    1032           0 :     return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
    1033             :   }
    1034             : 
    1035           0 :   nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
    1036           0 :   NS_ENSURE_TRUE(startContent, nullptr);
    1037           0 :   nsCOMPtr<nsIContent> priorNode = startContent->GetChildAt(aPoint.offset - 1);
    1038           0 :   NS_ENSURE_TRUE(priorNode, nullptr);
    1039             :   // We have a prior node.  If it's a block, return it.
    1040           0 :   if (IsBlockNode(priorNode)) {
    1041           0 :     return priorNode;
    1042             :   }
    1043           0 :   if (mHTMLEditor->IsContainer(priorNode)) {
    1044             :     // Else if it's a container, get deep rightmost child
    1045           0 :     nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
    1046           0 :     if (child) {
    1047           0 :       return child;
    1048             :     }
    1049             :   }
    1050             :   // Else return the node itself
    1051           0 :   return priorNode;
    1052             : }
    1053             : 
    1054             : nsIContent*
    1055           0 : WSRunObject::GetNextWSNodeInner(nsINode* aStartNode,
    1056             :                                 nsINode* aBlockParent)
    1057             : {
    1058             :   // Can't really recycle various getnext/prior routines because we have
    1059             :   // special needs here.  Need to step into inline containers but not block
    1060             :   // containers.
    1061           0 :   MOZ_ASSERT(aStartNode && aBlockParent);
    1062             : 
    1063           0 :   nsCOMPtr<nsIContent> nextNode = aStartNode->GetNextSibling();
    1064           0 :   nsCOMPtr<nsINode> curNode = aStartNode;
    1065           0 :   while (!nextNode) {
    1066             :     // We have exhausted nodes in parent of aStartNode.
    1067           0 :     nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
    1068           0 :     NS_ENSURE_TRUE(curParent, nullptr);
    1069           0 :     if (curParent == aBlockParent) {
    1070             :       // We have exhausted nodes in the block parent.  The convention here is
    1071             :       // to return null.
    1072           0 :       return nullptr;
    1073             :     }
    1074             :     // We have a parent: look for next sibling
    1075           0 :     nextNode = curParent->GetNextSibling();
    1076           0 :     curNode = curParent;
    1077             :   }
    1078             :   // We have a next node.  If it's a block, return it.
    1079           0 :   if (IsBlockNode(nextNode)) {
    1080           0 :     return nextNode;
    1081             :   }
    1082           0 :   if (mHTMLEditor->IsContainer(nextNode)) {
    1083             :     // Else if it's a container, get deep leftmost child
    1084           0 :     nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
    1085           0 :     if (child) {
    1086           0 :       return child;
    1087             :     }
    1088             :   }
    1089             :   // Else return the node itself
    1090           0 :   return nextNode;
    1091             : }
    1092             : 
    1093             : nsIContent*
    1094           0 : WSRunObject::GetNextWSNode(EditorDOMPoint aPoint,
    1095             :                            nsINode* aBlockParent)
    1096             : {
    1097             :   // Can't really recycle various getnext/prior routines because we have
    1098             :   // special needs here.  Need to step into inline containers but not block
    1099             :   // containers.
    1100           0 :   MOZ_ASSERT(aPoint.node && aBlockParent);
    1101             : 
    1102           0 :   if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
    1103           0 :     return GetNextWSNodeInner(aPoint.node, aBlockParent);
    1104             :   }
    1105           0 :   if (!mHTMLEditor->IsContainer(aPoint.node)) {
    1106           0 :     return GetNextWSNodeInner(aPoint.node, aBlockParent);
    1107             :   }
    1108             : 
    1109           0 :   nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
    1110           0 :   NS_ENSURE_TRUE(startContent, nullptr);
    1111             : 
    1112           0 :   nsCOMPtr<nsIContent> nextNode = startContent->GetChildAt(aPoint.offset);
    1113           0 :   if (!nextNode) {
    1114           0 :     if (aPoint.node == aBlockParent) {
    1115             :       // We are at end of the block.
    1116           0 :       return nullptr;
    1117             :     }
    1118             : 
    1119             :     // We are at end of non-block container
    1120           0 :     return GetNextWSNodeInner(aPoint.node, aBlockParent);
    1121             :   }
    1122             : 
    1123             :   // We have a next node.  If it's a block, return it.
    1124           0 :   if (IsBlockNode(nextNode)) {
    1125           0 :     return nextNode;
    1126             :   }
    1127           0 :   if (mHTMLEditor->IsContainer(nextNode)) {
    1128             :     // else if it's a container, get deep leftmost child
    1129           0 :     nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
    1130           0 :     if (child) {
    1131           0 :       return child;
    1132             :     }
    1133             :   }
    1134             :   // Else return the node itself
    1135           0 :   return nextNode;
    1136             : }
    1137             : 
    1138             : nsresult
    1139           0 : WSRunObject::PrepareToDeleteRangePriv(WSRunObject* aEndObject)
    1140             : {
    1141             :   // this routine adjust whitespace before *this* and after aEndObject
    1142             :   // in preperation for the two areas to become adjacent after the
    1143             :   // intervening content is deleted.  It's overly agressive right
    1144             :   // now.  There might be a block boundary remaining between them after
    1145             :   // the deletion, in which case these adjstments are unneeded (though
    1146             :   // I don't think they can ever be harmful?)
    1147             : 
    1148           0 :   NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER);
    1149             : 
    1150             :   // get the runs before and after selection
    1151             :   WSFragment *beforeRun, *afterRun;
    1152           0 :   FindRun(mNode, mOffset, &beforeRun, false);
    1153           0 :   aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, true);
    1154             : 
    1155             :   // trim after run of any leading ws
    1156           0 :   if (afterRun && (afterRun->mType & WSType::leadingWS)) {
    1157             :     nsresult rv =
    1158           0 :       aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset,
    1159           0 :                               afterRun->mEndNode, afterRun->mEndOffset);
    1160           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1161             :   }
    1162             :   // adjust normal ws in afterRun if needed
    1163           0 :   if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
    1164           0 :     if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
    1165           0 :         (!beforeRun && ((mStartReason & WSType::block) ||
    1166           0 :                         mStartReason == WSType::br))) {
    1167             :       // make sure leading char of following ws is an nbsp, so that it will show up
    1168             :       WSPoint point = aEndObject->GetCharAfter(aEndObject->mNode,
    1169           0 :                                                aEndObject->mOffset);
    1170           0 :       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
    1171           0 :         nsresult rv = aEndObject->ConvertToNBSP(point);
    1172           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1173             :       }
    1174             :     }
    1175             :   }
    1176             :   // trim before run of any trailing ws
    1177           0 :   if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
    1178           0 :     nsresult rv = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
    1179           0 :                               mNode, mOffset);
    1180           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1181           0 :   } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) {
    1182           0 :     if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
    1183           0 :         (afterRun && afterRun->mType == WSType::normalWS) ||
    1184           0 :         (!afterRun && (aEndObject->mEndReason & WSType::block))) {
    1185             :       // make sure trailing char of starting ws is an nbsp, so that it will show up
    1186           0 :       WSPoint point = GetCharBefore(mNode, mOffset);
    1187           0 :       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
    1188           0 :         RefPtr<Text> wsStartNode, wsEndNode;
    1189             :         int32_t wsStartOffset, wsEndOffset;
    1190           0 :         GetAsciiWSBounds(eBoth, mNode, mOffset,
    1191           0 :                          getter_AddRefs(wsStartNode), &wsStartOffset,
    1192           0 :                          getter_AddRefs(wsEndNode), &wsEndOffset);
    1193           0 :         point.mTextNode = wsStartNode;
    1194           0 :         point.mOffset = wsStartOffset;
    1195           0 :         nsresult rv = ConvertToNBSP(point);
    1196           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1197             :       }
    1198             :     }
    1199             :   }
    1200           0 :   return NS_OK;
    1201             : }
    1202             : 
    1203             : nsresult
    1204           0 : WSRunObject::PrepareToSplitAcrossBlocksPriv()
    1205             : {
    1206             :   // used to prepare ws to be split across two blocks.  The main issue
    1207             :   // here is make sure normalWS doesn't end up becoming non-significant
    1208             :   // leading or trailing ws after the split.
    1209             : 
    1210             :   // get the runs before and after selection
    1211             :   WSFragment *beforeRun, *afterRun;
    1212           0 :   FindRun(mNode, mOffset, &beforeRun, false);
    1213           0 :   FindRun(mNode, mOffset, &afterRun, true);
    1214             : 
    1215             :   // adjust normal ws in afterRun if needed
    1216           0 :   if (afterRun && afterRun->mType == WSType::normalWS) {
    1217             :     // make sure leading char of following ws is an nbsp, so that it will show up
    1218           0 :     WSPoint point = GetCharAfter(mNode, mOffset);
    1219           0 :     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
    1220           0 :       nsresult rv = ConvertToNBSP(point);
    1221           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1222             :     }
    1223             :   }
    1224             : 
    1225             :   // adjust normal ws in beforeRun if needed
    1226           0 :   if (beforeRun && beforeRun->mType == WSType::normalWS) {
    1227             :     // make sure trailing char of starting ws is an nbsp, so that it will show up
    1228           0 :     WSPoint point = GetCharBefore(mNode, mOffset);
    1229           0 :     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
    1230           0 :       RefPtr<Text> wsStartNode, wsEndNode;
    1231             :       int32_t wsStartOffset, wsEndOffset;
    1232           0 :       GetAsciiWSBounds(eBoth, mNode, mOffset,
    1233           0 :                        getter_AddRefs(wsStartNode), &wsStartOffset,
    1234           0 :                        getter_AddRefs(wsEndNode), &wsEndOffset);
    1235           0 :       point.mTextNode = wsStartNode;
    1236           0 :       point.mOffset = wsStartOffset;
    1237           0 :       nsresult rv = ConvertToNBSP(point);
    1238           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1239             :     }
    1240             :   }
    1241           0 :   return NS_OK;
    1242             : }
    1243             : 
    1244             : nsresult
    1245           0 : WSRunObject::DeleteChars(nsINode* aStartNode,
    1246             :                          int32_t aStartOffset,
    1247             :                          nsINode* aEndNode,
    1248             :                          int32_t aEndOffset)
    1249             : {
    1250             :   // MOOSE: this routine needs to be modified to preserve the integrity of the
    1251             :   // wsFragment info.
    1252           0 :   NS_ENSURE_TRUE(aStartNode && aEndNode, NS_ERROR_NULL_POINTER);
    1253             : 
    1254           0 :   if (aStartNode == aEndNode && aStartOffset == aEndOffset) {
    1255             :     // Nothing to delete
    1256           0 :     return NS_OK;
    1257             :   }
    1258             : 
    1259           0 :   int32_t idx = mNodeArray.IndexOf(aStartNode);
    1260           0 :   if (idx == -1) {
    1261             :     // If our strarting point wasn't one of our ws text nodes, then just go
    1262             :     // through them from the beginning.
    1263           0 :     idx = 0;
    1264             :   }
    1265             : 
    1266           0 :   if (aStartNode == aEndNode && aStartNode->GetAsText()) {
    1267           0 :     return mHTMLEditor->DeleteText(*aStartNode->GetAsText(),
    1268             :         static_cast<uint32_t>(aStartOffset),
    1269           0 :         static_cast<uint32_t>(aEndOffset - aStartOffset));
    1270             :   }
    1271             : 
    1272           0 :   RefPtr<nsRange> range;
    1273           0 :   int32_t count = mNodeArray.Length();
    1274           0 :   for (; idx < count; idx++) {
    1275           0 :     RefPtr<Text> node = mNodeArray[idx];
    1276           0 :     if (!node) {
    1277             :       // We ran out of ws nodes; must have been deleting to end
    1278           0 :       return NS_OK;
    1279             :     }
    1280           0 :     if (node == aStartNode) {
    1281           0 :       uint32_t len = node->Length();
    1282           0 :       if (uint32_t(aStartOffset) < len) {
    1283             :         nsresult rv =
    1284           0 :           mHTMLEditor->DeleteText(*node, AssertedCast<uint32_t>(aStartOffset),
    1285           0 :                                   len - aStartOffset);
    1286           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1287             :       }
    1288           0 :     } else if (node == aEndNode) {
    1289           0 :       if (aEndOffset) {
    1290             :         nsresult rv =
    1291           0 :           mHTMLEditor->DeleteText(*node, 0, AssertedCast<uint32_t>(aEndOffset));
    1292           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1293             :       }
    1294           0 :       break;
    1295             :     } else {
    1296           0 :       if (!range) {
    1297           0 :         range = new nsRange(aStartNode);
    1298             :         nsresult rv =
    1299           0 :           range->SetStartAndEnd(aStartNode, aStartOffset, aEndNode, aEndOffset);
    1300           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1301             :       }
    1302             :       bool nodeBefore, nodeAfter;
    1303             :       nsresult rv =
    1304           0 :         nsRange::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
    1305           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1306           0 :       if (nodeAfter) {
    1307           0 :         break;
    1308             :       }
    1309           0 :       if (!nodeBefore) {
    1310           0 :         rv = mHTMLEditor->DeleteNode(node);
    1311           0 :         NS_ENSURE_SUCCESS(rv, rv);
    1312           0 :         mNodeArray.RemoveElement(node);
    1313           0 :         --count;
    1314           0 :         --idx;
    1315             :       }
    1316             :     }
    1317             :   }
    1318           0 :   return NS_OK;
    1319             : }
    1320             : 
    1321             : WSRunObject::WSPoint
    1322           0 : WSRunObject::GetCharAfter(nsINode* aNode,
    1323             :                           int32_t aOffset)
    1324             : {
    1325           0 :   MOZ_ASSERT(aNode);
    1326             : 
    1327           0 :   int32_t idx = mNodeArray.IndexOf(aNode);
    1328           0 :   if (idx == -1) {
    1329             :     // Use range comparisons to get right ws node
    1330           0 :     return GetWSPointAfter(aNode, aOffset);
    1331             :   }
    1332             :   // Use WSPoint version of GetCharAfter()
    1333           0 :   return GetCharAfter(WSPoint(mNodeArray[idx], aOffset, 0));
    1334             : }
    1335             : 
    1336             : WSRunObject::WSPoint
    1337           0 : WSRunObject::GetCharBefore(nsINode* aNode,
    1338             :                            int32_t aOffset)
    1339             : {
    1340           0 :   MOZ_ASSERT(aNode);
    1341             : 
    1342           0 :   int32_t idx = mNodeArray.IndexOf(aNode);
    1343           0 :   if (idx == -1) {
    1344             :     // Use range comparisons to get right ws node
    1345           0 :     return GetWSPointBefore(aNode, aOffset);
    1346             :   }
    1347             :   // Use WSPoint version of GetCharBefore()
    1348           0 :   return GetCharBefore(WSPoint(mNodeArray[idx], aOffset, 0));
    1349             : }
    1350             : 
    1351             : WSRunObject::WSPoint
    1352           0 : WSRunObject::GetCharAfter(const WSPoint &aPoint)
    1353             : {
    1354           0 :   MOZ_ASSERT(aPoint.mTextNode);
    1355             : 
    1356           0 :   WSPoint outPoint;
    1357           0 :   outPoint.mTextNode = nullptr;
    1358           0 :   outPoint.mOffset = 0;
    1359           0 :   outPoint.mChar = 0;
    1360             : 
    1361           0 :   int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
    1362           0 :   if (idx == -1) {
    1363             :     // Can't find point, but it's not an error
    1364           0 :     return outPoint;
    1365             :   }
    1366             : 
    1367           0 :   if (static_cast<uint16_t>(aPoint.mOffset) < aPoint.mTextNode->TextLength()) {
    1368           0 :     outPoint = aPoint;
    1369           0 :     outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
    1370           0 :     return outPoint;
    1371             :   }
    1372             : 
    1373           0 :   int32_t numNodes = mNodeArray.Length();
    1374           0 :   if (idx + 1 < numNodes) {
    1375           0 :     outPoint.mTextNode = mNodeArray[idx + 1];
    1376           0 :     MOZ_ASSERT(outPoint.mTextNode);
    1377           0 :     outPoint.mOffset = 0;
    1378           0 :     outPoint.mChar = GetCharAt(outPoint.mTextNode, 0);
    1379             :   }
    1380             : 
    1381           0 :   return outPoint;
    1382             : }
    1383             : 
    1384             : WSRunObject::WSPoint
    1385           0 : WSRunObject::GetCharBefore(const WSPoint &aPoint)
    1386             : {
    1387           0 :   MOZ_ASSERT(aPoint.mTextNode);
    1388             : 
    1389           0 :   WSPoint outPoint;
    1390           0 :   outPoint.mTextNode = nullptr;
    1391           0 :   outPoint.mOffset = 0;
    1392           0 :   outPoint.mChar = 0;
    1393             : 
    1394           0 :   int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
    1395           0 :   if (idx == -1) {
    1396             :     // Can't find point, but it's not an error
    1397           0 :     return outPoint;
    1398             :   }
    1399             : 
    1400           0 :   if (aPoint.mOffset) {
    1401           0 :     outPoint = aPoint;
    1402           0 :     outPoint.mOffset--;
    1403           0 :     outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset - 1);
    1404           0 :     return outPoint;
    1405             :   }
    1406             : 
    1407           0 :   if (idx) {
    1408           0 :     outPoint.mTextNode = mNodeArray[idx - 1];
    1409             : 
    1410           0 :     uint32_t len = outPoint.mTextNode->TextLength();
    1411           0 :     if (len) {
    1412           0 :       outPoint.mOffset = len - 1;
    1413           0 :       outPoint.mChar = GetCharAt(outPoint.mTextNode, len - 1);
    1414             :     }
    1415             :   }
    1416           0 :   return outPoint;
    1417             : }
    1418             : 
    1419             : nsresult
    1420           0 : WSRunObject::ConvertToNBSP(WSPoint aPoint)
    1421             : {
    1422             :   // MOOSE: this routine needs to be modified to preserve the integrity of the
    1423             :   // wsFragment info.
    1424           0 :   NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
    1425             : 
    1426             :   // First, insert an nbsp
    1427           0 :   AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
    1428           0 :   nsAutoString nbspStr(nbsp);
    1429             :   nsresult rv =
    1430           0 :     mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *aPoint.mTextNode,
    1431           0 :                                             aPoint.mOffset, true);
    1432           0 :   NS_ENSURE_SUCCESS(rv, rv);
    1433             : 
    1434             :   // Next, find range of ws it will replace
    1435           0 :   RefPtr<Text> startNode, endNode;
    1436           0 :   int32_t startOffset = 0, endOffset = 0;
    1437             : 
    1438           0 :   GetAsciiWSBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
    1439           0 :                    getter_AddRefs(startNode), &startOffset,
    1440           0 :                    getter_AddRefs(endNode), &endOffset);
    1441             : 
    1442             :   // Finally, delete that replaced ws, if any
    1443           0 :   if (startNode) {
    1444           0 :     rv = DeleteChars(startNode, startOffset, endNode, endOffset);
    1445           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1446             :   }
    1447             : 
    1448           0 :   return NS_OK;
    1449             : }
    1450             : 
    1451             : void
    1452           0 : WSRunObject::GetAsciiWSBounds(int16_t aDir,
    1453             :                               nsINode* aNode,
    1454             :                               int32_t aOffset,
    1455             :                               Text** outStartNode,
    1456             :                               int32_t* outStartOffset,
    1457             :                               Text** outEndNode,
    1458             :                               int32_t* outEndOffset)
    1459             : {
    1460           0 :   MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
    1461             :              outEndOffset);
    1462             : 
    1463           0 :   RefPtr<Text> startNode, endNode;
    1464           0 :   int32_t startOffset = 0, endOffset = 0;
    1465             : 
    1466           0 :   if (aDir & eAfter) {
    1467           0 :     WSPoint point = GetCharAfter(aNode, aOffset);
    1468           0 :     if (point.mTextNode) {
    1469             :       // We found a text node, at least
    1470           0 :       startNode = endNode = point.mTextNode;
    1471           0 :       startOffset = endOffset = point.mOffset;
    1472             : 
    1473             :       // Scan ahead to end of ASCII ws
    1474           0 :       for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
    1475           0 :            point = GetCharAfter(point)) {
    1476           0 :         endNode = point.mTextNode;
    1477             :         // endOffset is _after_ ws
    1478           0 :         point.mOffset++;
    1479           0 :         endOffset = point.mOffset;
    1480             :       }
    1481             :     }
    1482             :   }
    1483             : 
    1484           0 :   if (aDir & eBefore) {
    1485           0 :     WSPoint point = GetCharBefore(aNode, aOffset);
    1486           0 :     if (point.mTextNode) {
    1487             :       // We found a text node, at least
    1488           0 :       startNode = point.mTextNode;
    1489           0 :       startOffset = point.mOffset + 1;
    1490           0 :       if (!endNode) {
    1491           0 :         endNode = startNode;
    1492           0 :         endOffset = startOffset;
    1493             :       }
    1494             : 
    1495             :       // Scan back to start of ASCII ws
    1496           0 :       for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
    1497           0 :            point = GetCharBefore(point)) {
    1498           0 :         startNode = point.mTextNode;
    1499           0 :         startOffset = point.mOffset;
    1500             :       }
    1501             :     }
    1502             :   }
    1503             : 
    1504           0 :   startNode.forget(outStartNode);
    1505           0 :   *outStartOffset = startOffset;
    1506           0 :   endNode.forget(outEndNode);
    1507           0 :   *outEndOffset = endOffset;
    1508           0 : }
    1509             : 
    1510             : /**
    1511             :  * Given a dompoint, find the ws run that is before or after it, as caller
    1512             :  * needs
    1513             :  */
    1514             : void
    1515           0 : WSRunObject::FindRun(nsINode* aNode,
    1516             :                      int32_t aOffset,
    1517             :                      WSFragment** outRun,
    1518             :                      bool after)
    1519             : {
    1520           0 :   MOZ_ASSERT(aNode && outRun);
    1521           0 :   *outRun = nullptr;
    1522             : 
    1523           0 :   for (WSFragment* run = mStartRun; run; run = run->mRight) {
    1524           0 :     int32_t comp = run->mStartNode ? nsContentUtils::ComparePoints(aNode,
    1525           0 :         aOffset, run->mStartNode, run->mStartOffset) : -1;
    1526           0 :     if (comp <= 0) {
    1527           0 :       if (after) {
    1528           0 :         *outRun = run;
    1529             :       } else {
    1530             :         // before
    1531           0 :         *outRun = nullptr;
    1532             :       }
    1533           0 :       return;
    1534             :     }
    1535           0 :     comp = run->mEndNode ? nsContentUtils::ComparePoints(aNode, aOffset,
    1536           0 :         run->mEndNode, run->mEndOffset) : -1;
    1537           0 :     if (comp < 0) {
    1538           0 :       *outRun = run;
    1539           0 :       return;
    1540           0 :     } else if (!comp) {
    1541           0 :       if (after) {
    1542           0 :         *outRun = run->mRight;
    1543             :       } else {
    1544             :         // before
    1545           0 :         *outRun = run;
    1546             :       }
    1547           0 :       return;
    1548             :     }
    1549           0 :     if (!run->mRight) {
    1550           0 :       if (after) {
    1551           0 :         *outRun = nullptr;
    1552             :       } else {
    1553             :         // before
    1554           0 :         *outRun = run;
    1555             :       }
    1556           0 :       return;
    1557             :     }
    1558             :   }
    1559             : }
    1560             : 
    1561             : char16_t
    1562           0 : WSRunObject::GetCharAt(Text* aTextNode,
    1563             :                        int32_t aOffset)
    1564             : {
    1565             :   // return 0 if we can't get a char, for whatever reason
    1566           0 :   NS_ENSURE_TRUE(aTextNode, 0);
    1567             : 
    1568           0 :   int32_t len = int32_t(aTextNode->TextLength());
    1569           0 :   if (aOffset < 0 || aOffset >= len) {
    1570           0 :     return 0;
    1571             :   }
    1572           0 :   return aTextNode->GetText()->CharAt(aOffset);
    1573             : }
    1574             : 
    1575             : WSRunObject::WSPoint
    1576           0 : WSRunObject::GetWSPointAfter(nsINode* aNode,
    1577             :                              int32_t aOffset)
    1578             : {
    1579             :   // Note: only to be called if aNode is not a ws node.
    1580             : 
    1581             :   // Binary search on wsnodes
    1582           0 :   uint32_t numNodes = mNodeArray.Length();
    1583             : 
    1584           0 :   if (!numNodes) {
    1585             :     // Do nothing if there are no nodes to search
    1586           0 :     WSPoint outPoint;
    1587           0 :     return outPoint;
    1588             :   }
    1589             : 
    1590           0 :   uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
    1591           0 :   int16_t cmp = 0;
    1592           0 :   RefPtr<Text> curNode;
    1593             : 
    1594             :   // Begin binary search.  We do this because we need to minimize calls to
    1595             :   // ComparePoints(), which is expensive.
    1596           0 :   while (curNum != lastNum) {
    1597           0 :     curNode = mNodeArray[curNum];
    1598           0 :     cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
    1599           0 :     if (cmp < 0) {
    1600           0 :       lastNum = curNum;
    1601             :     } else {
    1602           0 :       firstNum = curNum + 1;
    1603             :     }
    1604           0 :     curNum = (lastNum - firstNum)/2 + firstNum;
    1605           0 :     MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
    1606             :   }
    1607             : 
    1608             :   // When the binary search is complete, we always know that the current node
    1609             :   // is the same as the end node, which is always past our range. Therefore,
    1610             :   // we've found the node immediately after the point of interest.
    1611           0 :   if (curNum == mNodeArray.Length()) {
    1612             :     // hey asked for past our range (it's after the last node). GetCharAfter
    1613             :     // will do the work for us when we pass it the last index of the last node.
    1614           0 :     RefPtr<Text> textNode(mNodeArray[curNum - 1]);
    1615           0 :     WSPoint point(textNode, textNode->TextLength(), 0);
    1616           0 :     return GetCharAfter(point);
    1617             :   } else {
    1618             :     // The char after the point is the first character of our range.
    1619           0 :     RefPtr<Text> textNode(mNodeArray[curNum]);
    1620           0 :     WSPoint point(textNode, 0, 0);
    1621           0 :     return GetCharAfter(point);
    1622             :   }
    1623             : }
    1624             : 
    1625             : WSRunObject::WSPoint
    1626           0 : WSRunObject::GetWSPointBefore(nsINode* aNode,
    1627             :                               int32_t aOffset)
    1628             : {
    1629             :   // Note: only to be called if aNode is not a ws node.
    1630             : 
    1631             :   // Binary search on wsnodes
    1632           0 :   uint32_t numNodes = mNodeArray.Length();
    1633             : 
    1634           0 :   if (!numNodes) {
    1635             :     // Do nothing if there are no nodes to search
    1636           0 :     WSPoint outPoint;
    1637           0 :     return outPoint;
    1638             :   }
    1639             : 
    1640           0 :   uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
    1641           0 :   int16_t cmp = 0;
    1642           0 :   RefPtr<Text>  curNode;
    1643             : 
    1644             :   // Begin binary search.  We do this because we need to minimize calls to
    1645             :   // ComparePoints(), which is expensive.
    1646           0 :   while (curNum != lastNum) {
    1647           0 :     curNode = mNodeArray[curNum];
    1648           0 :     cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
    1649           0 :     if (cmp < 0) {
    1650           0 :       lastNum = curNum;
    1651             :     } else {
    1652           0 :       firstNum = curNum + 1;
    1653             :     }
    1654           0 :     curNum = (lastNum - firstNum)/2 + firstNum;
    1655           0 :     MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
    1656             :   }
    1657             : 
    1658             :   // When the binary search is complete, we always know that the current node
    1659             :   // is the same as the end node, which is always past our range. Therefore,
    1660             :   // we've found the node immediately after the point of interest.
    1661           0 :   if (curNum == mNodeArray.Length()) {
    1662             :     // Get the point before the end of the last node, we can pass the length of
    1663             :     // the node into GetCharBefore, and it will return the last character.
    1664           0 :     RefPtr<Text> textNode(mNodeArray[curNum - 1]);
    1665           0 :     WSPoint point(textNode, textNode->TextLength(), 0);
    1666           0 :     return GetCharBefore(point);
    1667             :   } else {
    1668             :     // We can just ask the current node for the point immediately before it,
    1669             :     // it will handle moving to the previous node (if any) and returning the
    1670             :     // appropriate character
    1671           0 :     RefPtr<Text> textNode(mNodeArray[curNum]);
    1672           0 :     WSPoint point(textNode, 0, 0);
    1673           0 :     return GetCharBefore(point);
    1674             :   }
    1675             : }
    1676             : 
    1677             : nsresult
    1678           0 : WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
    1679             : {
    1680             :   // Try to change an nbsp to a space, if possible, just to prevent nbsp
    1681             :   // proliferation.  Examine what is before and after the trailing nbsp, if
    1682             :   // any.
    1683           0 :   NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER);
    1684           0 :   bool leftCheck = false;
    1685           0 :   bool spaceNBSP = false;
    1686           0 :   bool rightCheck = false;
    1687             : 
    1688             :   // confirm run is normalWS
    1689           0 :   if (aRun->mType != WSType::normalWS) {
    1690           0 :     return NS_ERROR_FAILURE;
    1691             :   }
    1692             : 
    1693             :   // first check for trailing nbsp
    1694           0 :   WSPoint thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
    1695           0 :   if (thePoint.mTextNode && thePoint.mChar == nbsp) {
    1696             :     // now check that what is to the left of it is compatible with replacing nbsp with space
    1697           0 :     WSPoint prevPoint = GetCharBefore(thePoint);
    1698           0 :     if (prevPoint.mTextNode) {
    1699           0 :       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
    1700           0 :         leftCheck = true;
    1701             :       } else {
    1702           0 :         spaceNBSP = true;
    1703             :       }
    1704           0 :     } else if (aRun->mLeftType == WSType::text ||
    1705           0 :                aRun->mLeftType == WSType::special) {
    1706           0 :       leftCheck = true;
    1707             :     }
    1708           0 :     if (leftCheck || spaceNBSP) {
    1709             :       // now check that what is to the right of it is compatible with replacing
    1710             :       // nbsp with space
    1711           0 :       if (aRun->mRightType == WSType::text ||
    1712           0 :           aRun->mRightType == WSType::special ||
    1713           0 :           aRun->mRightType == WSType::br) {
    1714           0 :         rightCheck = true;
    1715             :       }
    1716           0 :       if ((aRun->mRightType & WSType::block) &&
    1717           0 :           IsBlockNode(GetWSBoundingParent())) {
    1718             :         // We are at a block boundary.  Insert a <br>.  Why?  Well, first note
    1719             :         // that the br will have no visible effect since it is up against a
    1720             :         // block boundary.  |foo<br><p>bar| renders like |foo<p>bar| and
    1721             :         // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|.  What
    1722             :         // this <br> addition gets us is the ability to convert a trailing nbsp
    1723             :         // to a space.  Consider: |<body>foo. '</body>|, where ' represents
    1724             :         // selection.  User types space attempting to put 2 spaces after the
    1725             :         // end of their sentence.  We used to do this as: |<body>foo.
    1726             :         // &nbsp</body>|  This caused problems with soft wrapping: the nbsp
    1727             :         // would wrap to the next line, which looked attrocious.  If you try to
    1728             :         // do: |<body>foo.&nbsp </body>| instead, the trailing space is
    1729             :         // invisible because it is against a block boundary.  If you do:
    1730             :         // |<body>foo.&nbsp&nbsp</body>| then you get an even uglier soft
    1731             :         // wrapping problem, where foo is on one line until you type the final
    1732             :         // space, and then "foo  " jumps down to the next line.  Ugh.  The best
    1733             :         // way I can find out of this is to throw in a harmless <br> here,
    1734             :         // which allows us to do: |<body>foo.&nbsp <br></body>|, which doesn't
    1735             :         // cause foo to jump lines, doesn't cause spaces to show up at the
    1736             :         // beginning of soft wrapped lines, and lets the user see 2 spaces when
    1737             :         // they type 2 spaces.
    1738             : 
    1739             :         nsCOMPtr<Element> brNode =
    1740           0 :           mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset);
    1741           0 :         NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
    1742             : 
    1743             :         // Refresh thePoint, prevPoint
    1744           0 :         thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
    1745           0 :         prevPoint = GetCharBefore(thePoint);
    1746           0 :         rightCheck = true;
    1747             :       }
    1748             :     }
    1749           0 :     if (leftCheck && rightCheck) {
    1750             :       // Now replace nbsp with space.  First, insert a space
    1751           0 :       AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
    1752           0 :       nsAutoString spaceStr(char16_t(32));
    1753             :       nsresult rv =
    1754           0 :         mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
    1755           0 :                                                 thePoint.mOffset, true);
    1756           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1757             : 
    1758             :       // Finally, delete that nbsp
    1759           0 :       rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
    1760           0 :                        thePoint.mTextNode, thePoint.mOffset + 2);
    1761           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1762           0 :     } else if (!mPRE && spaceNBSP && rightCheck) {
    1763             :       // Don't mess with this preformatted for now.  We have a run of ASCII
    1764             :       // whitespace (which will render as one space) followed by an nbsp (which
    1765             :       // is at the end of the whitespace run).  Let's switch their order.  This
    1766             :       // will ensure that if someone types two spaces after a sentence, and the
    1767             :       // editor softwraps at this point, the spaces won't be split across lines,
    1768             :       // which looks ugly and is bad for the moose.
    1769             : 
    1770           0 :       RefPtr<Text> startNode, endNode;
    1771             :       int32_t startOffset, endOffset;
    1772           0 :       GetAsciiWSBounds(eBoth, prevPoint.mTextNode, prevPoint.mOffset + 1,
    1773           0 :                        getter_AddRefs(startNode), &startOffset,
    1774           0 :                        getter_AddRefs(endNode), &endOffset);
    1775             : 
    1776             :       // Delete that nbsp
    1777           0 :       nsresult rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset,
    1778           0 :                                 thePoint.mTextNode, thePoint.mOffset + 1);
    1779           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1780             : 
    1781             :       // Finally, insert that nbsp before the ASCII ws run
    1782           0 :       AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
    1783           0 :       nsAutoString nbspStr(nbsp);
    1784           0 :       rv = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *startNode,
    1785           0 :                                                    startOffset, true);
    1786           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1787             :     }
    1788             :   }
    1789           0 :   return NS_OK;
    1790             : }
    1791             : 
    1792             : nsresult
    1793           0 : WSRunObject::CheckTrailingNBSP(WSFragment* aRun,
    1794             :                                nsINode* aNode,
    1795             :                                int32_t aOffset)
    1796             : {
    1797             :   // Try to change an nbsp to a space, if possible, just to prevent nbsp
    1798             :   // proliferation.  This routine is called when we are about to make this
    1799             :   // point in the ws abut an inserted break or text, so we don't have to worry
    1800             :   // about what is after it.  What is after it now will end up after the
    1801             :   // inserted object.
    1802           0 :   NS_ENSURE_TRUE(aRun && aNode, NS_ERROR_NULL_POINTER);
    1803           0 :   bool canConvert = false;
    1804           0 :   WSPoint thePoint = GetCharBefore(aNode, aOffset);
    1805           0 :   if (thePoint.mTextNode && thePoint.mChar == nbsp) {
    1806           0 :     WSPoint prevPoint = GetCharBefore(thePoint);
    1807           0 :     if (prevPoint.mTextNode) {
    1808           0 :       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
    1809           0 :         canConvert = true;
    1810             :       }
    1811           0 :     } else if (aRun->mLeftType == WSType::text ||
    1812           0 :                aRun->mLeftType == WSType::special) {
    1813           0 :       canConvert = true;
    1814             :     }
    1815             :   }
    1816           0 :   if (canConvert) {
    1817             :     // First, insert a space
    1818           0 :     AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
    1819           0 :     nsAutoString spaceStr(char16_t(32));
    1820             :     nsresult rv =
    1821           0 :       mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
    1822           0 :                                               thePoint.mOffset, true);
    1823           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1824             : 
    1825             :     // Finally, delete that nbsp
    1826           0 :     rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
    1827           0 :                      thePoint.mTextNode, thePoint.mOffset + 2);
    1828           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1829             :   }
    1830           0 :   return NS_OK;
    1831             : }
    1832             : 
    1833             : nsresult
    1834           0 : WSRunObject::CheckLeadingNBSP(WSFragment* aRun,
    1835             :                               nsINode* aNode,
    1836             :                               int32_t aOffset)
    1837             : {
    1838             :   // Try to change an nbsp to a space, if possible, just to prevent nbsp
    1839             :   // proliferation This routine is called when we are about to make this point
    1840             :   // in the ws abut an inserted text, so we don't have to worry about what is
    1841             :   // before it.  What is before it now will end up before the inserted text.
    1842           0 :   bool canConvert = false;
    1843           0 :   WSPoint thePoint = GetCharAfter(aNode, aOffset);
    1844           0 :   if (thePoint.mChar == nbsp) {
    1845           0 :     WSPoint tmp = thePoint;
    1846             :     // we want to be after thePoint
    1847           0 :     tmp.mOffset++;
    1848           0 :     WSPoint nextPoint = GetCharAfter(tmp);
    1849           0 :     if (nextPoint.mTextNode) {
    1850           0 :       if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) {
    1851           0 :         canConvert = true;
    1852             :       }
    1853           0 :     } else if (aRun->mRightType == WSType::text ||
    1854           0 :                aRun->mRightType == WSType::special ||
    1855           0 :                aRun->mRightType == WSType::br) {
    1856           0 :       canConvert = true;
    1857             :     }
    1858             :   }
    1859           0 :   if (canConvert) {
    1860             :     // First, insert a space
    1861           0 :     AutoTransactionsConserveSelection dontSpazMySelection(mHTMLEditor);
    1862           0 :     nsAutoString spaceStr(char16_t(32));
    1863             :     nsresult rv =
    1864           0 :       mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr, *thePoint.mTextNode,
    1865           0 :                                               thePoint.mOffset, true);
    1866           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1867             : 
    1868             :     // Finally, delete that nbsp
    1869           0 :     rv = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
    1870           0 :                      thePoint.mTextNode, thePoint.mOffset + 2);
    1871           0 :     NS_ENSURE_SUCCESS(rv, rv);
    1872             :   }
    1873           0 :   return NS_OK;
    1874             : }
    1875             : 
    1876             : 
    1877             : nsresult
    1878           0 : WSRunObject::Scrub()
    1879             : {
    1880           0 :   WSFragment *run = mStartRun;
    1881           0 :   while (run) {
    1882           0 :     if (run->mType & (WSType::leadingWS | WSType::trailingWS)) {
    1883           0 :       nsresult rv = DeleteChars(run->mStartNode, run->mStartOffset,
    1884           0 :                                 run->mEndNode, run->mEndOffset);
    1885           0 :       NS_ENSURE_SUCCESS(rv, rv);
    1886             :     }
    1887           0 :     run = run->mRight;
    1888             :   }
    1889           0 :   return NS_OK;
    1890             : }
    1891             : 
    1892             : bool
    1893           0 : WSRunObject::IsBlockNode(nsINode* aNode)
    1894             : {
    1895           0 :   return aNode && aNode->IsElement() &&
    1896           0 :          HTMLEditor::NodeIsBlockStatic(aNode->AsElement());
    1897             : }
    1898             : 
    1899             : } // namespace mozilla

Generated by: LCOV version 1.13