editor/libeditor/html/nsWSRunObject.cpp

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

mercurial