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