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"
16 #include "nsContentUtils.h"
18 #include "nsEditorUtils.h"
20 #include "nsHTMLEditor.h"
21 #include "nsIContent.h"
22 #include "nsIDOMDocument.h"
23 #include "nsIDOMNode.h"
24 #include "nsISupportsImpl.h"
26 #include "nsSelectionState.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
)
68 nsWSRunObject::nsWSRunObject(nsHTMLEditor
*aEd
, nsIDOMNode
*aNode
, int32_t aOffset
) :
69 mNode(do_QueryInterface(aNode
))
93 nsWSRunObject::~nsWSRunObject()
100 //--------------------------------------------------------------------------------------------
101 // public static methods
102 //--------------------------------------------------------------------------------------------
105 nsWSRunObject::ScrubBlockBoundary(nsHTMLEditor
* aHTMLEd
,
106 BlockBoundary aBoundary
,
110 NS_ENSURE_TRUE(aHTMLEd
&& aBlock
, NS_ERROR_NULL_POINTER
);
113 if (aBoundary
== kBlockStart
) {
115 } else if (aBoundary
== kBlockEnd
) {
116 offset
= aBlock
->Length();
118 // Else we are scrubbing an outer boundary - just before or after a block
120 NS_ENSURE_STATE(aOffset
>= 0);
124 nsWSRunObject
theWSObj(aHTMLEd
, aBlock
, offset
);
125 return theWSObj
.Scrub();
129 nsWSRunObject::PrepareToJoinBlocks(nsHTMLEditor
* aHTMLEd
,
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
);
142 nsWSRunObject::PrepareToDeleteRange(nsHTMLEditor
* aHTMLEd
,
143 nsCOMPtr
<nsINode
>* aStartNode
,
144 int32_t* aStartOffset
,
145 nsCOMPtr
<nsINode
>* aEndNode
,
148 NS_ENSURE_TRUE(aHTMLEd
&& aStartNode
&& *aStartNode
&& aStartOffset
&&
149 aEndNode
&& *aEndNode
&& aEndOffset
, NS_ERROR_NULL_POINTER
);
151 nsAutoTrackDOMPoint
trackerStart(aHTMLEd
->mRangeUpdater
, aStartNode
,
153 nsAutoTrackDOMPoint
trackerEnd(aHTMLEd
->mRangeUpdater
, aEndNode
, aEndOffset
);
155 nsWSRunObject
leftWSObj(aHTMLEd
, *aStartNode
, *aStartOffset
);
156 nsWSRunObject
rightWSObj(aHTMLEd
, *aEndNode
, *aEndOffset
);
158 return leftWSObj
.PrepareToDeleteRangePriv(&rightWSObj
);
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
);
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
,
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
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);
260 return mHTMLEditor
->CreateBRImpl(aInOutParent
, aInOutOffset
, aSelect
);
264 nsWSRunObject::InsertText(const nsAString
& aStringToInsert
,
265 nsCOMPtr
<nsINode
>* aInOutParent
,
266 int32_t* aInOutOffset
,
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()) {
283 nsAutoString
theString(aStringToInsert
);
285 WSFragment
*beforeRun
, *afterRun
;
286 FindRun(*aInOutParent
, *aInOutOffset
, &beforeRun
, false);
287 FindRun(*aInOutParent
, *aInOutOffset
, &afterRun
, true);
291 // Some scoping for nsAutoTrackDOMPoint. This will track our insertion
292 // point while we tweak any surrounding whitespace
293 nsAutoTrackDOMPoint
tracker(mHTMLEditor
->mRangeUpdater
, aInOutParent
,
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
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
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
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);
350 uint32_t lastCharIndex
= theString
.Length() - 1;
352 if (nsCRT::IsAsciiSpace(theString
[lastCharIndex
])) {
353 // We have a leading space
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.
373 for (uint32_t i
= 0; i
<= lastCharIndex
; i
++) {
374 if (nsCRT::IsAsciiSpace(theString
[i
])) {
376 // i - 1 can't be negative because prevWS starts out false
377 theString
.SetCharAt(nbsp
, i
- 1);
387 res
= mHTMLEditor
->InsertTextImpl(theString
, aInOutParent
, aInOutOffset
,
393 nsWSRunObject::DeleteWSBackward()
395 WSPoint point
= GetCharBefore(mNode
, mOffset
);
396 NS_ENSURE_TRUE(point
.mTextNode
, NS_OK
); // nothing to delete
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();
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;
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
);
444 nsWSRunObject::DeleteWSForward()
446 WSPoint point
= GetCharAfter(mNode
, mOffset
);
447 NS_ENSURE_TRUE(point
.mTextNode
, NS_OK
); // nothing to delete
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
);
490 nsWSRunObject::PriorVisibleNode(nsINode
* aNode
,
492 nsCOMPtr
<nsINode
>* outVisNode
,
493 int32_t* outVisOffset
,
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
);
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
;
517 *outType
= WSType::text
;
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
;
534 nsWSRunObject::NextVisibleNode(nsINode
* aNode
,
536 nsCOMPtr
<nsINode
>* outVisNode
,
537 int32_t* outVisOffset
,
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
);
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
;
561 *outType
= WSType::text
;
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
;
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
) {
586 nsresult res
= NS_OK
;
587 WSFragment
*curRun
= mStartRun
;
590 // look for normal ws run
591 if (curRun
->mType
== WSType::normalWS
) {
592 res
= CheckTrailingNBSPOfRun(curRun
);
595 curRun
= curRun
->mRight
;
601 //--------------------------------------------------------------------------------------------
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
)) {
615 wsBoundingParent
= parent
;
617 return wsBoundingParent
.forget();
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
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
);
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");
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
;
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
;
665 while (!mStartNode
) {
666 // we haven't found the start of ws yet. Keep looking
667 nsCOMPtr
<nsIContent
> priorNode
= GetPreviousWSNode(start
, wsBoundingParent
);
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();
683 // Zero length text node. Set start point to it
684 // so we can get past it!
685 start
.SetPoint(priorNode
, 0);
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");
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
;
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
);
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
;
722 mStartReason
= WSType::special
;
724 mStartReasonNode
= priorNode
;
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");
748 char16_t theChar
= textFrag
->CharAt(pos
);
749 if (!nsCRT::IsAsciiSpace(theChar
)) {
750 if (theChar
!= nbsp
) {
753 mEndReason
= WSType::text
;
754 mEndReasonNode
= textNode
;
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);
772 // we haven't found the end of ws yet. Keep looking
773 nsCOMPtr
<nsIContent
> nextNode
= GetNextWSNode(end
, wsBoundingParent
);
775 if (IsBlockNode(nextNode
)) {
776 // we encountered a new block. therefore no more ws.
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();
790 // Zero length text node. Set end point to it
791 // so we can get past it!
792 end
.SetPoint(textNode
, 0);
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");
800 char16_t theChar
= textFrag
->CharAt(pos
);
801 if (!nsCRT::IsAsciiSpace(theChar
)) {
802 if (theChar
!= nbsp
) {
805 mEndReason
= WSType::text
;
806 mEndReasonNode
= textNode
;
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);
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.
826 mEndOffset
= end
.offset
;
827 if (nsTextEditUtils::IsBreak(nextNode
)) {
828 mEndReason
= WSType::br
;
830 mEndReason
= WSType::special
;
832 mEndReasonNode
= nextNode
;
835 // no next node means we exhausted wsBoundingParent
837 mEndOffset
= end
.offset
;
838 mEndReason
= WSType::thisBlock
;
839 mEndReasonNode
= wsBoundingParent
;
847 nsWSRunObject::GetRuns()
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
856 ((mStartReason
== WSType::text
|| mStartReason
== WSType::special
) &&
857 (mEndReason
== WSType::text
|| mEndReason
== WSType::special
||
858 mEndReason
== WSType::br
))) {
859 MakeSingleWSRun(WSType::normalWS
);
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
))) {
869 if ((mStartReason
& WSType::block
) || mStartReason
== WSType::br
) {
870 wstype
= WSType::leadingWS
;
872 if (mEndReason
& WSType::block
) {
873 wstype
|= WSType::trailingWS
;
875 MakeSingleWSRun(wstype
);
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
) {
886 mStartRun
->mType
= WSType::leadingWS
;
887 mStartRun
->mEndNode
= mFirstNBSPNode
;
888 mStartRun
->mEndOffset
= mFirstNBSPOffset
;
889 mStartRun
->mLeftType
= mStartReason
;
890 mStartRun
->mRightType
= WSType::normalWS
;
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
;
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
;
923 normalRun
->mEndNode
= mLastNBSPNode
;
924 normalRun
->mEndOffset
= mLastNBSPOffset
+1;
925 normalRun
->mRightType
= WSType::trailingWS
;
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
;
938 normalRun
->mRight
= lastRun
;
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
;
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
;
970 mStartRun
->mRight
= lastRun
;
971 mStartRun
->mRightType
= WSType::trailingWS
;
977 nsWSRunObject::ClearRuns()
979 WSFragment
*tmp
, *run
;
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
;
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
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
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
)) {
1035 if (mHTMLEditor
->IsContainer(priorNode
)) {
1036 // Else if it's a container, get deep rightmost child
1037 nsCOMPtr
<nsIContent
> child
= mHTMLEditor
->GetRightmostChild(priorNode
);
1042 // Else return the node itself
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.
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
)) {
1080 if (mHTMLEditor
->IsContainer(priorNode
)) {
1081 // Else if it's a container, get deep rightmost child
1082 nsCOMPtr
<nsIContent
> child
= mHTMLEditor
->GetRightmostChild(priorNode
);
1087 // Else return the node itself
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
1098 MOZ_ASSERT(aStartNode
&& aBlockParent
);
1100 nsCOMPtr
<nsIContent
> nextNode
= aStartNode
->GetNextSibling();
1101 nsCOMPtr
<nsINode
> curNode
= aStartNode
;
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
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
)) {
1119 if (mHTMLEditor
->IsContainer(nextNode
)) {
1120 // Else if it's a container, get deep leftmost child
1121 nsCOMPtr
<nsIContent
> child
= mHTMLEditor
->GetLeftmostChild(nextNode
);
1126 // Else return the node itself
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
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
);
1150 if (aPoint
.node
== aBlockParent
) {
1151 // We are at end of the block.
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
)) {
1163 if (mHTMLEditor
->IsContainer(nextNode
)) {
1164 // else if it's a container, get deep leftmost child
1165 nsCOMPtr
<nsIContent
> child
= mHTMLEditor
->GetLeftmostChild(nextNode
);
1170 // Else return the node itself
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
);
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
);
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
1293 NS_ENSURE_TRUE(aStartNode
&& aEndNode
, NS_ERROR_NULL_POINTER
);
1295 if (aAR
== eOutsideUserSelectAll
) {
1296 nsCOMPtr
<nsIDOMNode
> san
=
1297 mHTMLEditor
->FindUserSelectAllNode(GetAsDOMNode(aStartNode
));
1302 if (aStartNode
!= aEndNode
) {
1303 san
= mHTMLEditor
->FindUserSelectAllNode(GetAsDOMNode(aEndNode
));
1310 if (aStartNode
== aEndNode
&& aStartOffset
== aEndOffset
) {
1311 // Nothing to delete
1315 int32_t idx
= mNodeArray
.IndexOf(aStartNode
);
1317 // If our strarting point wasn't one of our ws text nodes, then just go
1318 // through them from the beginning.
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
));
1329 RefPtr
<nsRange
> range
;
1330 int32_t count
= mNodeArray
.Length();
1331 for (; idx
< count
; idx
++) {
1332 RefPtr
<Text
> node
= mNodeArray
[idx
];
1334 // We ran out of ws nodes; must have been deleting to end
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
) {
1347 res
= mHTMLEditor
->DeleteText(*node
, 0,
1348 AssertedCast
<uint32_t>(aEndOffset
));
1349 NS_ENSURE_SUCCESS(res
, res
);
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
);
1365 res
= mHTMLEditor
->DeleteNode(node
);
1366 NS_ENSURE_SUCCESS(res
, res
);
1367 mNodeArray
.RemoveElement(node
);
1376 nsWSRunObject::WSPoint
1377 nsWSRunObject::GetCharAfter(nsINode
* aNode
, int32_t aOffset
)
1381 int32_t idx
= mNodeArray
.IndexOf(aNode
);
1383 // Use range comparisons to get right ws node
1384 return GetWSPointAfter(aNode
, aOffset
);
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
)
1396 int32_t idx
= mNodeArray
.IndexOf(aNode
);
1398 // Use range comparisons to get right ws node
1399 return GetWSPointBefore(aNode
, aOffset
);
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
);
1412 outPoint
.mTextNode
= nullptr;
1413 outPoint
.mOffset
= 0;
1416 int32_t idx
= mNodeArray
.IndexOf(aPoint
.mTextNode
);
1418 // Can't find point, but it's not an error
1421 int32_t numNodes
= mNodeArray
.Length();
1423 if (uint16_t(aPoint
.mOffset
) < aPoint
.mTextNode
->TextLength()) {
1425 outPoint
.mChar
= GetCharAt(aPoint
.mTextNode
, aPoint
.mOffset
);
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);
1436 nsWSRunObject::WSPoint
1437 nsWSRunObject::GetCharBefore(const WSPoint
&aPoint
)
1439 MOZ_ASSERT(aPoint
.mTextNode
);
1442 outPoint
.mTextNode
= nullptr;
1443 outPoint
.mOffset
= 0;
1446 int32_t idx
= mNodeArray
.IndexOf(aPoint
.mTextNode
);
1448 // Can't find point, but it's not an error
1452 if (aPoint
.mOffset
!= 0) {
1455 outPoint
.mChar
= GetCharAt(aPoint
.mTextNode
, aPoint
.mOffset
- 1);
1458 outPoint
.mTextNode
= mNodeArray
[idx
- 1];
1460 uint32_t len
= outPoint
.mTextNode
->TextLength();
1462 outPoint
.mOffset
= len
- 1;
1463 outPoint
.mChar
= GetCharAt(outPoint
.mTextNode
, len
- 1);
1470 nsWSRunObject::ConvertToNBSP(WSPoint aPoint
, AreaRestriction aAR
)
1472 // MOOSE: this routine needs to be modified to preserve the integrity of the
1474 NS_ENSURE_TRUE(aPoint
.mTextNode
, NS_ERROR_NULL_POINTER
);
1476 if (aAR
== eOutsideUserSelectAll
) {
1477 nsCOMPtr
<nsIDOMNode
> san
=
1478 mHTMLEditor
->FindUserSelectAllNode(GetAsDOMNode(aPoint
.mTextNode
));
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
1501 res
= DeleteChars(startNode
, startOffset
, endNode
, endOffset
);
1502 NS_ENSURE_SUCCESS(res
, res
);
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
&&
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
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;
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
1568 nsWSRunObject::FindRun(nsINode
* aNode
, int32_t aOffset
, WSFragment
** outRun
,
1571 MOZ_ASSERT(aNode
&& outRun
);
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;
1586 comp
= run
->mEndNode
? nsContentUtils::ComparePoints(aNode
, aOffset
,
1587 run
->mEndNode
, run
->mEndOffset
) : -1;
1591 } else if (comp
== 0) {
1593 *outRun
= run
->mRight
;
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
)
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();
1634 // Do nothing if there are no nodes to search
1639 uint32_t firstNum
= 0, curNum
= numNodes
/2, lastNum
= numNodes
;
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);
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
);
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();
1683 // Do nothing if there are no nodes to search
1688 uint32_t firstNum
= 0, curNum
= numNodes
/2, lastNum
= numNodes
;
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);
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
);
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
);
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
1731 NS_ENSURE_TRUE(aRun
, NS_ERROR_NULL_POINTER
);
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
)) {
1753 } else if (aRun
->mLeftType
== WSType::text
||
1754 aRun
->mLeftType
== WSType::special
) {
1757 if (leftCheck
|| spaceNBSP
) {
1758 // now check that what is to the right of it is compatible with replacing
1760 if (aRun
->mRightType
== WSType::text
||
1761 aRun
->mRightType
== WSType::special
||
1762 aRun
->mRightType
== WSType::br
) {
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 //  </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.  </body>| instead, the trailing space is
1778 // invisible because it is against a block boundary. If you do:
1779 // |<body>foo.  </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.  <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
);
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
);
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
,
1835 NS_ENSURE_SUCCESS(res
, res
);
1842 nsWSRunObject::CheckTrailingNBSP(WSFragment
* aRun
, nsINode
* aNode
,
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
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
)) {
1859 } else if (aRun
->mLeftType
== WSType::text
||
1860 aRun
->mLeftType
== WSType::special
) {
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
);
1881 nsWSRunObject::CheckLeadingNBSP(WSFragment
* aRun
, nsINode
* aNode
,
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
1894 WSPoint nextPoint
= GetCharAfter(tmp
);
1895 if (nextPoint
.mTextNode
) {
1896 if (!nsCRT::IsAsciiSpace(nextPoint
.mChar
)) {
1899 } else if (aRun
->mRightType
== WSType::text
||
1900 aRun
->mRightType
== WSType::special
||
1901 aRun
->mRightType
== WSType::br
) {
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
);
1923 nsWSRunObject::Scrub()
1925 WSFragment
*run
= mStartRun
;
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
);