Bug 1247796. Use keyboardFocusIndicatorColor for ActiveBorder system color keyword...
[gecko.git] / editor / libeditor / nsWSRunObject.cpp
blob8c4ce5e63454d5fd5cb7c651ebabf0feabb242cc
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsWSRunObject.h"
8 #include "mozilla/OwningNonNull.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/mozalloc.h"
13 #include "nsAString.h"
14 #include "nsAutoPtr.h"
15 #include "nsCRT.h"
16 #include "nsContentUtils.h"
17 #include "nsDebug.h"
18 #include "nsEditorUtils.h"
19 #include "nsError.h"
20 #include "nsHTMLEditor.h"
21 #include "nsIContent.h"
22 #include "nsIDOMDocument.h"
23 #include "nsIDOMNode.h"
24 #include "nsISupportsImpl.h"
25 #include "nsRange.h"
26 #include "nsSelectionState.h"
27 #include "nsString.h"
28 #include "nsTextEditUtils.h"
29 #include "nsTextFragment.h"
31 using namespace mozilla;
32 using namespace mozilla::dom;
34 const char16_t nbsp = 160;
36 static bool IsBlockNode(nsINode* node)
38 return node && node->IsElement() &&
39 nsHTMLEditor::NodeIsBlockStatic(node->AsElement());
42 //- constructor / destructor -----------------------------------------------
43 nsWSRunObject::nsWSRunObject(nsHTMLEditor* aEd, nsINode* aNode, int32_t aOffset)
44 : mNode(aNode)
45 , mOffset(aOffset)
46 , mPRE(false)
47 , mStartNode()
48 , mStartOffset(0)
49 , mStartReason()
50 , mStartReasonNode()
51 , mEndNode()
52 , mEndOffset(0)
53 , mEndReason()
54 , mEndReasonNode()
55 , mFirstNBSPNode()
56 , mFirstNBSPOffset(0)
57 , mLastNBSPNode()
58 , mLastNBSPOffset(0)
59 , mNodeArray()
60 , mStartRun(nullptr)
61 , mEndRun(nullptr)
62 , mHTMLEditor(aEd)
64 GetWSNodes();
65 GetRuns();
68 nsWSRunObject::nsWSRunObject(nsHTMLEditor *aEd, nsIDOMNode *aNode, int32_t aOffset) :
69 mNode(do_QueryInterface(aNode))
70 ,mOffset(aOffset)
71 ,mPRE(false)
72 ,mStartNode()
73 ,mStartOffset(0)
74 ,mStartReason()
75 ,mStartReasonNode()
76 ,mEndNode()
77 ,mEndOffset(0)
78 ,mEndReason()
79 ,mEndReasonNode()
80 ,mFirstNBSPNode()
81 ,mFirstNBSPOffset(0)
82 ,mLastNBSPNode()
83 ,mLastNBSPOffset(0)
84 ,mNodeArray()
85 ,mStartRun(nullptr)
86 ,mEndRun(nullptr)
87 ,mHTMLEditor(aEd)
89 GetWSNodes();
90 GetRuns();
93 nsWSRunObject::~nsWSRunObject()
95 ClearRuns();
100 //--------------------------------------------------------------------------------------------
101 // public static methods
102 //--------------------------------------------------------------------------------------------
104 nsresult
105 nsWSRunObject::ScrubBlockBoundary(nsHTMLEditor* aHTMLEd,
106 BlockBoundary aBoundary,
107 nsINode* aBlock,
108 int32_t aOffset)
110 NS_ENSURE_TRUE(aHTMLEd && aBlock, NS_ERROR_NULL_POINTER);
112 int32_t offset;
113 if (aBoundary == kBlockStart) {
114 offset = 0;
115 } else if (aBoundary == kBlockEnd) {
116 offset = aBlock->Length();
117 } else {
118 // Else we are scrubbing an outer boundary - just before or after a block
119 // element.
120 NS_ENSURE_STATE(aOffset >= 0);
121 offset = aOffset;
124 nsWSRunObject theWSObj(aHTMLEd, aBlock, offset);
125 return theWSObj.Scrub();
128 nsresult
129 nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor* aHTMLEd,
130 Element* aLeftBlock,
131 Element* aRightBlock)
133 NS_ENSURE_TRUE(aLeftBlock && aRightBlock && aHTMLEd, NS_ERROR_NULL_POINTER);
135 nsWSRunObject leftWSObj(aHTMLEd, aLeftBlock, aLeftBlock->Length());
136 nsWSRunObject rightWSObj(aHTMLEd, aRightBlock, 0);
138 return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
141 nsresult
142 nsWSRunObject::PrepareToDeleteRange(nsHTMLEditor* aHTMLEd,
143 nsCOMPtr<nsINode>* aStartNode,
144 int32_t* aStartOffset,
145 nsCOMPtr<nsINode>* aEndNode,
146 int32_t* aEndOffset)
148 NS_ENSURE_TRUE(aHTMLEd && aStartNode && *aStartNode && aStartOffset &&
149 aEndNode && *aEndNode && aEndOffset, NS_ERROR_NULL_POINTER);
151 nsAutoTrackDOMPoint trackerStart(aHTMLEd->mRangeUpdater, aStartNode,
152 aStartOffset);
153 nsAutoTrackDOMPoint trackerEnd(aHTMLEd->mRangeUpdater, aEndNode, aEndOffset);
155 nsWSRunObject leftWSObj(aHTMLEd, *aStartNode, *aStartOffset);
156 nsWSRunObject rightWSObj(aHTMLEd, *aEndNode, *aEndOffset);
158 return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
161 nsresult
162 nsWSRunObject::PrepareToDeleteNode(nsHTMLEditor* aHTMLEd,
163 nsIContent* aContent)
165 NS_ENSURE_TRUE(aContent && aHTMLEd, NS_ERROR_NULL_POINTER);
167 nsCOMPtr<nsINode> parent = aContent->GetParentNode();
168 NS_ENSURE_STATE(parent);
169 int32_t offset = parent->IndexOf(aContent);
171 nsWSRunObject leftWSObj(aHTMLEd, parent, offset);
172 nsWSRunObject rightWSObj(aHTMLEd, parent, offset + 1);
174 return leftWSObj.PrepareToDeleteRangePriv(&rightWSObj);
177 nsresult
178 nsWSRunObject::PrepareToSplitAcrossBlocks(nsHTMLEditor* aHTMLEd,
179 nsCOMPtr<nsINode>* aSplitNode,
180 int32_t* aSplitOffset)
182 NS_ENSURE_TRUE(aHTMLEd && aSplitNode && *aSplitNode && aSplitOffset,
183 NS_ERROR_NULL_POINTER);
185 nsAutoTrackDOMPoint tracker(aHTMLEd->mRangeUpdater, aSplitNode, aSplitOffset);
187 nsWSRunObject wsObj(aHTMLEd, *aSplitNode, *aSplitOffset);
189 return wsObj.PrepareToSplitAcrossBlocksPriv();
192 //--------------------------------------------------------------------------------------------
193 // public instance methods
194 //--------------------------------------------------------------------------------------------
196 already_AddRefed<Element>
197 nsWSRunObject::InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
198 int32_t* aInOutOffset,
199 nsIEditor::EDirection aSelect)
201 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
202 // meanwhile, the pre case is handled in WillInsertText in
203 // nsHTMLEditRules.cpp
204 NS_ENSURE_TRUE(aInOutParent && aInOutOffset, nullptr);
206 nsresult res = NS_OK;
207 WSFragment *beforeRun, *afterRun;
208 FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
209 FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
212 // Some scoping for nsAutoTrackDOMPoint. This will track our insertion
213 // point while we tweak any surrounding whitespace
214 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
215 aInOutOffset);
217 // Handle any changes needed to ws run after inserted br
218 if (!afterRun || (afterRun->mType & WSType::trailingWS)) {
219 // Don't need to do anything. Just insert break. ws won't change.
220 } else if (afterRun->mType & WSType::leadingWS) {
221 // Delete the leading ws that is after insertion point. We don't
222 // have to (it would still not be significant after br), but it's
223 // just more aesthetically pleasing to.
224 res = DeleteChars(*aInOutParent, *aInOutOffset,
225 afterRun->mEndNode, afterRun->mEndOffset,
226 eOutsideUserSelectAll);
227 NS_ENSURE_SUCCESS(res, nullptr);
228 } else if (afterRun->mType == WSType::normalWS) {
229 // Need to determine if break at front of non-nbsp run. If so, convert
230 // run to nbsp.
231 WSPoint thePoint = GetCharAfter(*aInOutParent, *aInOutOffset);
232 if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
233 WSPoint prevPoint = GetCharBefore(thePoint);
234 if (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar)) {
235 // We are at start of non-nbsps. Convert to a single nbsp.
236 res = ConvertToNBSP(thePoint);
237 NS_ENSURE_SUCCESS(res, nullptr);
242 // Handle any changes needed to ws run before inserted br
243 if (!beforeRun || (beforeRun->mType & WSType::leadingWS)) {
244 // Don't need to do anything. Just insert break. ws won't change.
245 } else if (beforeRun->mType & WSType::trailingWS) {
246 // Need to delete the trailing ws that is before insertion point, because it
247 // would become significant after break inserted.
248 res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
249 *aInOutParent, *aInOutOffset,
250 eOutsideUserSelectAll);
251 NS_ENSURE_SUCCESS(res, nullptr);
252 } else if (beforeRun->mType == WSType::normalWS) {
253 // Try to change an nbsp to a space, just to prevent nbsp proliferation
254 res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
255 NS_ENSURE_SUCCESS(res, nullptr);
259 // ready, aim, fire!
260 return mHTMLEditor->CreateBRImpl(aInOutParent, aInOutOffset, aSelect);
263 nsresult
264 nsWSRunObject::InsertText(const nsAString& aStringToInsert,
265 nsCOMPtr<nsINode>* aInOutParent,
266 int32_t* aInOutOffset,
267 nsIDocument* aDoc)
269 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
270 // meanwhile, the pre case is handled in WillInsertText in
271 // nsHTMLEditRules.cpp
273 // MOOSE: for now, just getting the ws logic straight. This implementation
274 // is very slow. Will need to replace edit rules impl with a more efficient
275 // text sink here that does the minimal amount of searching/replacing/copying
277 NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
279 if (aStringToInsert.IsEmpty()) {
280 return NS_OK;
283 nsAutoString theString(aStringToInsert);
285 WSFragment *beforeRun, *afterRun;
286 FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
287 FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
289 nsresult res;
291 // Some scoping for nsAutoTrackDOMPoint. This will track our insertion
292 // point while we tweak any surrounding whitespace
293 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
294 aInOutOffset);
296 // Handle any changes needed to ws run after inserted text
297 if (!afterRun || afterRun->mType & WSType::trailingWS) {
298 // Don't need to do anything. Just insert text. ws won't change.
299 } else if (afterRun->mType & WSType::leadingWS) {
300 // Delete the leading ws that is after insertion point, because it
301 // would become significant after text inserted.
302 res = DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode,
303 afterRun->mEndOffset, eOutsideUserSelectAll);
304 NS_ENSURE_SUCCESS(res, res);
305 } else if (afterRun->mType == WSType::normalWS) {
306 // Try to change an nbsp to a space, if possible, just to prevent nbsp
307 // proliferation
308 res = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
309 NS_ENSURE_SUCCESS(res, res);
312 // Handle any changes needed to ws run before inserted text
313 if (!beforeRun || beforeRun->mType & WSType::leadingWS) {
314 // Don't need to do anything. Just insert text. ws won't change.
315 } else if (beforeRun->mType & WSType::trailingWS) {
316 // Need to delete the trailing ws that is before insertion point, because
317 // it would become significant after text inserted.
318 res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
319 *aInOutParent, *aInOutOffset, eOutsideUserSelectAll);
320 NS_ENSURE_SUCCESS(res, res);
321 } else if (beforeRun->mType == WSType::normalWS) {
322 // Try to change an nbsp to a space, if possible, just to prevent nbsp
323 // proliferation
324 res = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
325 NS_ENSURE_SUCCESS(res, res);
329 // Next up, tweak head and tail of string as needed. First the head: there
330 // are a variety of circumstances that would require us to convert a leading
331 // ws char into an nbsp:
333 if (nsCRT::IsAsciiSpace(theString[0])) {
334 // We have a leading space
335 if (beforeRun) {
336 if (beforeRun->mType & WSType::leadingWS) {
337 theString.SetCharAt(nbsp, 0);
338 } else if (beforeRun->mType & WSType::normalWS) {
339 WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset);
340 if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
341 theString.SetCharAt(nbsp, 0);
344 } else if (mStartReason & WSType::block || mStartReason == WSType::br) {
345 theString.SetCharAt(nbsp, 0);
349 // Then the tail
350 uint32_t lastCharIndex = theString.Length() - 1;
352 if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
353 // We have a leading space
354 if (afterRun) {
355 if (afterRun->mType & WSType::trailingWS) {
356 theString.SetCharAt(nbsp, lastCharIndex);
357 } else if (afterRun->mType & WSType::normalWS) {
358 WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset);
359 if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
360 theString.SetCharAt(nbsp, lastCharIndex);
363 } else if (mEndReason & WSType::block) {
364 theString.SetCharAt(nbsp, lastCharIndex);
368 // Next, scan string for adjacent ws and convert to nbsp/space combos
369 // MOOSE: don't need to convert tabs here since that is done by
370 // WillInsertText() before we are called. Eventually, all that logic will be
371 // pushed down into here and made more efficient.
372 bool prevWS = false;
373 for (uint32_t i = 0; i <= lastCharIndex; i++) {
374 if (nsCRT::IsAsciiSpace(theString[i])) {
375 if (prevWS) {
376 // i - 1 can't be negative because prevWS starts out false
377 theString.SetCharAt(nbsp, i - 1);
378 } else {
379 prevWS = true;
381 } else {
382 prevWS = false;
386 // Ready, aim, fire!
387 res = mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutOffset,
388 aDoc);
389 return NS_OK;
392 nsresult
393 nsWSRunObject::DeleteWSBackward()
395 WSPoint point = GetCharBefore(mNode, mOffset);
396 NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
398 if (mPRE) {
399 // easy case, preformatted ws
400 if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
401 return DeleteChars(point.mTextNode, point.mOffset,
402 point.mTextNode, point.mOffset + 1);
406 // Caller's job to ensure that previous char is really ws. If it is normal
407 // ws, we need to delete the whole run.
408 if (nsCRT::IsAsciiSpace(point.mChar)) {
409 RefPtr<Text> startNodeText, endNodeText;
410 int32_t startOffset, endOffset;
411 GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
412 getter_AddRefs(startNodeText), &startOffset,
413 getter_AddRefs(endNodeText), &endOffset);
415 // adjust surrounding ws
416 nsCOMPtr<nsINode> startNode = startNodeText.get();
417 nsCOMPtr<nsINode> endNode = endNodeText.get();
418 nsresult res =
419 nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
420 address_of(startNode), &startOffset,
421 address_of(endNode), &endOffset);
422 NS_ENSURE_SUCCESS(res, res);
424 // finally, delete that ws
425 return DeleteChars(startNode, startOffset, endNode, endOffset);
426 } else if (point.mChar == nbsp) {
427 nsCOMPtr<nsINode> node(point.mTextNode);
428 // adjust surrounding ws
429 int32_t startOffset = point.mOffset;
430 int32_t endOffset = point.mOffset + 1;
431 nsresult res =
432 nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
433 address_of(node), &startOffset,
434 address_of(node), &endOffset);
435 NS_ENSURE_SUCCESS(res, res);
437 // finally, delete that ws
438 return DeleteChars(node, startOffset, node, endOffset);
440 return NS_OK;
443 nsresult
444 nsWSRunObject::DeleteWSForward()
446 WSPoint point = GetCharAfter(mNode, mOffset);
447 NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
449 if (mPRE) {
450 // easy case, preformatted ws
451 if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
452 return DeleteChars(point.mTextNode, point.mOffset,
453 point.mTextNode, point.mOffset + 1);
457 // Caller's job to ensure that next char is really ws. If it is normal ws,
458 // we need to delete the whole run.
459 if (nsCRT::IsAsciiSpace(point.mChar)) {
460 RefPtr<Text> startNodeText, endNodeText;
461 int32_t startOffset, endOffset;
462 GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
463 getter_AddRefs(startNodeText), &startOffset,
464 getter_AddRefs(endNodeText), &endOffset);
466 // Adjust surrounding ws
467 nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
468 nsresult res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
469 address_of(startNode), &startOffset, address_of(endNode), &endOffset);
470 NS_ENSURE_SUCCESS(res, res);
472 // Finally, delete that ws
473 return DeleteChars(startNode, startOffset, endNode, endOffset);
474 } else if (point.mChar == nbsp) {
475 nsCOMPtr<nsINode> node(point.mTextNode);
476 // Adjust surrounding ws
477 int32_t startOffset = point.mOffset;
478 int32_t endOffset = point.mOffset+1;
479 nsresult res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
480 address_of(node), &startOffset, address_of(node), &endOffset);
481 NS_ENSURE_SUCCESS(res, res);
483 // Finally, delete that ws
484 return DeleteChars(node, startOffset, node, endOffset);
486 return NS_OK;
489 void
490 nsWSRunObject::PriorVisibleNode(nsINode* aNode,
491 int32_t aOffset,
492 nsCOMPtr<nsINode>* outVisNode,
493 int32_t* outVisOffset,
494 WSType* outType)
496 // Find first visible thing before the point. Position
497 // outVisNode/outVisOffset just _after_ that thing. If we don't find
498 // anything return start of ws.
499 MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
501 WSFragment* run;
502 FindRun(aNode, aOffset, &run, false);
504 // Is there a visible run there or earlier?
505 for (; run; run = run->mLeft) {
506 if (run->mType == WSType::normalWS) {
507 WSPoint point = GetCharBefore(aNode, aOffset);
508 if (point.mTextNode) {
509 *outVisNode = point.mTextNode;
510 *outVisOffset = point.mOffset + 1;
511 if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
512 *outType = WSType::normalWS;
513 } else if (!point.mChar) {
514 // MOOSE: not possible?
515 *outType = WSType::none;
516 } else {
517 *outType = WSType::text;
519 return;
521 // If no text node, keep looking. We should eventually fall out of loop
525 // If we get here, then nothing in ws data to find. Return start reason.
526 *outVisNode = mStartReasonNode;
527 // This really isn't meaningful if mStartReasonNode != mStartNode
528 *outVisOffset = mStartOffset;
529 *outType = mStartReason;
533 void
534 nsWSRunObject::NextVisibleNode(nsINode* aNode,
535 int32_t aOffset,
536 nsCOMPtr<nsINode>* outVisNode,
537 int32_t* outVisOffset,
538 WSType* outType)
540 // Find first visible thing after the point. Position
541 // outVisNode/outVisOffset just _before_ that thing. If we don't find
542 // anything return end of ws.
543 MOZ_ASSERT(aNode && outVisNode && outVisOffset && outType);
545 WSFragment* run;
546 FindRun(aNode, aOffset, &run, true);
548 // Is there a visible run there or later?
549 for (; run; run = run->mRight) {
550 if (run->mType == WSType::normalWS) {
551 WSPoint point = GetCharAfter(aNode, aOffset);
552 if (point.mTextNode) {
553 *outVisNode = point.mTextNode;
554 *outVisOffset = point.mOffset;
555 if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
556 *outType = WSType::normalWS;
557 } else if (!point.mChar) {
558 // MOOSE: not possible?
559 *outType = WSType::none;
560 } else {
561 *outType = WSType::text;
563 return;
565 // If no text node, keep looking. We should eventually fall out of loop
569 // If we get here, then nothing in ws data to find. Return end reason
570 *outVisNode = mEndReasonNode;
571 // This really isn't meaningful if mEndReasonNode != mEndNode
572 *outVisOffset = mEndOffset;
573 *outType = mEndReason;
576 nsresult
577 nsWSRunObject::AdjustWhitespace()
579 // this routine examines a run of ws and tries to get rid of some unneeded nbsp's,
580 // replacing them with regualr ascii space if possible. Keeping things simple
581 // for now and just trying to fix up the trailing ws in the run.
582 if (!mLastNBSPNode) {
583 // nothing to do!
584 return NS_OK;
586 nsresult res = NS_OK;
587 WSFragment *curRun = mStartRun;
588 while (curRun)
590 // look for normal ws run
591 if (curRun->mType == WSType::normalWS) {
592 res = CheckTrailingNBSPOfRun(curRun);
593 break;
595 curRun = curRun->mRight;
597 return res;
601 //--------------------------------------------------------------------------------------------
602 // protected methods
603 //--------------------------------------------------------------------------------------------
605 already_AddRefed<nsINode>
606 nsWSRunObject::GetWSBoundingParent()
608 NS_ENSURE_TRUE(mNode, nullptr);
609 OwningNonNull<nsINode> wsBoundingParent = *mNode;
610 while (!IsBlockNode(wsBoundingParent)) {
611 nsCOMPtr<nsINode> parent = wsBoundingParent->GetParentNode();
612 if (!parent || !mHTMLEditor->IsEditable(parent)) {
613 break;
615 wsBoundingParent = parent;
617 return wsBoundingParent.forget();
620 nsresult
621 nsWSRunObject::GetWSNodes()
623 // collect up an array of nodes that are contiguous with the insertion point
624 // and which contain only whitespace. Stop if you reach non-ws text or a new
625 // block boundary.
626 ::DOMPoint start(mNode, mOffset), end(mNode, mOffset);
627 nsCOMPtr<nsINode> wsBoundingParent = GetWSBoundingParent();
629 // first look backwards to find preceding ws nodes
630 if (RefPtr<Text> textNode = mNode->GetAsText()) {
631 const nsTextFragment* textFrag = textNode->GetText();
633 mNodeArray.InsertElementAt(0, textNode);
634 if (mOffset) {
635 for (int32_t pos = mOffset - 1; pos >= 0; pos--) {
636 // sanity bounds check the char position. bug 136165
637 if (uint32_t(pos) >= textFrag->GetLength()) {
638 NS_NOTREACHED("looking beyond end of text fragment");
639 continue;
641 char16_t theChar = textFrag->CharAt(pos);
642 if (!nsCRT::IsAsciiSpace(theChar)) {
643 if (theChar != nbsp) {
644 mStartNode = textNode;
645 mStartOffset = pos + 1;
646 mStartReason = WSType::text;
647 mStartReasonNode = textNode;
648 break;
650 // as we look backwards update our earliest found nbsp
651 mFirstNBSPNode = textNode;
652 mFirstNBSPOffset = pos;
653 // also keep track of latest nbsp so far
654 if (!mLastNBSPNode) {
655 mLastNBSPNode = textNode;
656 mLastNBSPOffset = pos;
659 start.node = textNode;
660 start.offset = pos;
665 while (!mStartNode) {
666 // we haven't found the start of ws yet. Keep looking
667 nsCOMPtr<nsIContent> priorNode = GetPreviousWSNode(start, wsBoundingParent);
668 if (priorNode) {
669 if (IsBlockNode(priorNode)) {
670 mStartNode = start.node;
671 mStartOffset = start.offset;
672 mStartReason = WSType::otherBlock;
673 mStartReasonNode = priorNode;
674 } else if (RefPtr<Text> textNode = priorNode->GetAsText()) {
675 mNodeArray.InsertElementAt(0, textNode);
676 const nsTextFragment *textFrag;
677 if (!textNode || !(textFrag = textNode->GetText())) {
678 return NS_ERROR_NULL_POINTER;
680 uint32_t len = textNode->TextLength();
682 if (len < 1) {
683 // Zero length text node. Set start point to it
684 // so we can get past it!
685 start.SetPoint(priorNode, 0);
686 } else {
687 for (int32_t pos = len - 1; pos >= 0; pos--) {
688 // sanity bounds check the char position. bug 136165
689 if (uint32_t(pos) >= textFrag->GetLength()) {
690 NS_NOTREACHED("looking beyond end of text fragment");
691 continue;
693 char16_t theChar = textFrag->CharAt(pos);
694 if (!nsCRT::IsAsciiSpace(theChar)) {
695 if (theChar != nbsp) {
696 mStartNode = textNode;
697 mStartOffset = pos + 1;
698 mStartReason = WSType::text;
699 mStartReasonNode = textNode;
700 break;
702 // as we look backwards update our earliest found nbsp
703 mFirstNBSPNode = textNode;
704 mFirstNBSPOffset = pos;
705 // also keep track of latest nbsp so far
706 if (!mLastNBSPNode) {
707 mLastNBSPNode = textNode;
708 mLastNBSPOffset = pos;
711 start.SetPoint(textNode, pos);
714 } else {
715 // it's a break or a special node, like <img>, that is not a block and not
716 // a break but still serves as a terminator to ws runs.
717 mStartNode = start.node;
718 mStartOffset = start.offset;
719 if (nsTextEditUtils::IsBreak(priorNode)) {
720 mStartReason = WSType::br;
721 } else {
722 mStartReason = WSType::special;
724 mStartReasonNode = priorNode;
726 } else {
727 // no prior node means we exhausted wsBoundingParent
728 mStartNode = start.node;
729 mStartOffset = start.offset;
730 mStartReason = WSType::thisBlock;
731 mStartReasonNode = wsBoundingParent;
735 // then look ahead to find following ws nodes
736 if (RefPtr<Text> textNode = mNode->GetAsText()) {
737 // don't need to put it on list. it already is from code above
738 const nsTextFragment *textFrag = textNode->GetText();
740 uint32_t len = textNode->TextLength();
741 if (uint16_t(mOffset)<len) {
742 for (uint32_t pos = mOffset; pos < len; pos++) {
743 // sanity bounds check the char position. bug 136165
744 if (pos >= textFrag->GetLength()) {
745 NS_NOTREACHED("looking beyond end of text fragment");
746 continue;
748 char16_t theChar = textFrag->CharAt(pos);
749 if (!nsCRT::IsAsciiSpace(theChar)) {
750 if (theChar != nbsp) {
751 mEndNode = textNode;
752 mEndOffset = pos;
753 mEndReason = WSType::text;
754 mEndReasonNode = textNode;
755 break;
757 // as we look forwards update our latest found nbsp
758 mLastNBSPNode = textNode;
759 mLastNBSPOffset = pos;
760 // also keep track of earliest nbsp so far
761 if (!mFirstNBSPNode) {
762 mFirstNBSPNode = textNode;
763 mFirstNBSPOffset = pos;
766 end.SetPoint(textNode, pos + 1);
771 while (!mEndNode) {
772 // we haven't found the end of ws yet. Keep looking
773 nsCOMPtr<nsIContent> nextNode = GetNextWSNode(end, wsBoundingParent);
774 if (nextNode) {
775 if (IsBlockNode(nextNode)) {
776 // we encountered a new block. therefore no more ws.
777 mEndNode = end.node;
778 mEndOffset = end.offset;
779 mEndReason = WSType::otherBlock;
780 mEndReasonNode = nextNode;
781 } else if (RefPtr<Text> textNode = nextNode->GetAsText()) {
782 mNodeArray.AppendElement(textNode);
783 const nsTextFragment *textFrag;
784 if (!textNode || !(textFrag = textNode->GetText())) {
785 return NS_ERROR_NULL_POINTER;
787 uint32_t len = textNode->TextLength();
789 if (len < 1) {
790 // Zero length text node. Set end point to it
791 // so we can get past it!
792 end.SetPoint(textNode, 0);
793 } else {
794 for (uint32_t pos = 0; pos < len; pos++) {
795 // sanity bounds check the char position. bug 136165
796 if (pos >= textFrag->GetLength()) {
797 NS_NOTREACHED("looking beyond end of text fragment");
798 continue;
800 char16_t theChar = textFrag->CharAt(pos);
801 if (!nsCRT::IsAsciiSpace(theChar)) {
802 if (theChar != nbsp) {
803 mEndNode = textNode;
804 mEndOffset = pos;
805 mEndReason = WSType::text;
806 mEndReasonNode = textNode;
807 break;
809 // as we look forwards update our latest found nbsp
810 mLastNBSPNode = textNode;
811 mLastNBSPOffset = pos;
812 // also keep track of earliest nbsp so far
813 if (!mFirstNBSPNode) {
814 mFirstNBSPNode = textNode;
815 mFirstNBSPOffset = pos;
818 end.SetPoint(textNode, pos + 1);
821 } else {
822 // we encountered a break or a special node, like <img>,
823 // that is not a block and not a break but still
824 // serves as a terminator to ws runs.
825 mEndNode = end.node;
826 mEndOffset = end.offset;
827 if (nsTextEditUtils::IsBreak(nextNode)) {
828 mEndReason = WSType::br;
829 } else {
830 mEndReason = WSType::special;
832 mEndReasonNode = nextNode;
834 } else {
835 // no next node means we exhausted wsBoundingParent
836 mEndNode = end.node;
837 mEndOffset = end.offset;
838 mEndReason = WSType::thisBlock;
839 mEndReasonNode = wsBoundingParent;
843 return NS_OK;
846 void
847 nsWSRunObject::GetRuns()
849 ClearRuns();
851 // handle some easy cases first
852 mHTMLEditor->IsPreformatted(GetAsDOMNode(mNode), &mPRE);
853 // if it's preformatedd, or if we are surrounded by text or special, it's all one
854 // big normal ws run
855 if (mPRE ||
856 ((mStartReason == WSType::text || mStartReason == WSType::special) &&
857 (mEndReason == WSType::text || mEndReason == WSType::special ||
858 mEndReason == WSType::br))) {
859 MakeSingleWSRun(WSType::normalWS);
860 return;
863 // if we are before or after a block (or after a break), and there are no nbsp's,
864 // then it's all non-rendering ws.
865 if (!mFirstNBSPNode && !mLastNBSPNode &&
866 ((mStartReason & WSType::block) || mStartReason == WSType::br ||
867 (mEndReason & WSType::block))) {
868 WSType wstype;
869 if ((mStartReason & WSType::block) || mStartReason == WSType::br) {
870 wstype = WSType::leadingWS;
872 if (mEndReason & WSType::block) {
873 wstype |= WSType::trailingWS;
875 MakeSingleWSRun(wstype);
876 return;
879 // otherwise a little trickier. shucks.
880 mStartRun = new WSFragment();
881 mStartRun->mStartNode = mStartNode;
882 mStartRun->mStartOffset = mStartOffset;
884 if (mStartReason & WSType::block || mStartReason == WSType::br) {
885 // set up mStartRun
886 mStartRun->mType = WSType::leadingWS;
887 mStartRun->mEndNode = mFirstNBSPNode;
888 mStartRun->mEndOffset = mFirstNBSPOffset;
889 mStartRun->mLeftType = mStartReason;
890 mStartRun->mRightType = WSType::normalWS;
892 // set up next run
893 WSFragment *normalRun = new WSFragment();
894 mStartRun->mRight = normalRun;
895 normalRun->mType = WSType::normalWS;
896 normalRun->mStartNode = mFirstNBSPNode;
897 normalRun->mStartOffset = mFirstNBSPOffset;
898 normalRun->mLeftType = WSType::leadingWS;
899 normalRun->mLeft = mStartRun;
900 if (mEndReason != WSType::block) {
901 // then no trailing ws. this normal run ends the overall ws run.
902 normalRun->mRightType = mEndReason;
903 normalRun->mEndNode = mEndNode;
904 normalRun->mEndOffset = mEndOffset;
905 mEndRun = normalRun;
907 else
909 // we might have trailing ws.
910 // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
911 // will point to it, even though in general start/end points not
912 // guaranteed to be in text nodes.
913 if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
915 // normal ws runs right up to adjacent block (nbsp next to block)
916 normalRun->mRightType = mEndReason;
917 normalRun->mEndNode = mEndNode;
918 normalRun->mEndOffset = mEndOffset;
919 mEndRun = normalRun;
921 else
923 normalRun->mEndNode = mLastNBSPNode;
924 normalRun->mEndOffset = mLastNBSPOffset+1;
925 normalRun->mRightType = WSType::trailingWS;
927 // set up next run
928 WSFragment *lastRun = new WSFragment();
929 lastRun->mType = WSType::trailingWS;
930 lastRun->mStartNode = mLastNBSPNode;
931 lastRun->mStartOffset = mLastNBSPOffset+1;
932 lastRun->mEndNode = mEndNode;
933 lastRun->mEndOffset = mEndOffset;
934 lastRun->mLeftType = WSType::normalWS;
935 lastRun->mLeft = normalRun;
936 lastRun->mRightType = mEndReason;
937 mEndRun = lastRun;
938 normalRun->mRight = lastRun;
941 } else {
942 // mStartReason is not WSType::block or WSType::br; set up mStartRun
943 mStartRun->mType = WSType::normalWS;
944 mStartRun->mEndNode = mLastNBSPNode;
945 mStartRun->mEndOffset = mLastNBSPOffset+1;
946 mStartRun->mLeftType = mStartReason;
948 // we might have trailing ws.
949 // it so happens that *if* there is an nbsp at end, {mEndNode,mEndOffset-1}
950 // will point to it, even though in general start/end points not
951 // guaranteed to be in text nodes.
952 if ((mLastNBSPNode == mEndNode) && (mLastNBSPOffset == (mEndOffset-1)))
954 mStartRun->mRightType = mEndReason;
955 mStartRun->mEndNode = mEndNode;
956 mStartRun->mEndOffset = mEndOffset;
957 mEndRun = mStartRun;
959 else
961 // set up next run
962 WSFragment *lastRun = new WSFragment();
963 lastRun->mType = WSType::trailingWS;
964 lastRun->mStartNode = mLastNBSPNode;
965 lastRun->mStartOffset = mLastNBSPOffset+1;
966 lastRun->mLeftType = WSType::normalWS;
967 lastRun->mLeft = mStartRun;
968 lastRun->mRightType = mEndReason;
969 mEndRun = lastRun;
970 mStartRun->mRight = lastRun;
971 mStartRun->mRightType = WSType::trailingWS;
976 void
977 nsWSRunObject::ClearRuns()
979 WSFragment *tmp, *run;
980 run = mStartRun;
981 while (run)
983 tmp = run->mRight;
984 delete run;
985 run = tmp;
987 mStartRun = 0;
988 mEndRun = 0;
991 void
992 nsWSRunObject::MakeSingleWSRun(WSType aType)
994 mStartRun = new WSFragment();
996 mStartRun->mStartNode = mStartNode;
997 mStartRun->mStartOffset = mStartOffset;
998 mStartRun->mType = aType;
999 mStartRun->mEndNode = mEndNode;
1000 mStartRun->mEndOffset = mEndOffset;
1001 mStartRun->mLeftType = mStartReason;
1002 mStartRun->mRightType = mEndReason;
1004 mEndRun = mStartRun;
1007 nsIContent*
1008 nsWSRunObject::GetPreviousWSNodeInner(nsINode* aStartNode,
1009 nsINode* aBlockParent)
1011 // Can't really recycle various getnext/prior routines because we have
1012 // special needs here. Need to step into inline containers but not block
1013 // containers.
1014 MOZ_ASSERT(aStartNode && aBlockParent);
1016 nsCOMPtr<nsIContent> priorNode = aStartNode->GetPreviousSibling();
1017 OwningNonNull<nsINode> curNode = *aStartNode;
1018 while (!priorNode) {
1019 // We have exhausted nodes in parent of aStartNode.
1020 nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
1021 NS_ENSURE_TRUE(curParent, nullptr);
1022 if (curParent == aBlockParent) {
1023 // We have exhausted nodes in the block parent. The convention here is
1024 // to return null.
1025 return nullptr;
1027 // We have a parent: look for previous sibling
1028 priorNode = curParent->GetPreviousSibling();
1029 curNode = curParent;
1031 // We have a prior node. If it's a block, return it.
1032 if (IsBlockNode(priorNode)) {
1033 return priorNode;
1035 if (mHTMLEditor->IsContainer(priorNode)) {
1036 // Else if it's a container, get deep rightmost child
1037 nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
1038 if (child) {
1039 return child;
1042 // Else return the node itself
1043 return priorNode;
1046 nsIContent*
1047 nsWSRunObject::GetPreviousWSNode(::DOMPoint aPoint,
1048 nsINode* aBlockParent)
1050 // Can't really recycle various getnext/prior routines because we
1051 // have special needs here. Need to step into inline containers but
1052 // not block containers.
1053 MOZ_ASSERT(aPoint.node && aBlockParent);
1055 if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
1056 return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
1058 if (!mHTMLEditor->IsContainer(aPoint.node)) {
1059 return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
1062 if (!aPoint.offset) {
1063 if (aPoint.node == aBlockParent) {
1064 // We are at start of the block.
1065 return nullptr;
1068 // We are at start of non-block container
1069 return GetPreviousWSNodeInner(aPoint.node, aBlockParent);
1072 nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
1073 NS_ENSURE_TRUE(startContent, nullptr);
1074 nsCOMPtr<nsIContent> priorNode = startContent->GetChildAt(aPoint.offset - 1);
1075 NS_ENSURE_TRUE(priorNode, nullptr);
1076 // We have a prior node. If it's a block, return it.
1077 if (IsBlockNode(priorNode)) {
1078 return priorNode;
1080 if (mHTMLEditor->IsContainer(priorNode)) {
1081 // Else if it's a container, get deep rightmost child
1082 nsCOMPtr<nsIContent> child = mHTMLEditor->GetRightmostChild(priorNode);
1083 if (child) {
1084 return child;
1087 // Else return the node itself
1088 return priorNode;
1091 nsIContent*
1092 nsWSRunObject::GetNextWSNodeInner(nsINode* aStartNode,
1093 nsINode* aBlockParent)
1095 // Can't really recycle various getnext/prior routines because we have
1096 // special needs here. Need to step into inline containers but not block
1097 // containers.
1098 MOZ_ASSERT(aStartNode && aBlockParent);
1100 nsCOMPtr<nsIContent> nextNode = aStartNode->GetNextSibling();
1101 nsCOMPtr<nsINode> curNode = aStartNode;
1102 while (!nextNode) {
1103 // We have exhausted nodes in parent of aStartNode.
1104 nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
1105 NS_ENSURE_TRUE(curParent, nullptr);
1106 if (curParent == aBlockParent) {
1107 // We have exhausted nodes in the block parent. The convention here is
1108 // to return null.
1109 return nullptr;
1111 // We have a parent: look for next sibling
1112 nextNode = curParent->GetNextSibling();
1113 curNode = curParent;
1115 // We have a next node. If it's a block, return it.
1116 if (IsBlockNode(nextNode)) {
1117 return nextNode;
1119 if (mHTMLEditor->IsContainer(nextNode)) {
1120 // Else if it's a container, get deep leftmost child
1121 nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
1122 if (child) {
1123 return child;
1126 // Else return the node itself
1127 return nextNode;
1130 nsIContent*
1131 nsWSRunObject::GetNextWSNode(::DOMPoint aPoint, nsINode* aBlockParent)
1133 // Can't really recycle various getnext/prior routines because we have
1134 // special needs here. Need to step into inline containers but not block
1135 // containers.
1136 MOZ_ASSERT(aPoint.node && aBlockParent);
1138 if (aPoint.node->NodeType() == nsIDOMNode::TEXT_NODE) {
1139 return GetNextWSNodeInner(aPoint.node, aBlockParent);
1141 if (!mHTMLEditor->IsContainer(aPoint.node)) {
1142 return GetNextWSNodeInner(aPoint.node, aBlockParent);
1145 nsCOMPtr<nsIContent> startContent = do_QueryInterface(aPoint.node);
1146 NS_ENSURE_TRUE(startContent, nullptr);
1148 nsCOMPtr<nsIContent> nextNode = startContent->GetChildAt(aPoint.offset);
1149 if (!nextNode) {
1150 if (aPoint.node == aBlockParent) {
1151 // We are at end of the block.
1152 return nullptr;
1155 // We are at end of non-block container
1156 return GetNextWSNodeInner(aPoint.node, aBlockParent);
1159 // We have a next node. If it's a block, return it.
1160 if (IsBlockNode(nextNode)) {
1161 return nextNode;
1163 if (mHTMLEditor->IsContainer(nextNode)) {
1164 // else if it's a container, get deep leftmost child
1165 nsCOMPtr<nsIContent> child = mHTMLEditor->GetLeftmostChild(nextNode);
1166 if (child) {
1167 return child;
1170 // Else return the node itself
1171 return nextNode;
1174 nsresult
1175 nsWSRunObject::PrepareToDeleteRangePriv(nsWSRunObject* aEndObject)
1177 // this routine adjust whitespace before *this* and after aEndObject
1178 // in preperation for the two areas to become adjacent after the
1179 // intervening content is deleted. It's overly agressive right
1180 // now. There might be a block boundary remaining between them after
1181 // the deletion, in which case these adjstments are unneeded (though
1182 // I don't think they can ever be harmful?)
1184 NS_ENSURE_TRUE(aEndObject, NS_ERROR_NULL_POINTER);
1185 nsresult res = NS_OK;
1187 // get the runs before and after selection
1188 WSFragment *beforeRun, *afterRun;
1189 FindRun(mNode, mOffset, &beforeRun, false);
1190 aEndObject->FindRun(aEndObject->mNode, aEndObject->mOffset, &afterRun, true);
1192 // trim after run of any leading ws
1193 if (afterRun && (afterRun->mType & WSType::leadingWS)) {
1194 res = aEndObject->DeleteChars(aEndObject->mNode, aEndObject->mOffset,
1195 afterRun->mEndNode, afterRun->mEndOffset,
1196 eOutsideUserSelectAll);
1197 NS_ENSURE_SUCCESS(res, res);
1199 // adjust normal ws in afterRun if needed
1200 if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
1201 if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
1202 (!beforeRun && ((mStartReason & WSType::block) ||
1203 mStartReason == WSType::br))) {
1204 // make sure leading char of following ws is an nbsp, so that it will show up
1205 WSPoint point = aEndObject->GetCharAfter(aEndObject->mNode,
1206 aEndObject->mOffset);
1207 if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
1209 res = aEndObject->ConvertToNBSP(point, eOutsideUserSelectAll);
1210 NS_ENSURE_SUCCESS(res, res);
1214 // trim before run of any trailing ws
1215 if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
1216 res = DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
1217 mNode, mOffset, eOutsideUserSelectAll);
1218 NS_ENSURE_SUCCESS(res, res);
1219 } else if (beforeRun && beforeRun->mType == WSType::normalWS && !mPRE) {
1220 if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
1221 (afterRun && afterRun->mType == WSType::normalWS) ||
1222 (!afterRun && (aEndObject->mEndReason & WSType::block))) {
1223 // make sure trailing char of starting ws is an nbsp, so that it will show up
1224 WSPoint point = GetCharBefore(mNode, mOffset);
1225 if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
1227 RefPtr<Text> wsStartNode, wsEndNode;
1228 int32_t wsStartOffset, wsEndOffset;
1229 GetAsciiWSBounds(eBoth, mNode, mOffset,
1230 getter_AddRefs(wsStartNode), &wsStartOffset,
1231 getter_AddRefs(wsEndNode), &wsEndOffset);
1232 point.mTextNode = wsStartNode;
1233 point.mOffset = wsStartOffset;
1234 res = ConvertToNBSP(point, eOutsideUserSelectAll);
1235 NS_ENSURE_SUCCESS(res, res);
1239 return res;
1242 nsresult
1243 nsWSRunObject::PrepareToSplitAcrossBlocksPriv()
1245 // used to prepare ws to be split across two blocks. The main issue
1246 // here is make sure normalWS doesn't end up becoming non-significant
1247 // leading or trailing ws after the split.
1248 nsresult res = NS_OK;
1250 // get the runs before and after selection
1251 WSFragment *beforeRun, *afterRun;
1252 FindRun(mNode, mOffset, &beforeRun, false);
1253 FindRun(mNode, mOffset, &afterRun, true);
1255 // adjust normal ws in afterRun if needed
1256 if (afterRun && afterRun->mType == WSType::normalWS) {
1257 // make sure leading char of following ws is an nbsp, so that it will show up
1258 WSPoint point = GetCharAfter(mNode, mOffset);
1259 if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
1261 res = ConvertToNBSP(point);
1262 NS_ENSURE_SUCCESS(res, res);
1266 // adjust normal ws in beforeRun if needed
1267 if (beforeRun && beforeRun->mType == WSType::normalWS) {
1268 // make sure trailing char of starting ws is an nbsp, so that it will show up
1269 WSPoint point = GetCharBefore(mNode, mOffset);
1270 if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar))
1272 RefPtr<Text> wsStartNode, wsEndNode;
1273 int32_t wsStartOffset, wsEndOffset;
1274 GetAsciiWSBounds(eBoth, mNode, mOffset,
1275 getter_AddRefs(wsStartNode), &wsStartOffset,
1276 getter_AddRefs(wsEndNode), &wsEndOffset);
1277 point.mTextNode = wsStartNode;
1278 point.mOffset = wsStartOffset;
1279 res = ConvertToNBSP(point);
1280 NS_ENSURE_SUCCESS(res, res);
1283 return res;
1286 nsresult
1287 nsWSRunObject::DeleteChars(nsINode* aStartNode, int32_t aStartOffset,
1288 nsINode* aEndNode, int32_t aEndOffset,
1289 AreaRestriction aAR)
1291 // MOOSE: this routine needs to be modified to preserve the integrity of the
1292 // wsFragment info.
1293 NS_ENSURE_TRUE(aStartNode && aEndNode, NS_ERROR_NULL_POINTER);
1295 if (aAR == eOutsideUserSelectAll) {
1296 nsCOMPtr<nsIDOMNode> san =
1297 mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aStartNode));
1298 if (san) {
1299 return NS_OK;
1302 if (aStartNode != aEndNode) {
1303 san = mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aEndNode));
1304 if (san) {
1305 return NS_OK;
1310 if (aStartNode == aEndNode && aStartOffset == aEndOffset) {
1311 // Nothing to delete
1312 return NS_OK;
1315 int32_t idx = mNodeArray.IndexOf(aStartNode);
1316 if (idx == -1) {
1317 // If our strarting point wasn't one of our ws text nodes, then just go
1318 // through them from the beginning.
1319 idx = 0;
1322 if (aStartNode == aEndNode && aStartNode->GetAsText()) {
1323 return mHTMLEditor->DeleteText(*aStartNode->GetAsText(),
1324 static_cast<uint32_t>(aStartOffset),
1325 static_cast<uint32_t>(aEndOffset - aStartOffset));
1328 nsresult res;
1329 RefPtr<nsRange> range;
1330 int32_t count = mNodeArray.Length();
1331 for (; idx < count; idx++) {
1332 RefPtr<Text> node = mNodeArray[idx];
1333 if (!node) {
1334 // We ran out of ws nodes; must have been deleting to end
1335 return NS_OK;
1337 if (node == aStartNode) {
1338 uint32_t len = node->Length();
1339 if (uint32_t(aStartOffset) < len) {
1340 res = mHTMLEditor->DeleteText(*node,
1341 AssertedCast<uint32_t>(aStartOffset),
1342 len - aStartOffset);
1343 NS_ENSURE_SUCCESS(res, res);
1345 } else if (node == aEndNode) {
1346 if (aEndOffset) {
1347 res = mHTMLEditor->DeleteText(*node, 0,
1348 AssertedCast<uint32_t>(aEndOffset));
1349 NS_ENSURE_SUCCESS(res, res);
1351 break;
1352 } else {
1353 if (!range) {
1354 range = new nsRange(aStartNode);
1355 res = range->Set(aStartNode, aStartOffset, aEndNode, aEndOffset);
1356 NS_ENSURE_SUCCESS(res, res);
1358 bool nodeBefore, nodeAfter;
1359 res = nsRange::CompareNodeToRange(node, range, &nodeBefore, &nodeAfter);
1360 NS_ENSURE_SUCCESS(res, res);
1361 if (nodeAfter) {
1362 break;
1364 if (!nodeBefore) {
1365 res = mHTMLEditor->DeleteNode(node);
1366 NS_ENSURE_SUCCESS(res, res);
1367 mNodeArray.RemoveElement(node);
1368 --count;
1369 --idx;
1373 return NS_OK;
1376 nsWSRunObject::WSPoint
1377 nsWSRunObject::GetCharAfter(nsINode* aNode, int32_t aOffset)
1379 MOZ_ASSERT(aNode);
1381 int32_t idx = mNodeArray.IndexOf(aNode);
1382 if (idx == -1) {
1383 // Use range comparisons to get right ws node
1384 return GetWSPointAfter(aNode, aOffset);
1385 } else {
1386 // Use WSPoint version of GetCharAfter()
1387 return GetCharAfter(WSPoint(mNodeArray[idx], aOffset, 0));
1391 nsWSRunObject::WSPoint
1392 nsWSRunObject::GetCharBefore(nsINode* aNode, int32_t aOffset)
1394 MOZ_ASSERT(aNode);
1396 int32_t idx = mNodeArray.IndexOf(aNode);
1397 if (idx == -1) {
1398 // Use range comparisons to get right ws node
1399 return GetWSPointBefore(aNode, aOffset);
1400 } else {
1401 // Use WSPoint version of GetCharBefore()
1402 return GetCharBefore(WSPoint(mNodeArray[idx], aOffset, 0));
1406 nsWSRunObject::WSPoint
1407 nsWSRunObject::GetCharAfter(const WSPoint &aPoint)
1409 MOZ_ASSERT(aPoint.mTextNode);
1411 WSPoint outPoint;
1412 outPoint.mTextNode = nullptr;
1413 outPoint.mOffset = 0;
1414 outPoint.mChar = 0;
1416 int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
1417 if (idx == -1) {
1418 // Can't find point, but it's not an error
1419 return outPoint;
1421 int32_t numNodes = mNodeArray.Length();
1423 if (uint16_t(aPoint.mOffset) < aPoint.mTextNode->TextLength()) {
1424 outPoint = aPoint;
1425 outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset);
1426 return outPoint;
1427 } else if (idx + 1 < numNodes) {
1428 outPoint.mTextNode = mNodeArray[idx + 1];
1429 MOZ_ASSERT(outPoint.mTextNode);
1430 outPoint.mOffset = 0;
1431 outPoint.mChar = GetCharAt(outPoint.mTextNode, 0);
1433 return outPoint;
1436 nsWSRunObject::WSPoint
1437 nsWSRunObject::GetCharBefore(const WSPoint &aPoint)
1439 MOZ_ASSERT(aPoint.mTextNode);
1441 WSPoint outPoint;
1442 outPoint.mTextNode = nullptr;
1443 outPoint.mOffset = 0;
1444 outPoint.mChar = 0;
1446 int32_t idx = mNodeArray.IndexOf(aPoint.mTextNode);
1447 if (idx == -1) {
1448 // Can't find point, but it's not an error
1449 return outPoint;
1452 if (aPoint.mOffset != 0) {
1453 outPoint = aPoint;
1454 outPoint.mOffset--;
1455 outPoint.mChar = GetCharAt(aPoint.mTextNode, aPoint.mOffset - 1);
1456 return outPoint;
1457 } else if (idx) {
1458 outPoint.mTextNode = mNodeArray[idx - 1];
1460 uint32_t len = outPoint.mTextNode->TextLength();
1461 if (len) {
1462 outPoint.mOffset = len - 1;
1463 outPoint.mChar = GetCharAt(outPoint.mTextNode, len - 1);
1466 return outPoint;
1469 nsresult
1470 nsWSRunObject::ConvertToNBSP(WSPoint aPoint, AreaRestriction aAR)
1472 // MOOSE: this routine needs to be modified to preserve the integrity of the
1473 // wsFragment info.
1474 NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
1476 if (aAR == eOutsideUserSelectAll) {
1477 nsCOMPtr<nsIDOMNode> san =
1478 mHTMLEditor->FindUserSelectAllNode(GetAsDOMNode(aPoint.mTextNode));
1479 if (san) {
1480 return NS_OK;
1484 // First, insert an nbsp
1485 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
1486 nsAutoString nbspStr(nbsp);
1487 nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr,
1488 *aPoint.mTextNode, aPoint.mOffset, true);
1489 NS_ENSURE_SUCCESS(res, res);
1491 // Next, find range of ws it will replace
1492 RefPtr<Text> startNode, endNode;
1493 int32_t startOffset = 0, endOffset = 0;
1495 GetAsciiWSBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
1496 getter_AddRefs(startNode), &startOffset,
1497 getter_AddRefs(endNode), &endOffset);
1499 // Finally, delete that replaced ws, if any
1500 if (startNode) {
1501 res = DeleteChars(startNode, startOffset, endNode, endOffset);
1502 NS_ENSURE_SUCCESS(res, res);
1505 return NS_OK;
1508 void
1509 nsWSRunObject::GetAsciiWSBounds(int16_t aDir, nsINode* aNode, int32_t aOffset,
1510 Text** outStartNode, int32_t* outStartOffset,
1511 Text** outEndNode, int32_t* outEndOffset)
1513 MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
1514 outEndOffset);
1516 RefPtr<Text> startNode, endNode;
1517 int32_t startOffset = 0, endOffset = 0;
1519 if (aDir & eAfter) {
1520 WSPoint point = GetCharAfter(aNode, aOffset);
1521 if (point.mTextNode) {
1522 // We found a text node, at least
1523 startNode = endNode = point.mTextNode;
1524 startOffset = endOffset = point.mOffset;
1526 // Scan ahead to end of ASCII ws
1527 for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
1528 point = GetCharAfter(point)) {
1529 endNode = point.mTextNode;
1530 // endOffset is _after_ ws
1531 point.mOffset++;
1532 endOffset = point.mOffset;
1537 if (aDir & eBefore) {
1538 WSPoint point = GetCharBefore(aNode, aOffset);
1539 if (point.mTextNode) {
1540 // We found a text node, at least
1541 startNode = point.mTextNode;
1542 startOffset = point.mOffset + 1;
1543 if (!endNode) {
1544 endNode = startNode;
1545 endOffset = startOffset;
1548 // Scan back to start of ASCII ws
1549 for (; nsCRT::IsAsciiSpace(point.mChar) && point.mTextNode;
1550 point = GetCharBefore(point)) {
1551 startNode = point.mTextNode;
1552 startOffset = point.mOffset;
1557 startNode.forget(outStartNode);
1558 *outStartOffset = startOffset;
1559 endNode.forget(outEndNode);
1560 *outEndOffset = endOffset;
1564 * Given a dompoint, find the ws run that is before or after it, as caller
1565 * needs
1567 void
1568 nsWSRunObject::FindRun(nsINode* aNode, int32_t aOffset, WSFragment** outRun,
1569 bool after)
1571 MOZ_ASSERT(aNode && outRun);
1572 *outRun = nullptr;
1574 for (WSFragment* run = mStartRun; run; run = run->mRight) {
1575 int32_t comp = run->mStartNode ? nsContentUtils::ComparePoints(aNode,
1576 aOffset, run->mStartNode, run->mStartOffset) : -1;
1577 if (comp <= 0) {
1578 if (after) {
1579 *outRun = run;
1580 } else {
1581 // before
1582 *outRun = nullptr;
1584 return;
1586 comp = run->mEndNode ? nsContentUtils::ComparePoints(aNode, aOffset,
1587 run->mEndNode, run->mEndOffset) : -1;
1588 if (comp < 0) {
1589 *outRun = run;
1590 return;
1591 } else if (comp == 0) {
1592 if (after) {
1593 *outRun = run->mRight;
1594 } else {
1595 // before
1596 *outRun = run;
1598 return;
1600 if (!run->mRight) {
1601 if (after) {
1602 *outRun = nullptr;
1603 } else {
1604 // before
1605 *outRun = run;
1607 return;
1612 char16_t
1613 nsWSRunObject::GetCharAt(Text* aTextNode, int32_t aOffset)
1615 // return 0 if we can't get a char, for whatever reason
1616 NS_ENSURE_TRUE(aTextNode, 0);
1618 int32_t len = int32_t(aTextNode->TextLength());
1619 if (aOffset < 0 || aOffset >= len)
1620 return 0;
1622 return aTextNode->GetText()->CharAt(aOffset);
1625 nsWSRunObject::WSPoint
1626 nsWSRunObject::GetWSPointAfter(nsINode* aNode, int32_t aOffset)
1628 // Note: only to be called if aNode is not a ws node.
1630 // Binary search on wsnodes
1631 uint32_t numNodes = mNodeArray.Length();
1633 if (!numNodes) {
1634 // Do nothing if there are no nodes to search
1635 WSPoint outPoint;
1636 return outPoint;
1639 uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
1640 int16_t cmp = 0;
1641 RefPtr<Text> curNode;
1643 // Begin binary search. We do this because we need to minimize calls to
1644 // ComparePoints(), which is expensive.
1645 while (curNum != lastNum) {
1646 curNode = mNodeArray[curNum];
1647 cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
1648 if (cmp < 0) {
1649 lastNum = curNum;
1650 } else {
1651 firstNum = curNum + 1;
1653 curNum = (lastNum - firstNum)/2 + firstNum;
1654 MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
1657 // When the binary search is complete, we always know that the current node
1658 // is the same as the end node, which is always past our range. Therefore,
1659 // we've found the node immediately after the point of interest.
1660 if (curNum == mNodeArray.Length()) {
1661 // hey asked for past our range (it's after the last node). GetCharAfter
1662 // will do the work for us when we pass it the last index of the last node.
1663 RefPtr<Text> textNode(mNodeArray[curNum - 1]);
1664 WSPoint point(textNode, textNode->TextLength(), 0);
1665 return GetCharAfter(point);
1666 } else {
1667 // The char after the point is the first character of our range.
1668 RefPtr<Text> textNode(mNodeArray[curNum]);
1669 WSPoint point(textNode, 0, 0);
1670 return GetCharAfter(point);
1674 nsWSRunObject::WSPoint
1675 nsWSRunObject::GetWSPointBefore(nsINode* aNode, int32_t aOffset)
1677 // Note: only to be called if aNode is not a ws node.
1679 // Binary search on wsnodes
1680 uint32_t numNodes = mNodeArray.Length();
1682 if (!numNodes) {
1683 // Do nothing if there are no nodes to search
1684 WSPoint outPoint;
1685 return outPoint;
1688 uint32_t firstNum = 0, curNum = numNodes/2, lastNum = numNodes;
1689 int16_t cmp = 0;
1690 RefPtr<Text> curNode;
1692 // Begin binary search. We do this because we need to minimize calls to
1693 // ComparePoints(), which is expensive.
1694 while (curNum != lastNum) {
1695 curNode = mNodeArray[curNum];
1696 cmp = nsContentUtils::ComparePoints(aNode, aOffset, curNode, 0);
1697 if (cmp < 0) {
1698 lastNum = curNum;
1699 } else {
1700 firstNum = curNum + 1;
1702 curNum = (lastNum - firstNum)/2 + firstNum;
1703 MOZ_ASSERT(firstNum <= curNum && curNum <= lastNum, "Bad binary search");
1706 // When the binary search is complete, we always know that the current node
1707 // is the same as the end node, which is always past our range. Therefore,
1708 // we've found the node immediately after the point of interest.
1709 if (curNum == mNodeArray.Length()) {
1710 // Get the point before the end of the last node, we can pass the length of
1711 // the node into GetCharBefore, and it will return the last character.
1712 RefPtr<Text> textNode(mNodeArray[curNum - 1]);
1713 WSPoint point(textNode, textNode->TextLength(), 0);
1714 return GetCharBefore(point);
1715 } else {
1716 // We can just ask the current node for the point immediately before it,
1717 // it will handle moving to the previous node (if any) and returning the
1718 // appropriate character
1719 RefPtr<Text> textNode(mNodeArray[curNum]);
1720 WSPoint point(textNode, 0, 0);
1721 return GetCharBefore(point);
1725 nsresult
1726 nsWSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
1728 // Try to change an nbsp to a space, if possible, just to prevent nbsp
1729 // proliferation. Examine what is before and after the trailing nbsp, if
1730 // any.
1731 NS_ENSURE_TRUE(aRun, NS_ERROR_NULL_POINTER);
1732 nsresult res;
1733 bool leftCheck = false;
1734 bool spaceNBSP = false;
1735 bool rightCheck = false;
1737 // confirm run is normalWS
1738 if (aRun->mType != WSType::normalWS) {
1739 return NS_ERROR_FAILURE;
1742 // first check for trailing nbsp
1743 WSPoint thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
1744 if (thePoint.mTextNode && thePoint.mChar == nbsp) {
1745 // now check that what is to the left of it is compatible with replacing nbsp with space
1746 WSPoint prevPoint = GetCharBefore(thePoint);
1747 if (prevPoint.mTextNode) {
1748 if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
1749 leftCheck = true;
1750 } else {
1751 spaceNBSP = true;
1753 } else if (aRun->mLeftType == WSType::text ||
1754 aRun->mLeftType == WSType::special) {
1755 leftCheck = true;
1757 if (leftCheck || spaceNBSP) {
1758 // now check that what is to the right of it is compatible with replacing
1759 // nbsp with space
1760 if (aRun->mRightType == WSType::text ||
1761 aRun->mRightType == WSType::special ||
1762 aRun->mRightType == WSType::br) {
1763 rightCheck = true;
1765 if ((aRun->mRightType & WSType::block) &&
1766 IsBlockNode(nsCOMPtr<nsINode>(GetWSBoundingParent()))) {
1767 // We are at a block boundary. Insert a <br>. Why? Well, first note
1768 // that the br will have no visible effect since it is up against a
1769 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
1770 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
1771 // this <br> addition gets us is the ability to convert a trailing nbsp
1772 // to a space. Consider: |<body>foo. '</body>|, where ' represents
1773 // selection. User types space attempting to put 2 spaces after the
1774 // end of their sentence. We used to do this as: |<body>foo.
1775 // &nbsp</body>| This caused problems with soft wrapping: the nbsp
1776 // would wrap to the next line, which looked attrocious. If you try to
1777 // do: |<body>foo.&nbsp </body>| instead, the trailing space is
1778 // invisible because it is against a block boundary. If you do:
1779 // |<body>foo.&nbsp&nbsp</body>| then you get an even uglier soft
1780 // wrapping problem, where foo is on one line until you type the final
1781 // space, and then "foo " jumps down to the next line. Ugh. The best
1782 // way I can find out of this is to throw in a harmless <br> here,
1783 // which allows us to do: |<body>foo.&nbsp <br></body>|, which doesn't
1784 // cause foo to jump lines, doesn't cause spaces to show up at the
1785 // beginning of soft wrapped lines, and lets the user see 2 spaces when
1786 // they type 2 spaces.
1788 nsCOMPtr<Element> brNode =
1789 mHTMLEditor->CreateBR(aRun->mEndNode, aRun->mEndOffset);
1790 NS_ENSURE_TRUE(brNode, NS_ERROR_FAILURE);
1792 // Refresh thePoint, prevPoint
1793 thePoint = GetCharBefore(aRun->mEndNode, aRun->mEndOffset);
1794 prevPoint = GetCharBefore(thePoint);
1795 rightCheck = true;
1798 if (leftCheck && rightCheck) {
1799 // Now replace nbsp with space. First, insert a space
1800 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
1801 nsAutoString spaceStr(char16_t(32));
1802 res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr,
1803 *thePoint.mTextNode,
1804 thePoint.mOffset, true);
1805 NS_ENSURE_SUCCESS(res, res);
1807 // Finally, delete that nbsp
1808 res = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
1809 thePoint.mTextNode, thePoint.mOffset + 2);
1810 NS_ENSURE_SUCCESS(res, res);
1811 } else if (!mPRE && spaceNBSP && rightCheck) {
1812 // Don't mess with this preformatted for now. We have a run of ASCII
1813 // whitespace (which will render as one space) followed by an nbsp (which
1814 // is at the end of the whitespace run). Let's switch their order. This
1815 // will ensure that if someone types two spaces after a sentence, and the
1816 // editor softwraps at this point, the spaces won't be split across lines,
1817 // which looks ugly and is bad for the moose.
1819 RefPtr<Text> startNode, endNode;
1820 int32_t startOffset, endOffset;
1821 GetAsciiWSBounds(eBoth, prevPoint.mTextNode, prevPoint.mOffset + 1,
1822 getter_AddRefs(startNode), &startOffset,
1823 getter_AddRefs(endNode), &endOffset);
1825 // Delete that nbsp
1826 res = DeleteChars(thePoint.mTextNode, thePoint.mOffset,
1827 thePoint.mTextNode, thePoint.mOffset + 1);
1828 NS_ENSURE_SUCCESS(res, res);
1830 // Finally, insert that nbsp before the ASCII ws run
1831 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
1832 nsAutoString nbspStr(nbsp);
1833 res = mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *startNode,
1834 startOffset, true);
1835 NS_ENSURE_SUCCESS(res, res);
1838 return NS_OK;
1841 nsresult
1842 nsWSRunObject::CheckTrailingNBSP(WSFragment* aRun, nsINode* aNode,
1843 int32_t aOffset)
1845 // Try to change an nbsp to a space, if possible, just to prevent nbsp
1846 // proliferation. This routine is called when we are about to make this
1847 // point in the ws abut an inserted break or text, so we don't have to worry
1848 // about what is after it. What is after it now will end up after the
1849 // inserted object.
1850 NS_ENSURE_TRUE(aRun && aNode, NS_ERROR_NULL_POINTER);
1851 bool canConvert = false;
1852 WSPoint thePoint = GetCharBefore(aNode, aOffset);
1853 if (thePoint.mTextNode && thePoint.mChar == nbsp) {
1854 WSPoint prevPoint = GetCharBefore(thePoint);
1855 if (prevPoint.mTextNode) {
1856 if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
1857 canConvert = true;
1859 } else if (aRun->mLeftType == WSType::text ||
1860 aRun->mLeftType == WSType::special) {
1861 canConvert = true;
1864 if (canConvert) {
1865 // First, insert a space
1866 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
1867 nsAutoString spaceStr(char16_t(32));
1868 nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr,
1869 *thePoint.mTextNode, thePoint.mOffset, true);
1870 NS_ENSURE_SUCCESS(res, res);
1872 // Finally, delete that nbsp
1873 res = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
1874 thePoint.mTextNode, thePoint.mOffset + 2);
1875 NS_ENSURE_SUCCESS(res, res);
1877 return NS_OK;
1880 nsresult
1881 nsWSRunObject::CheckLeadingNBSP(WSFragment* aRun, nsINode* aNode,
1882 int32_t aOffset)
1884 // Try to change an nbsp to a space, if possible, just to prevent nbsp
1885 // proliferation This routine is called when we are about to make this point
1886 // in the ws abut an inserted text, so we don't have to worry about what is
1887 // before it. What is before it now will end up before the inserted text.
1888 bool canConvert = false;
1889 WSPoint thePoint = GetCharAfter(aNode, aOffset);
1890 if (thePoint.mChar == nbsp) {
1891 WSPoint tmp = thePoint;
1892 // we want to be after thePoint
1893 tmp.mOffset++;
1894 WSPoint nextPoint = GetCharAfter(tmp);
1895 if (nextPoint.mTextNode) {
1896 if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) {
1897 canConvert = true;
1899 } else if (aRun->mRightType == WSType::text ||
1900 aRun->mRightType == WSType::special ||
1901 aRun->mRightType == WSType::br) {
1902 canConvert = true;
1905 if (canConvert) {
1906 // First, insert a space
1907 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
1908 nsAutoString spaceStr(char16_t(32));
1909 nsresult res = mHTMLEditor->InsertTextIntoTextNodeImpl(spaceStr,
1910 *thePoint.mTextNode, thePoint.mOffset, true);
1911 NS_ENSURE_SUCCESS(res, res);
1913 // Finally, delete that nbsp
1914 res = DeleteChars(thePoint.mTextNode, thePoint.mOffset + 1,
1915 thePoint.mTextNode, thePoint.mOffset + 2);
1916 NS_ENSURE_SUCCESS(res, res);
1918 return NS_OK;
1922 nsresult
1923 nsWSRunObject::Scrub()
1925 WSFragment *run = mStartRun;
1926 while (run)
1928 if (run->mType & (WSType::leadingWS | WSType::trailingWS)) {
1929 nsresult res = DeleteChars(run->mStartNode, run->mStartOffset,
1930 run->mEndNode, run->mEndOffset);
1931 NS_ENSURE_SUCCESS(res, res);
1933 run = run->mRight;
1935 return NS_OK;