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