editor/libeditor/html/nsWSRunObject.cpp

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

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

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial