1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WSRunObject.h"
8 #include "HTMLEditUtils.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Casting.h"
12 #include "mozilla/EditorDOMPoint.h"
13 #include "mozilla/HTMLEditor.h"
14 #include "mozilla/mozalloc.h"
15 #include "mozilla/OwningNonNull.h"
16 #include "mozilla/RangeUtils.h"
17 #include "mozilla/SelectionState.h"
18 #include "mozilla/StaticPrefs_dom.h" // for StaticPrefs::dom_*
19 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
20 #include "mozilla/InternalMutationEvent.h"
21 #include "mozilla/dom/AncestorIterator.h"
23 #include "nsAString.h"
25 #include "nsContentUtils.h"
28 #include "nsIContent.h"
29 #include "nsIContentInlines.h"
30 #include "nsISupportsImpl.h"
33 #include "nsTextFragment.h"
39 using ChildBlockBoundary
= HTMLEditUtils::ChildBlockBoundary
;
41 const char16_t kNBSP
= 160;
43 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
44 const EditorDOMPoint
& aPoint
) const;
45 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
46 const EditorRawDOMPoint
& aPoint
) const;
47 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
48 const EditorDOMPoint
& aPoint
) const;
49 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
50 const EditorRawDOMPoint
& aPoint
) const;
51 template EditorDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
52 Text
& aTextNode
, const Element
* aAncestorLimiter
);
53 template EditorRawDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
54 Text
& aTextNode
, const Element
* aAncestorLimiter
);
55 template EditorDOMPoint
WSRunScanner::GetFirstVisiblePoint(
56 Text
& aTextNode
, const Element
* aAncestorLimiter
);
57 template EditorRawDOMPoint
WSRunScanner::GetFirstVisiblePoint(
58 Text
& aTextNode
, const Element
* aAncestorLimiter
);
60 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
61 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aScanStartPoint
);
62 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
63 HTMLEditor
& aHTMLEditor
, const EditorRawDOMPoint
& aScanStartPoint
);
64 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
65 HTMLEditor
& aHTMLEditor
, const EditorDOMPointInText
& aScanStartPoint
);
67 template WSRunScanner::TextFragmentData::TextFragmentData(
68 const EditorDOMPoint
& aPoint
, const Element
* aEditingHost
);
69 template WSRunScanner::TextFragmentData::TextFragmentData(
70 const EditorRawDOMPoint
& aPoint
, const Element
* aEditingHost
);
71 template WSRunScanner::TextFragmentData::TextFragmentData(
72 const EditorDOMPointInText
& aPoint
, const Element
* aEditingHost
);
74 nsresult
WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
75 HTMLEditor
& aHTMLEditor
, nsCOMPtr
<nsINode
>* aSplitNode
,
76 int32_t* aSplitOffset
) {
77 if (NS_WARN_IF(!aSplitNode
) || NS_WARN_IF(!*aSplitNode
) ||
78 NS_WARN_IF(!aSplitOffset
)) {
79 return NS_ERROR_INVALID_ARG
;
82 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), aSplitNode
,
85 nsresult rv
= WhiteSpaceVisibilityKeeper::
86 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
87 aHTMLEditor
, EditorDOMPoint(*aSplitNode
, *aSplitOffset
));
88 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
89 "WhiteSpaceVisibilityKeeper::"
90 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() "
96 EditActionResult
WhiteSpaceVisibilityKeeper::
97 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
98 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
99 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
100 const Maybe
<nsAtom
*>& aListElementTagName
,
101 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
103 EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
104 MOZ_ASSERT(&aRightBlockElement
== aAtRightBlockChild
.GetContainer());
106 // NOTE: This method may extend deletion range:
107 // - to delete invisible white-spaces at end of aLeftBlockElement
108 // - to delete invisible white-spaces at start of
109 // afterRightBlockChild.GetChild()
110 // - to delete invisible white-spaces before afterRightBlockChild.GetChild()
111 // - to delete invisible `<br>` element at end of aLeftBlockElement
113 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
115 EditorDOMPoint afterRightBlockChild
= aAtRightBlockChild
.NextPoint();
116 MOZ_ASSERT(afterRightBlockChild
.IsSetAndValid());
117 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
118 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
121 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
122 "failed at left block");
123 return EditActionResult(rv
);
125 if (!afterRightBlockChild
.IsSetAndValid()) {
127 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
128 "running script and the point to be modified was changed");
129 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
132 OwningNonNull
<Element
> rightBlockElement
= aRightBlockElement
;
134 // We can't just track rightBlockElement because it's an Element.
135 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(),
136 &afterRightBlockChild
);
137 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
138 aHTMLEditor
, afterRightBlockChild
);
141 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
142 "failed at right block child");
143 return EditActionResult(rv
);
146 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
147 // Do we really need to do update rightBlockElement here??
148 // XXX And afterRightBlockChild.GetContainerAsElement() always returns
149 // an element pointer so that probably here should not use
150 // accessors of EditorDOMPoint, should use DOM API directly instead.
151 if (afterRightBlockChild
.GetContainerAsElement()) {
152 rightBlockElement
= *afterRightBlockChild
.GetContainerAsElement();
153 } else if (NS_WARN_IF(
154 !afterRightBlockChild
.GetContainerParentAsElement())) {
155 return EditActionResult(NS_ERROR_UNEXPECTED
);
157 rightBlockElement
= *afterRightBlockChild
.GetContainerParentAsElement();
162 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
163 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
164 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
166 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
167 "The preceding invisible BR element computation was different");
168 EditActionResult
ret(NS_OK
);
169 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
170 // AutoInclusiveAncestorBlockElementsJoiner.
171 if (NS_WARN_IF(aListElementTagName
.isSome())) {
172 // Since 2002, here was the following comment:
173 // > The idea here is to take all children in rightListElement that are
174 // > past offset, and pull them into leftlistElement.
175 // However, this has never been performed because we are here only when
176 // neither left list nor right list is a descendant of the other but
177 // in such case, getting a list item in the right list node almost
178 // always failed since a variable for offset of
179 // rightListElement->GetChildAt() was not initialized. So, it might be
180 // a bug, but we should keep this traditional behavior for now. If you
181 // find when we get here, please remove this comment if we don't need to
182 // do it. Otherwise, please move children of the right list node to the
183 // end of the left list node.
185 // XXX Although, we do nothing here, but for keeping traditional
186 // behavior, we should mark as handled.
189 // XXX Why do we ignore the result of MoveOneHardLineContents()?
190 NS_ASSERTION(rightBlockElement
== afterRightBlockChild
.GetContainer(),
191 "The relation is not guaranteed but assumed");
193 Result
<bool, nsresult
> firstLineHasContent
=
194 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(EditorRawDOMPoint(
195 rightBlockElement
, afterRightBlockChild
.Offset()));
196 #endif // #ifdef DEBUG
197 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
198 EditorDOMPoint(rightBlockElement
, afterRightBlockChild
.Offset()),
199 EditorDOMPoint(&aLeftBlockElement
, 0),
200 HTMLEditor::MoveToEndOfContainer::Yes
);
201 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
202 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
204 NS_WARNING_ASSERTION(moveNodeResult
.Succeeded(),
205 "HTMLEditor::MoveOneHardLineContents("
206 "MoveToEndOfContainer::Yes) failed, but ignored");
207 if (moveNodeResult
.Succeeded()) {
209 MOZ_ASSERT(!firstLineHasContent
.isErr());
210 if (firstLineHasContent
.inspect()) {
211 NS_ASSERTION(moveNodeResult
.Handled(),
212 "Failed to consider whether moving or not something");
214 NS_ASSERTION(moveNodeResult
.Ignored(),
215 "Failed to consider whether moving or not something");
217 #endif // #ifdef DEBUG
218 ret
|= moveNodeResult
;
220 // Now, all children of rightBlockElement were moved to leftBlockElement.
221 // So, afterRightBlockChild is now invalid.
222 afterRightBlockChild
.Clear();
225 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
229 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
230 *invisibleBRElementAtEndOfLeftBlockElement
);
231 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
232 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
235 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
236 return EditActionResult(rv
);
238 return EditActionHandled();
242 EditActionResult
WhiteSpaceVisibilityKeeper::
243 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
244 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
245 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
246 nsIContent
& aLeftContentInBlock
,
247 const Maybe
<nsAtom
*>& aListElementTagName
,
248 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
250 EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
252 &aLeftBlockElement
== &aLeftContentInBlock
||
253 EditorUtils::IsDescendantOf(aLeftContentInBlock
, aLeftBlockElement
));
254 MOZ_ASSERT(&aLeftBlockElement
== aAtLeftBlockChild
.GetContainer());
256 // NOTE: This method may extend deletion range:
257 // - to delete invisible white-spaces at start of aRightBlockElement
258 // - to delete invisible white-spaces before aRightBlockElement
259 // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild()
260 // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild()
261 // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
263 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
265 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
266 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0));
269 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() failed "
271 return EditActionResult(rv
);
273 if (!aAtLeftBlockChild
.IsSetAndValid()) {
275 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
276 "running script and the point to be modified was changed");
277 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
280 OwningNonNull
<Element
> originalLeftBlockElement
= aLeftBlockElement
;
281 OwningNonNull
<Element
> leftBlockElement
= aLeftBlockElement
;
282 EditorDOMPoint
atLeftBlockChild(aAtLeftBlockChild
);
284 // We can't just track leftBlockElement because it's an Element, so track
286 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &atLeftBlockChild
);
287 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
288 aHTMLEditor
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
289 atLeftBlockChild
.Offset()));
292 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
293 "failed at left block child");
294 return EditActionResult(rv
);
296 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
297 // Do we really need to do update aRightBlockElement here??
298 // XXX And atLeftBlockChild.GetContainerAsElement() always returns
299 // an element pointer so that probably here should not use
300 // accessors of EditorDOMPoint, should use DOM API directly instead.
301 if (atLeftBlockChild
.GetContainerAsElement()) {
302 leftBlockElement
= *atLeftBlockChild
.GetContainerAsElement();
303 } else if (NS_WARN_IF(!atLeftBlockChild
.GetContainerParentAsElement())) {
304 return EditActionResult(NS_ERROR_UNEXPECTED
);
306 leftBlockElement
= *atLeftBlockChild
.GetContainerParentAsElement();
311 RefPtr
<HTMLBRElement
> invisibleBRElementBeforeLeftBlockElement
=
312 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
313 aHTMLEditor
, atLeftBlockChild
);
315 aPrecedingInvisibleBRElement
== invisibleBRElementBeforeLeftBlockElement
,
316 "The preceding invisible BR element computation was different");
317 EditActionResult
ret(NS_OK
);
318 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
319 // AutoInclusiveAncestorBlockElementsJoiner.
320 if (aListElementTagName
.isSome()) {
321 // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
322 MOZ_ASSERT(originalLeftBlockElement
== atLeftBlockChild
.GetContainer(),
323 "This is not guaranteed, but assumed");
325 Result
<bool, nsresult
> rightBlockHasContent
=
326 aHTMLEditor
.CanMoveChildren(aRightBlockElement
, aLeftBlockElement
);
327 #endif // #ifdef DEBUG
328 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveChildrenWithTransaction(
329 aRightBlockElement
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
330 atLeftBlockChild
.Offset()));
331 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
332 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
334 NS_WARNING_ASSERTION(
335 moveNodeResult
.Succeeded(),
336 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
337 if (moveNodeResult
.Succeeded()) {
338 ret
|= moveNodeResult
;
340 MOZ_ASSERT(!rightBlockHasContent
.isErr());
341 if (rightBlockHasContent
.inspect()) {
342 NS_ASSERTION(moveNodeResult
.Handled(),
343 "Failed to consider whether moving or not children");
345 NS_ASSERTION(moveNodeResult
.Ignored(),
346 "Failed to consider whether moving or not children");
348 #endif // #ifdef DEBUG
350 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
351 atLeftBlockChild
.Clear();
353 // Left block is a parent of right block, and the parent of the previous
354 // visible content. Right block is a child and contains the contents we
357 EditorDOMPoint atPreviousContent
;
358 if (&aLeftContentInBlock
== leftBlockElement
) {
359 // We are working with valid HTML, aLeftContentInBlock is a block node,
360 // and is therefore allowed to contain aRightBlockElement. This is the
361 // simple case, we will simply move the content in aRightBlockElement
363 atPreviousContent
= atLeftBlockChild
;
365 // We try to work as well as possible with HTML that's already invalid.
366 // Although "right block" is a block, and a block must not be contained
367 // in inline elements, reality is that broken documents do exist. The
368 // DIRECT parent of "left NODE" might be an inline element. Previous
369 // versions of this code skipped inline parents until the first block
370 // parent was found (and used "left block" as the destination).
371 // However, in some situations this strategy moves the content to an
372 // unexpected position. (see bug 200416) The new idea is to make the
373 // moving content a sibling, next to the previous visible content.
374 atPreviousContent
.Set(&aLeftContentInBlock
);
376 // We want to move our content just after the previous visible node.
377 atPreviousContent
.AdvanceOffset();
380 MOZ_ASSERT(atPreviousContent
.IsSet());
382 // Because we don't want the moving content to receive the style of the
383 // previous content, we split the previous content's style.
386 Result
<bool, nsresult
> firstLineHasContent
=
387 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
388 EditorRawDOMPoint(&aRightBlockElement
, 0));
389 #endif // #ifdef DEBUG
391 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
392 // XXX It's odd to continue handling this edit action if there is no
394 if (!editingHost
|| &aLeftContentInBlock
!= editingHost
) {
395 SplitNodeResult splitResult
=
396 aHTMLEditor
.SplitAncestorStyledInlineElementsAt(atPreviousContent
,
398 if (splitResult
.Failed()) {
399 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
400 return EditActionResult(splitResult
.Rv());
403 if (splitResult
.Handled()) {
404 if (splitResult
.GetNextNode()) {
405 atPreviousContent
.Set(splitResult
.GetNextNode());
406 if (!atPreviousContent
.IsSet()) {
407 NS_WARNING("Next node of split point was orphaned");
408 return EditActionResult(NS_ERROR_NULL_POINTER
);
411 atPreviousContent
= splitResult
.SplitPoint();
412 if (!atPreviousContent
.IsSet()) {
413 NS_WARNING("Split node was orphaned");
414 return EditActionResult(NS_ERROR_NULL_POINTER
);
420 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
421 EditorDOMPoint(&aRightBlockElement
, 0), atPreviousContent
);
422 if (moveNodeResult
.Failed()) {
423 NS_WARNING("HTMLEditor::MoveOneHardLineContents() failed");
424 return EditActionResult(moveNodeResult
.Rv());
428 MOZ_ASSERT(!firstLineHasContent
.isErr());
429 if (firstLineHasContent
.inspect()) {
430 NS_ASSERTION(moveNodeResult
.Handled(),
431 "Failed to consider whether moving or not something");
433 NS_ASSERTION(moveNodeResult
.Ignored(),
434 "Failed to consider whether moving or not something");
436 #endif // #ifdef DEBUG
438 ret
|= moveNodeResult
;
441 if (!invisibleBRElementBeforeLeftBlockElement
) {
445 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
446 *invisibleBRElementBeforeLeftBlockElement
);
447 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
448 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
451 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
452 return EditActionResult(rv
);
454 return EditActionHandled();
458 EditActionResult
WhiteSpaceVisibilityKeeper::
459 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
460 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
461 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
462 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
464 !EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
466 !EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
468 // NOTE: This method may extend deletion range:
469 // - to delete invisible white-spaces at end of aLeftBlockElement
470 // - to delete invisible white-spaces at start of aRightBlockElement
471 // - to delete invisible `<br>` element at end of aLeftBlockElement
473 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
475 // Adjust white-space at block boundaries
476 nsresult rv
= WhiteSpaceVisibilityKeeper::
477 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
479 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement
),
480 EditorDOMPoint(&aRightBlockElement
, 0)));
483 "WhiteSpaceVisibilityKeeper::"
484 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
485 return EditActionResult(rv
);
488 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
489 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
490 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
492 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
493 "The preceding invisible BR element computation was different");
494 EditActionResult
ret(NS_OK
);
495 if (aListElementTagName
.isSome() ||
496 aLeftBlockElement
.NodeInfo()->NameAtom() ==
497 aRightBlockElement
.NodeInfo()->NameAtom()) {
498 // Nodes are same type. merge them.
499 EditorDOMPoint atFirstChildOfRightNode
;
500 nsresult rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
501 aLeftBlockElement
, aRightBlockElement
, &atFirstChildOfRightNode
);
502 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
503 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
505 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
506 "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
507 " failed, but ignored");
508 if (aListElementTagName
.isSome() && atFirstChildOfRightNode
.IsSet()) {
509 CreateElementResult convertListTypeResult
=
510 aHTMLEditor
.ChangeListElementType(
511 aRightBlockElement
, MOZ_KnownLive(*aListElementTagName
.ref()),
513 if (NS_WARN_IF(convertListTypeResult
.EditorDestroyed())) {
514 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
516 NS_WARNING_ASSERTION(
517 convertListTypeResult
.Succeeded(),
518 "HTMLEditor::ChangeListElementType() failed, but ignored");
523 Result
<bool, nsresult
> firstLineHasContent
=
524 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
525 EditorRawDOMPoint(&aRightBlockElement
, 0));
526 #endif // #ifdef DEBUG
528 // Nodes are dissimilar types.
529 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
530 EditorDOMPoint(&aRightBlockElement
, 0),
531 EditorDOMPoint(&aLeftBlockElement
, 0),
532 HTMLEditor::MoveToEndOfContainer::Yes
);
533 if (moveNodeResult
.Failed()) {
535 "HTMLEditor::MoveOneHardLineContents(MoveToEndOfContainer::Yes) "
537 return EditActionResult(moveNodeResult
.Rv());
541 MOZ_ASSERT(!firstLineHasContent
.isErr());
542 if (firstLineHasContent
.inspect()) {
543 NS_ASSERTION(moveNodeResult
.Handled(),
544 "Failed to consider whether moving or not something");
546 NS_ASSERTION(moveNodeResult
.Ignored(),
547 "Failed to consider whether moving or not something");
549 #endif // #ifdef DEBUG
550 ret
|= moveNodeResult
;
553 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
554 return ret
.MarkAsHandled();
557 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
558 *invisibleBRElementAtEndOfLeftBlockElement
);
559 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
560 return ret
.SetResult(NS_ERROR_EDITOR_DESTROYED
);
562 // XXX In other top level if blocks, the result of
563 // DeleteNodeWithTransaction() is ignored. Why does only this result
566 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
567 return EditActionResult(rv
);
569 return EditActionHandled();
573 Result
<RefPtr
<Element
>, nsresult
> WhiteSpaceVisibilityKeeper::InsertBRElement(
574 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
) {
575 if (NS_WARN_IF(!aPointToInsert
.IsSet())) {
576 return Err(NS_ERROR_INVALID_ARG
);
579 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
580 // meanwhile, the pre case is handled in HandleInsertText() in
581 // HTMLEditSubActionHandler.cpp
583 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
584 TextFragmentData
textFragmentDataAtInsertionPoint(aPointToInsert
,
586 const EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine
=
587 textFragmentDataAtInsertionPoint
588 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
589 const EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine
=
590 textFragmentDataAtInsertionPoint
591 .GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
592 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpaces
=
593 !invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned() ||
594 !invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()
595 ? Some(textFragmentDataAtInsertionPoint
.VisibleWhiteSpacesDataRef())
597 const PointPosition pointPositionWithVisibleWhiteSpaces
=
598 visibleWhiteSpaces
.isSome() && visibleWhiteSpaces
.ref().IsInitialized()
599 ? visibleWhiteSpaces
.ref().ComparePoint(aPointToInsert
)
600 : PointPosition::NotInSameDOMTree
;
602 EditorDOMPoint
pointToInsert(aPointToInsert
);
604 // Some scoping for AutoTrackDOMPoint. This will track our insertion
605 // point while we tweak any surrounding white-space
606 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToInsert
);
608 if (invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()) {
609 if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Collapsed()) {
610 // XXX Why don't we remove all of the invisible white-spaces?
611 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef() ==
613 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
614 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef(),
615 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.EndRef(),
616 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
619 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
624 // If new line will start with visible white-spaces, it needs to be start
626 else if (pointPositionWithVisibleWhiteSpaces
==
627 PointPosition::StartOfFragment
||
628 pointPositionWithVisibleWhiteSpaces
==
629 PointPosition::MiddleOfFragment
) {
630 EditorRawDOMPointInText atNextCharOfInsertionPoint
=
631 textFragmentDataAtInsertionPoint
.GetInclusiveNextEditableCharPoint(
633 if (atNextCharOfInsertionPoint
.IsSet() &&
634 !atNextCharOfInsertionPoint
.IsEndOfContainer() &&
635 atNextCharOfInsertionPoint
.IsCharASCIISpace() &&
636 !EditorUtils::IsContentPreformatted(
637 *atNextCharOfInsertionPoint
.ContainerAsText())) {
638 EditorRawDOMPointInText atPreviousCharOfNextCharOfInsertionPoint
=
639 textFragmentDataAtInsertionPoint
.GetPreviousEditableCharPoint(
640 atNextCharOfInsertionPoint
);
641 if (!atPreviousCharOfNextCharOfInsertionPoint
.IsSet() ||
642 atPreviousCharOfNextCharOfInsertionPoint
.IsEndOfContainer() ||
643 !atPreviousCharOfNextCharOfInsertionPoint
.IsCharASCIISpace()) {
644 // We are at start of non-nbsps. Convert to a single nbsp.
645 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
646 textFragmentDataAtInsertionPoint
647 .GetEndOfCollapsibleASCIIWhiteSpaces(
648 atNextCharOfInsertionPoint
);
650 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
652 EditorDOMRangeInTexts(atNextCharOfInsertionPoint
,
653 endOfCollapsibleASCIIWhiteSpaces
),
654 nsDependentSubstring(&kNBSP
, 1));
657 "WhiteSpaceVisibilityKeeper::"
658 "ReplaceTextAndRemoveEmptyTextNodes() failed");
665 if (invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned()) {
666 if (!invisibleLeadingWhiteSpaceRangeOfNewLine
.Collapsed()) {
667 // XXX Why don't we remove all of the invisible white-spaces?
668 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef() ==
670 // XXX If the DOM tree has been changed above,
671 // invisibleLeadingWhiteSpaceRangeOfNewLine may be invalid now.
672 // So, we may do something wrong here.
673 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
674 invisibleLeadingWhiteSpaceRangeOfNewLine
.StartRef(),
675 invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef(),
676 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
679 "WhiteSpaceVisibilityKeeper::"
680 "DeleteTextAndTextNodesWithTransaction() failed");
685 // If the `<br>` element is put immediately after an NBSP, it should be
686 // replaced with an ASCII white-space.
687 else if (pointPositionWithVisibleWhiteSpaces
==
688 PointPosition::MiddleOfFragment
||
689 pointPositionWithVisibleWhiteSpaces
==
690 PointPosition::EndOfFragment
) {
691 // XXX If the DOM tree has been changed above, pointToInsert` and/or
692 // `visibleWhiteSpaces` may be invalid. So, we may do
693 // something wrong here.
694 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
695 textFragmentDataAtInsertionPoint
696 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
698 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
699 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
700 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
701 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
702 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
704 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
711 RefPtr
<Element
> newBRElement
= aHTMLEditor
.InsertBRElementWithTransaction(
712 pointToInsert
, nsIEditor::eNone
);
713 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
714 return Err(NS_ERROR_EDITOR_DESTROYED
);
717 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
718 return Err(NS_ERROR_FAILURE
);
724 nsresult
WhiteSpaceVisibilityKeeper::ReplaceText(
725 HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
726 const EditorDOMRange
& aRangeToBeReplaced
,
727 EditorRawDOMPoint
* aPointAfterInsertedString
/* = nullptr */) {
728 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
729 // meanwhile, the pre case is handled in HandleInsertText() in
730 // HTMLEditSubActionHandler.cpp
732 // MOOSE: for now, just getting the ws logic straight. This implementation
733 // is very slow. Will need to replace edit rules impl with a more efficient
734 // text sink here that does the minimal amount of searching/replacing/copying
736 if (aStringToInsert
.IsEmpty()) {
737 MOZ_ASSERT(aRangeToBeReplaced
.Collapsed());
738 if (aPointAfterInsertedString
) {
739 *aPointAfterInsertedString
= aRangeToBeReplaced
.StartRef();
744 RefPtr
<Element
> editingHost
= aHTMLEditor
.GetActiveEditingHost();
745 TextFragmentData
textFragmentDataAtStart(aRangeToBeReplaced
.StartRef(),
747 const bool isInsertionPointEqualsOrIsBeforeStartOfText
=
748 aRangeToBeReplaced
.StartRef().EqualsOrIsBefore(
749 textFragmentDataAtStart
.StartRef());
750 TextFragmentData textFragmentDataAtEnd
=
751 aRangeToBeReplaced
.Collapsed()
752 ? textFragmentDataAtStart
753 : TextFragmentData(aRangeToBeReplaced
.EndRef(), editingHost
);
754 const bool isInsertionPointEqualsOrAfterEndOfText
=
755 textFragmentDataAtEnd
.EndRef().EqualsOrIsBefore(
756 aRangeToBeReplaced
.EndRef());
758 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
759 textFragmentDataAtStart
760 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
761 aRangeToBeReplaced
.StartRef());
762 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
763 textFragmentDataAtEnd
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
764 aRangeToBeReplaced
.EndRef());
765 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtStart
=
766 !invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()
767 ? Some(textFragmentDataAtStart
.VisibleWhiteSpacesDataRef())
769 const PointPosition pointPositionWithVisibleWhiteSpacesAtStart
=
770 visibleWhiteSpacesAtStart
.isSome() &&
771 visibleWhiteSpacesAtStart
.ref().IsInitialized()
772 ? visibleWhiteSpacesAtStart
.ref().ComparePoint(
773 aRangeToBeReplaced
.StartRef())
774 : PointPosition::NotInSameDOMTree
;
775 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtEnd
=
776 !invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()
777 ? Some(textFragmentDataAtEnd
.VisibleWhiteSpacesDataRef())
779 const PointPosition pointPositionWithVisibleWhiteSpacesAtEnd
=
780 visibleWhiteSpacesAtEnd
.isSome() &&
781 visibleWhiteSpacesAtEnd
.ref().IsInitialized()
782 ? visibleWhiteSpacesAtEnd
.ref().ComparePoint(
783 aRangeToBeReplaced
.EndRef())
784 : PointPosition::NotInSameDOMTree
;
786 EditorDOMPoint
pointToInsert(aRangeToBeReplaced
.StartRef());
787 nsAutoString
theString(aStringToInsert
);
789 // Some scoping for AutoTrackDOMPoint. This will track our insertion
790 // point while we tweak any surrounding white-space
791 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToInsert
);
793 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
794 if (!invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
795 // XXX Why don't we remove all of the invisible white-spaces?
796 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() ==
798 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
799 invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef(),
800 invisibleTrailingWhiteSpaceRangeAtEnd
.EndRef(),
801 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
804 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
809 // Replace an NBSP at inclusive next character of replacing range to an
810 // ASCII white-space if inserting into a visible white-space sequence.
811 // XXX With modifying the inserting string later, this creates a line break
812 // opportunity after the inserting string, but this causes
813 // inconsistent result with inserting order. E.g., type white-space
814 // n times with various order.
815 else if (pointPositionWithVisibleWhiteSpacesAtEnd
==
816 PointPosition::StartOfFragment
||
817 pointPositionWithVisibleWhiteSpacesAtEnd
==
818 PointPosition::MiddleOfFragment
) {
819 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
820 textFragmentDataAtEnd
821 .GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
823 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
824 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
825 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
826 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
827 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
829 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
835 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
836 if (!invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
837 // XXX Why don't we remove all of the invisible white-spaces?
838 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeAtStart
.EndRef() ==
840 // XXX If the DOM tree has been changed above,
841 // invisibleLeadingWhiteSpaceRangeAtStart may be invalid now.
842 // So, we may do something wrong here.
843 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
844 invisibleLeadingWhiteSpaceRangeAtStart
.StartRef(),
845 invisibleLeadingWhiteSpaceRangeAtStart
.EndRef(),
846 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
849 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
854 // Replace an NBSP at previous character of insertion point to an ASCII
855 // white-space if inserting into a visible white-space sequence.
856 // XXX With modifying the inserting string later, this creates a line break
857 // opportunity before the inserting string, but this causes
858 // inconsistent result with inserting order. E.g., type white-space
859 // n times with various order.
860 else if (pointPositionWithVisibleWhiteSpacesAtStart
==
861 PointPosition::MiddleOfFragment
||
862 pointPositionWithVisibleWhiteSpacesAtStart
==
863 PointPosition::EndOfFragment
) {
864 // XXX If the DOM tree has been changed above, pointToInsert` and/or
865 // `visibleWhiteSpaces` may be invalid. So, we may do
866 // something wrong here.
867 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
868 textFragmentDataAtStart
869 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
871 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
872 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
873 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
874 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
875 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
877 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
883 // After this block, pointToInsert is modified by AutoTrackDOMPoint.
886 // Next up, tweak head and tail of string as needed. First the head: there
887 // are a variety of circumstances that would require us to convert a leading
888 // ws char into an nbsp:
890 if (nsCRT::IsAsciiSpace(theString
[0])) {
891 // If inserting string will follow some invisible leading white-spaces, the
892 // string needs to start with an NBSP.
893 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
894 theString
.SetCharAt(kNBSP
, 0);
896 // If inserting around visible white-spaces, check whether the previous
897 // character of insertion point is an NBSP or an ASCII white-space.
898 else if (pointPositionWithVisibleWhiteSpacesAtStart
==
899 PointPosition::MiddleOfFragment
||
900 pointPositionWithVisibleWhiteSpacesAtStart
==
901 PointPosition::EndOfFragment
) {
902 EditorDOMPointInText atPreviousChar
=
903 textFragmentDataAtStart
.GetPreviousEditableCharPoint(pointToInsert
);
904 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsEndOfContainer() &&
905 atPreviousChar
.IsCharASCIISpace()) {
906 theString
.SetCharAt(kNBSP
, 0);
909 // If the insertion point is (was) before the start of text and it's
910 // immediately after a hard line break, the first ASCII white-space should
911 // be replaced with an NBSP for making it visible.
912 else if (textFragmentDataAtStart
.StartsFromHardLineBreak() &&
913 isInsertionPointEqualsOrIsBeforeStartOfText
) {
914 theString
.SetCharAt(kNBSP
, 0);
919 uint32_t lastCharIndex
= theString
.Length() - 1;
921 if (nsCRT::IsAsciiSpace(theString
[lastCharIndex
])) {
922 // If inserting string will be followed by some invisible trailing
923 // white-spaces, the string needs to end with an NBSP.
924 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
925 theString
.SetCharAt(kNBSP
, lastCharIndex
);
927 // If inserting around visible white-spaces, check whether the inclusive
928 // next character of end of replaced range is an NBSP or an ASCII
930 if (pointPositionWithVisibleWhiteSpacesAtEnd
==
931 PointPosition::StartOfFragment
||
932 pointPositionWithVisibleWhiteSpacesAtEnd
==
933 PointPosition::MiddleOfFragment
) {
934 EditorDOMPointInText atNextChar
=
935 textFragmentDataAtEnd
.GetInclusiveNextEditableCharPoint(
937 if (atNextChar
.IsSet() && !atNextChar
.IsEndOfContainer() &&
938 atNextChar
.IsCharASCIISpace()) {
939 theString
.SetCharAt(kNBSP
, lastCharIndex
);
942 // If the end of replacing range is (was) after the end of text and it's
943 // immediately before block boundary, the last ASCII white-space should
944 // be replaced with an NBSP for making it visible.
945 else if (textFragmentDataAtEnd
.EndsByBlockBoundary() &&
946 isInsertionPointEqualsOrAfterEndOfText
) {
947 theString
.SetCharAt(kNBSP
, lastCharIndex
);
951 // Next, scan string for adjacent ws and convert to nbsp/space combos
952 // MOOSE: don't need to convert tabs here since that is done by
953 // WillInsertText() before we are called. Eventually, all that logic will be
954 // pushed down into here and made more efficient.
956 for (uint32_t i
= 0; i
<= lastCharIndex
; i
++) {
957 if (nsCRT::IsAsciiSpace(theString
[i
])) {
959 // i - 1 can't be negative because prevWS starts out false
960 theString
.SetCharAt(kNBSP
, i
- 1);
969 // XXX If the point is not editable, InsertTextWithTransaction() returns
970 // error, but we keep handling it. But I think that it wastes the
971 // runtime cost. So, perhaps, we should return error code which couldn't
972 // modify it and make each caller of this method decide whether it should
973 // keep or stop handling the edit action.
974 if (!aHTMLEditor
.GetDocument()) {
976 "WhiteSpaceVisibilityKeeper::ReplaceText() lost proper document");
977 return NS_ERROR_UNEXPECTED
;
979 OwningNonNull
<Document
> document
= *aHTMLEditor
.GetDocument();
980 nsresult rv
= aHTMLEditor
.InsertTextWithTransaction(
981 document
, theString
, pointToInsert
, aPointAfterInsertedString
);
982 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
983 return NS_ERROR_EDITOR_DESTROYED
;
985 if (NS_SUCCEEDED(rv
)) {
989 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed, but ignored");
991 // XXX Temporarily, set new insertion point to the original point.
992 if (aPointAfterInsertedString
) {
993 *aPointAfterInsertedString
= pointToInsert
;
999 nsresult
WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
1000 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
1001 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
1002 TextFragmentData
textFragmentDataAtDeletion(aPoint
, editingHost
);
1003 EditorDOMPointInText atPreviousCharOfStart
=
1004 textFragmentDataAtDeletion
.GetPreviousEditableCharPoint(aPoint
);
1005 if (!atPreviousCharOfStart
.IsSet() ||
1006 atPreviousCharOfStart
.IsEndOfContainer()) {
1010 // Easy case, preformatted ws.
1011 if (EditorUtils::IsContentPreformatted(
1012 *atPreviousCharOfStart
.ContainerAsText())) {
1013 if (!atPreviousCharOfStart
.IsCharASCIISpace() &&
1014 !atPreviousCharOfStart
.IsCharNBSP()) {
1017 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1018 atPreviousCharOfStart
, atPreviousCharOfStart
.NextPoint(),
1019 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1020 NS_WARNING_ASSERTION(
1022 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1026 // Caller's job to ensure that previous char is really ws. If it is normal
1027 // ws, we need to delete the whole run.
1028 if (atPreviousCharOfStart
.IsCharASCIISpace()) {
1029 EditorDOMPoint startToDelete
=
1030 textFragmentDataAtDeletion
.GetFirstASCIIWhiteSpacePointCollapsedTo(
1031 atPreviousCharOfStart
);
1032 EditorDOMPoint endToDelete
=
1033 textFragmentDataAtDeletion
.GetEndOfCollapsibleASCIIWhiteSpaces(
1034 atPreviousCharOfStart
);
1036 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1037 aHTMLEditor
, &startToDelete
, &endToDelete
);
1038 if (NS_FAILED(rv
)) {
1040 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1045 // finally, delete that ws
1046 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1047 startToDelete
, endToDelete
,
1048 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1049 NS_WARNING_ASSERTION(
1051 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1055 if (atPreviousCharOfStart
.IsCharNBSP()) {
1056 EditorDOMPoint
startToDelete(atPreviousCharOfStart
);
1057 EditorDOMPoint
endToDelete(startToDelete
.NextPoint());
1059 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1060 aHTMLEditor
, &startToDelete
, &endToDelete
);
1061 if (NS_FAILED(rv
)) {
1063 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1068 // finally, delete that ws
1069 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1070 startToDelete
, endToDelete
,
1071 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1072 NS_WARNING_ASSERTION(
1074 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1082 nsresult
WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
1083 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
1084 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
1085 TextFragmentData
textFragmentDataAtDeletion(aPoint
, editingHost
);
1086 EditorDOMPointInText atNextCharOfStart
=
1087 textFragmentDataAtDeletion
.GetInclusiveNextEditableCharPoint(aPoint
);
1088 if (!atNextCharOfStart
.IsSet() || atNextCharOfStart
.IsEndOfContainer()) {
1092 // Easy case, preformatted ws.
1093 if (EditorUtils::IsContentPreformatted(
1094 *atNextCharOfStart
.ContainerAsText())) {
1095 if (!atNextCharOfStart
.IsCharASCIISpace() &&
1096 !atNextCharOfStart
.IsCharNBSP()) {
1099 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1100 atNextCharOfStart
, atNextCharOfStart
.NextPoint(),
1101 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1102 NS_WARNING_ASSERTION(
1104 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1108 // Caller's job to ensure that next char is really ws. If it is normal ws,
1109 // we need to delete the whole run.
1110 if (atNextCharOfStart
.IsCharASCIISpace()) {
1111 EditorDOMPoint startToDelete
=
1112 textFragmentDataAtDeletion
.GetFirstASCIIWhiteSpacePointCollapsedTo(
1114 EditorDOMPoint endToDelete
=
1115 textFragmentDataAtDeletion
.GetEndOfCollapsibleASCIIWhiteSpaces(
1118 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1119 aHTMLEditor
, &startToDelete
, &endToDelete
);
1120 if (NS_FAILED(rv
)) {
1122 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1127 // Finally, delete that ws
1128 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1129 startToDelete
, endToDelete
,
1130 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1131 NS_WARNING_ASSERTION(
1133 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1137 if (atNextCharOfStart
.IsCharNBSP()) {
1138 EditorDOMPoint
startToDelete(atNextCharOfStart
);
1139 EditorDOMPoint
endToDelete(startToDelete
.NextPoint());
1141 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1142 aHTMLEditor
, &startToDelete
, &endToDelete
);
1143 if (NS_FAILED(rv
)) {
1145 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1150 // Finally, delete that ws
1151 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1152 startToDelete
, endToDelete
,
1153 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1154 NS_WARNING_ASSERTION(
1156 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1164 nsresult
WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
1165 HTMLEditor
& aHTMLEditor
, nsIContent
& aContentToDelete
,
1166 const EditorDOMPoint
& aCaretPoint
) {
1167 EditorDOMPoint
atContent(&aContentToDelete
);
1168 if (!atContent
.IsSet()) {
1169 NS_WARNING("Deleting content node was an orphan node");
1170 return NS_ERROR_FAILURE
;
1172 if (!HTMLEditUtils::IsRemovableNode(aContentToDelete
)) {
1173 NS_WARNING("Deleting content node wasn't removable");
1174 return NS_ERROR_FAILURE
;
1176 nsresult rv
= WhiteSpaceVisibilityKeeper::
1177 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1178 aHTMLEditor
, EditorDOMRange(atContent
, atContent
.NextPoint()));
1179 if (NS_FAILED(rv
)) {
1181 "WhiteSpaceVisibilityKeeper::"
1182 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
1186 nsCOMPtr
<nsIContent
> previousEditableSibling
=
1187 aHTMLEditor
.GetPriorHTMLSibling(&aContentToDelete
);
1188 // Delete the node, and join like nodes if appropriate
1189 rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContentToDelete
);
1190 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1191 return NS_ERROR_EDITOR_DESTROYED
;
1193 if (NS_FAILED(rv
)) {
1194 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1197 // Are they both text nodes? If so, join them!
1198 // XXX This may cause odd behavior if there is non-editable nodes
1199 // around the atomic content.
1200 if (!aCaretPoint
.IsInTextNode() || !previousEditableSibling
||
1201 !previousEditableSibling
->IsText()) {
1205 nsIContent
* nextEditableSibling
=
1206 aHTMLEditor
.GetNextHTMLSibling(previousEditableSibling
);
1207 if (aCaretPoint
.GetContainer() != nextEditableSibling
) {
1210 EditorDOMPoint atFirstChildOfRightNode
;
1211 rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
1212 *previousEditableSibling
,
1213 MOZ_KnownLive(*aCaretPoint
.GetContainerAsText()),
1214 &atFirstChildOfRightNode
);
1215 if (NS_FAILED(rv
)) {
1216 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1219 if (!atFirstChildOfRightNode
.IsSet()) {
1221 "HTMLEditor::JoinNearestEditableNodesWithTransaction() didn't return "
1222 "right node position");
1223 return NS_ERROR_FAILURE
;
1226 rv
= aHTMLEditor
.CollapseSelectionTo(atFirstChildOfRightNode
);
1227 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1228 "HTMLEditor::CollapseSelectionTo() failed");
1232 template <typename PT
, typename CT
>
1233 WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1234 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1235 MOZ_ASSERT(aPoint
.IsSet());
1237 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1238 return WSScanResult(nullptr, WSType::UnexpectedError
);
1241 // If the range has visible text and start of the visible text is before
1242 // aPoint, return previous character in the text.
1243 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1244 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1245 if (visibleWhiteSpaces
.IsInitialized() &&
1246 visibleWhiteSpaces
.StartRef().IsBefore(aPoint
)) {
1247 EditorDOMPointInText atPreviousChar
= GetPreviousEditableCharPoint(aPoint
);
1248 // When it's a non-empty text node, return it.
1249 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsContainerEmpty()) {
1250 MOZ_ASSERT(!atPreviousChar
.IsEndOfContainer());
1251 return WSScanResult(atPreviousChar
.NextPoint(),
1252 atPreviousChar
.IsCharASCIISpaceOrNBSP()
1253 ? WSType::NormalWhiteSpaces
1254 : WSType::NormalText
);
1258 // Otherwise, return the start of the range.
1259 if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
1260 TextFragmentDataAtStartRef().StartRef().GetContainer()) {
1261 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
1263 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1264 TextFragmentDataAtStartRef().StartRawReason());
1266 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1267 TextFragmentDataAtStartRef().StartRawReason());
1270 template <typename PT
, typename CT
>
1271 WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
1272 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1273 MOZ_ASSERT(aPoint
.IsSet());
1275 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1276 return WSScanResult(nullptr, WSType::UnexpectedError
);
1279 // If the range has visible text and aPoint equals or is before the end of the
1280 // visible text, return inclusive next character in the text.
1281 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1282 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1283 if (visibleWhiteSpaces
.IsInitialized() &&
1284 aPoint
.EqualsOrIsBefore(visibleWhiteSpaces
.EndRef())) {
1285 EditorDOMPointInText atNextChar
= GetInclusiveNextEditableCharPoint(aPoint
);
1286 // When it's a non-empty text node, return it.
1287 if (atNextChar
.IsSet() && !atNextChar
.IsContainerEmpty()) {
1288 return WSScanResult(
1290 !atNextChar
.IsEndOfContainer() && atNextChar
.IsCharASCIISpaceOrNBSP()
1291 ? WSType::NormalWhiteSpaces
1292 : WSType::NormalText
);
1296 // Otherwise, return the end of the range.
1297 if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
1298 TextFragmentDataAtStartRef().EndRef().GetContainer()) {
1299 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
1301 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1302 TextFragmentDataAtStartRef().EndRawReason());
1304 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1305 TextFragmentDataAtStartRef().EndRawReason());
1308 template <typename EditorDOMPointType
>
1309 WSRunScanner::TextFragmentData::TextFragmentData(
1310 const EditorDOMPointType
& aPoint
, const Element
* aEditingHost
)
1311 : mEditingHost(aEditingHost
), mIsPreformatted(false) {
1312 if (!aPoint
.IsSetAndValid()) {
1313 NS_WARNING("aPoint was invalid");
1316 if (!aPoint
.IsInContentNode()) {
1317 NS_WARNING("aPoint was in Document or DocumentFragment");
1318 // I.e., we're try to modify outside of root element. We don't need to
1319 // support such odd case because web apps cannot append text nodes as
1320 // direct child of Document node.
1324 mScanStartPoint
= aPoint
;
1325 NS_ASSERTION(EditorUtils::IsEditableContent(
1326 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
1327 "Given content is not editable");
1329 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
1330 "Given content is not an element and an orphan node");
1331 nsIContent
* editableBlockParentOrTopmotEditableInlineContent
=
1332 EditorUtils::IsEditableContent(*mScanStartPoint
.ContainerAsContent(),
1335 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
1336 *mScanStartPoint
.ContainerAsContent())
1338 if (!editableBlockParentOrTopmotEditableInlineContent
) {
1339 // Meaning that the container of `mScanStartPoint` is not editable.
1340 editableBlockParentOrTopmotEditableInlineContent
=
1341 mScanStartPoint
.ContainerAsContent();
1344 mStart
= BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1345 mScanStartPoint
, *editableBlockParentOrTopmotEditableInlineContent
,
1346 mEditingHost
, &mNBSPData
);
1347 mEnd
= BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1348 mScanStartPoint
, *editableBlockParentOrTopmotEditableInlineContent
,
1349 mEditingHost
, &mNBSPData
);
1350 // If scan start point is start/end of preformatted text node, only
1351 // mEnd/mStart crosses a preformatted character so that when one of
1352 // them crosses a preformatted character, this fragment's range is
1354 // Additionally, if the scan start point is preformatted, and there is
1355 // no text node around it, the range is also preformatted.
1356 mIsPreformatted
= mStart
.AcrossPreformattedCharacter() ||
1357 mEnd
.AcrossPreformattedCharacter() ||
1358 (EditorUtils::IsContentPreformatted(
1359 *mScanStartPoint
.ContainerAsContent()) &&
1360 !mStart
.IsNormalText() && !mEnd
.IsNormalText());
1364 template <typename EditorDOMPointType
>
1365 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1366 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1367 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1368 MOZ_ASSERT(aPoint
.IsSetAndValid());
1369 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1370 MOZ_DIAGNOSTIC_ASSERT(
1371 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1373 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1374 for (uint32_t i
= std::min(aPoint
.Offset(), textFragment
.GetLength()); i
;
1376 char16_t ch
= textFragment
.CharAt(i
- 1);
1377 if (nsCRT::IsAsciiSpace(ch
)) {
1381 if (ch
== HTMLEditUtils::kNBSP
) {
1383 aNBSPData
->NotifyNBSP(
1384 EditorDOMPointInText(aPoint
.ContainerAsText(), i
- 1),
1385 NoBreakingSpaceData::Scanning::Backward
);
1390 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1391 *aPoint
.ContainerAsText(), WSType::NormalText
,
1399 template <typename EditorDOMPointType
>
1400 WSRunScanner::TextFragmentData::BoundaryData
WSRunScanner::TextFragmentData::
1401 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1402 const EditorDOMPointType
& aPoint
,
1403 const nsIContent
& aEditableBlockParentOrTopmostEditableInlineContent
,
1404 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1405 MOZ_ASSERT(aPoint
.IsSetAndValid());
1407 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer()) {
1408 // If the point is in a text node which is preformatted, we should return
1409 // the point as a visible character point.
1410 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1411 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1414 // If the text node is not preformatted, we should look for its preceding
1416 Maybe
<BoundaryData
> startInTextNode
=
1417 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint
,
1419 if (startInTextNode
.isSome()) {
1420 return startInTextNode
.ref();
1422 // The text node does not have visible character, let's keep scanning
1424 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1425 EditorDOMPoint(aPoint
.ContainerAsText(), 0),
1426 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1430 // Then, we need to check previous leaf node.
1431 nsIContent
* previousLeafContentOrBlock
=
1432 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
1433 aPoint
, aEditableBlockParentOrTopmostEditableInlineContent
,
1435 if (!previousLeafContentOrBlock
) {
1436 // no prior node means we exhausted
1437 // aEditableBlockParentOrTopmostEditableInlineContent
1438 // mReasonContent can be either a block element or any non-editable
1439 // content in this case.
1440 return BoundaryData(aPoint
,
1441 const_cast<nsIContent
&>(
1442 aEditableBlockParentOrTopmostEditableInlineContent
),
1443 WSType::CurrentBlockBoundary
, Preformatted::No
);
1446 if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock
)) {
1447 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1448 WSType::OtherBlockBoundary
, Preformatted::No
);
1451 if (!previousLeafContentOrBlock
->IsText() ||
1452 !previousLeafContentOrBlock
->IsEditable()) {
1453 // it's a break or a special node, like <img>, that is not a block and
1454 // not a break but still serves as a terminator to ws runs.
1455 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1456 previousLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1458 : WSType::SpecialContent
,
1462 if (!previousLeafContentOrBlock
->AsText()->TextLength()) {
1463 // If it's an empty text node, keep looking for its previous leaf content.
1464 // Note that even if the empty text node is preformatted, we should keep
1465 // looking for the previous one.
1466 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1467 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1468 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1472 if (EditorUtils::IsContentPreformatted(*previousLeafContentOrBlock
)) {
1473 // If the previous text node is preformatted and not empty, we should return
1474 // its end as found a visible character. Note that we stop scanning
1475 // collapsible white-spaces due to reaching preformatted non-empty text
1476 // node. I.e., the following text node might be not preformatted.
1477 return BoundaryData(EditorDOMPoint::AtEndOf(*previousLeafContentOrBlock
),
1478 *previousLeafContentOrBlock
, WSType::NormalText
,
1482 Maybe
<BoundaryData
> startInTextNode
=
1483 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1484 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock
->AsText()),
1486 if (startInTextNode
.isSome()) {
1487 return startInTextNode
.ref();
1490 // The text node does not have visible character, let's keep scanning
1492 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1493 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1494 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1499 template <typename EditorDOMPointType
>
1500 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1501 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1502 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1503 MOZ_ASSERT(aPoint
.IsSetAndValid());
1504 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1505 MOZ_DIAGNOSTIC_ASSERT(
1506 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1508 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1509 for (uint32_t i
= aPoint
.Offset(); i
< textFragment
.GetLength(); i
++) {
1510 char16_t ch
= textFragment
.CharAt(i
);
1511 if (nsCRT::IsAsciiSpace(ch
)) {
1515 if (ch
== HTMLEditUtils::kNBSP
) {
1517 aNBSPData
->NotifyNBSP(EditorDOMPointInText(aPoint
.ContainerAsText(), i
),
1518 NoBreakingSpaceData::Scanning::Forward
);
1523 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1524 *aPoint
.ContainerAsText(), WSType::NormalText
,
1532 template <typename EditorDOMPointType
>
1533 WSRunScanner::TextFragmentData::BoundaryData
1534 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1535 const EditorDOMPointType
& aPoint
,
1536 const nsIContent
& aEditableBlockParentOrTopmostEditableInlineContent
,
1537 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1538 MOZ_ASSERT(aPoint
.IsSetAndValid());
1540 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer()) {
1541 // If the point is in a text node which is preformatted, we should return
1542 // the point as a visible character point.
1543 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1544 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1547 // If the text node is not preformatted, we should look for inclusive
1549 Maybe
<BoundaryData
> endInTextNode
=
1550 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint
, aNBSPData
);
1551 if (endInTextNode
.isSome()) {
1552 return endInTextNode
.ref();
1554 // The text node does not have visible character, let's keep scanning
1556 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1557 EditorDOMPointInText::AtEndOf(*aPoint
.ContainerAsText()),
1558 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1562 // Then, we need to check next leaf node.
1563 nsIContent
* nextLeafContentOrBlock
=
1564 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1565 aPoint
, aEditableBlockParentOrTopmostEditableInlineContent
,
1567 if (!nextLeafContentOrBlock
) {
1568 // no next node means we exhausted
1569 // aEditableBlockParentOrTopmostEditableInlineContent
1570 // mReasonContent can be either a block element or any non-editable
1571 // content in this case.
1572 return BoundaryData(aPoint
,
1573 const_cast<nsIContent
&>(
1574 aEditableBlockParentOrTopmostEditableInlineContent
),
1575 WSType::CurrentBlockBoundary
, Preformatted::No
);
1578 if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock
)) {
1579 // we encountered a new block. therefore no more ws.
1580 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1581 WSType::OtherBlockBoundary
, Preformatted::No
);
1584 if (!nextLeafContentOrBlock
->IsText() ||
1585 !nextLeafContentOrBlock
->IsEditable()) {
1586 // we encountered a break or a special node, like <img>,
1587 // that is not a block and not a break but still
1588 // serves as a terminator to ws runs.
1589 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1590 nextLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1592 : WSType::SpecialContent
,
1596 if (!nextLeafContentOrBlock
->AsText()->TextFragment().GetLength()) {
1597 // If it's an empty text node, keep looking for its next leaf content.
1598 // Note that even if the empty text node is preformatted, we should keep
1599 // looking for the next one.
1600 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1601 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0),
1602 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1606 if (EditorUtils::IsContentPreformatted(*nextLeafContentOrBlock
)) {
1607 // If the next text node is preformatted and not empty, we should return
1608 // its start as found a visible character. Note that we stop scanning
1609 // collapsible white-spaces due to reaching preformatted non-empty text
1610 // node. I.e., the following text node might be not preformatted.
1611 return BoundaryData(EditorDOMPoint(nextLeafContentOrBlock
, 0),
1612 *nextLeafContentOrBlock
, WSType::NormalText
,
1616 Maybe
<BoundaryData
> endInTextNode
=
1617 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1618 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0), aNBSPData
);
1619 if (endInTextNode
.isSome()) {
1620 return endInTextNode
.ref();
1623 // The text node does not have visible character, let's keep scanning
1625 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1626 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock
->AsText()),
1627 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1631 const EditorDOMRange
&
1632 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
1633 if (mLeadingWhiteSpaceRange
.isSome()) {
1634 return mLeadingWhiteSpaceRange
.ref();
1637 // If it's preformatted or not start of line, the range is not invisible
1638 // leading white-spaces.
1639 if (!StartsFromHardLineBreak()) {
1640 mLeadingWhiteSpaceRange
.emplace();
1641 return mLeadingWhiteSpaceRange
.ref();
1644 // If there is no NBSP, all of the given range is leading white-spaces.
1645 // Note that this result may be collapsed if there is no leading white-spaces.
1646 if (!mNBSPData
.FoundNBSP()) {
1647 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1648 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1649 return mLeadingWhiteSpaceRange
.ref();
1652 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1654 // Even if the first NBSP is the start, i.e., there is no invisible leading
1655 // white-space, return collapsed range.
1656 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mNBSPData
.FirstPointRef());
1657 return mLeadingWhiteSpaceRange
.ref();
1660 const EditorDOMRange
&
1661 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
1662 if (mTrailingWhiteSpaceRange
.isSome()) {
1663 return mTrailingWhiteSpaceRange
.ref();
1666 // If it's preformatted or not immediately before block boundary, the range is
1667 // not invisible trailing white-spaces. Note that collapsible white-spaces
1668 // before a `<br>` element is visible.
1669 if (!EndsByBlockBoundary()) {
1670 mTrailingWhiteSpaceRange
.emplace();
1671 return mTrailingWhiteSpaceRange
.ref();
1674 // If there is no NBSP, all of the given range is trailing white-spaces.
1675 // Note that this result may be collapsed if there is no trailing white-
1677 if (!mNBSPData
.FoundNBSP()) {
1678 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1679 mTrailingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1680 return mTrailingWhiteSpaceRange
.ref();
1683 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1685 // If last NBSP is immediately before the end, there is no trailing white-
1687 if (mEnd
.PointRef().IsSet() &&
1688 mNBSPData
.LastPointRef().GetContainer() ==
1689 mEnd
.PointRef().GetContainer() &&
1690 mNBSPData
.LastPointRef().Offset() == mEnd
.PointRef().Offset() - 1) {
1691 mTrailingWhiteSpaceRange
.emplace();
1692 return mTrailingWhiteSpaceRange
.ref();
1695 // Otherwise, the may be some trailing white-spaces.
1696 MOZ_ASSERT(!mNBSPData
.LastPointRef().IsEndOfContainer());
1697 mTrailingWhiteSpaceRange
.emplace(mNBSPData
.LastPointRef().NextPoint(),
1699 return mTrailingWhiteSpaceRange
.ref();
1702 EditorDOMRangeInTexts
1703 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
1704 const EditorDOMRange
& aRange
) const {
1705 if (!aRange
.IsPositioned()) {
1706 return EditorDOMRangeInTexts();
1708 if (aRange
.Collapsed()) {
1709 // If collapsed, we can do nothing.
1710 return EditorDOMRangeInTexts();
1712 if (aRange
.IsInTextNodes()) {
1713 // Note that this may return a range which don't include any invisible
1714 // white-spaces due to empty text nodes.
1715 return aRange
.GetAsInTexts();
1718 EditorDOMPointInText firstPoint
=
1719 aRange
.StartRef().IsInTextNode()
1720 ? aRange
.StartRef().AsInText()
1721 : GetInclusiveNextEditableCharPoint(aRange
.StartRef());
1722 if (!firstPoint
.IsSet()) {
1723 return EditorDOMRangeInTexts();
1725 EditorDOMPointInText endPoint
;
1726 if (aRange
.EndRef().IsInTextNode()) {
1727 endPoint
= aRange
.EndRef().AsInText();
1729 // FYI: GetPreviousEditableCharPoint() returns last character's point
1730 // of preceding text node if it's not empty, but we need end of
1731 // the text node here.
1732 endPoint
= GetPreviousEditableCharPoint(aRange
.EndRef());
1733 if (endPoint
.IsSet() && endPoint
.IsAtLastContent()) {
1734 MOZ_ALWAYS_TRUE(endPoint
.AdvanceOffset());
1737 if (!endPoint
.IsSet() || firstPoint
== endPoint
) {
1738 return EditorDOMRangeInTexts();
1740 return EditorDOMRangeInTexts(firstPoint
, endPoint
);
1743 const WSRunScanner::VisibleWhiteSpacesData
&
1744 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
1745 if (mVisibleWhiteSpacesData
.isSome()) {
1746 return mVisibleWhiteSpacesData
.ref();
1749 if (IsPreformattedOrSurrondedByVisibleContent()) {
1750 VisibleWhiteSpacesData visibleWhiteSpaces
;
1751 if (mStart
.PointRef().IsSet()) {
1752 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1754 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1755 if (mEnd
.PointRef().IsSet()) {
1756 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1758 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1759 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1760 return mVisibleWhiteSpacesData
.ref();
1763 // If all of the range is invisible leading or trailing white-spaces,
1764 // there is no visible content.
1765 const EditorDOMRange
& leadingWhiteSpaceRange
=
1766 InvisibleLeadingWhiteSpaceRangeRef();
1767 const bool maybeHaveLeadingWhiteSpaces
=
1768 leadingWhiteSpaceRange
.StartRef().IsSet() ||
1769 leadingWhiteSpaceRange
.EndRef().IsSet();
1770 if (maybeHaveLeadingWhiteSpaces
&&
1771 leadingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1772 leadingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1773 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1774 return mVisibleWhiteSpacesData
.ref();
1776 const EditorDOMRange
& trailingWhiteSpaceRange
=
1777 InvisibleTrailingWhiteSpaceRangeRef();
1778 const bool maybeHaveTrailingWhiteSpaces
=
1779 trailingWhiteSpaceRange
.StartRef().IsSet() ||
1780 trailingWhiteSpaceRange
.EndRef().IsSet();
1781 if (maybeHaveTrailingWhiteSpaces
&&
1782 trailingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1783 trailingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1784 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1785 return mVisibleWhiteSpacesData
.ref();
1788 if (!StartsFromHardLineBreak()) {
1789 VisibleWhiteSpacesData visibleWhiteSpaces
;
1790 if (mStart
.PointRef().IsSet()) {
1791 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1793 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1794 if (!maybeHaveTrailingWhiteSpaces
) {
1795 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1796 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1797 mVisibleWhiteSpacesData
= Some(visibleWhiteSpaces
);
1798 return mVisibleWhiteSpacesData
.ref();
1800 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1801 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1803 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1804 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1805 return mVisibleWhiteSpacesData
.ref();
1808 MOZ_ASSERT(StartsFromHardLineBreak());
1809 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces
);
1811 VisibleWhiteSpacesData visibleWhiteSpaces
;
1812 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
1813 visibleWhiteSpaces
.SetStartPoint(leadingWhiteSpaceRange
.EndRef());
1815 visibleWhiteSpaces
.SetStartFromLeadingWhiteSpaces();
1816 if (!EndsByBlockBoundary()) {
1817 // then no trailing ws. this normal run ends the overall ws run.
1818 if (mEnd
.PointRef().IsSet()) {
1819 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1821 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1822 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1823 return mVisibleWhiteSpacesData
.ref();
1826 MOZ_ASSERT(EndsByBlockBoundary());
1828 if (!maybeHaveTrailingWhiteSpaces
) {
1829 // normal ws runs right up to adjacent block (nbsp next to block)
1830 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1831 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1832 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1833 return mVisibleWhiteSpacesData
.ref();
1836 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1837 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1839 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1840 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1841 return mVisibleWhiteSpacesData
.ref();
1845 nsresult
WhiteSpaceVisibilityKeeper::
1846 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1847 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
) {
1848 if (NS_WARN_IF(!aRangeToDelete
.IsPositionedAndValid()) ||
1849 NS_WARN_IF(!aRangeToDelete
.IsInContentNodes())) {
1850 return NS_ERROR_INVALID_ARG
;
1853 EditorDOMRange
rangeToDelete(aRangeToDelete
);
1854 bool mayBecomeUnexpectedDOMTree
= aHTMLEditor
.MayHaveMutationEventListeners(
1855 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
1856 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
1857 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
1858 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
);
1860 RefPtr
<Element
> editingHost
= aHTMLEditor
.GetActiveEditingHost();
1861 TextFragmentData
textFragmentDataAtStart(rangeToDelete
.StartRef(),
1863 TextFragmentData
textFragmentDataAtEnd(rangeToDelete
.EndRef(), editingHost
);
1864 ReplaceRangeData replaceRangeDataAtEnd
=
1865 textFragmentDataAtEnd
.GetReplaceRangeDataAtEndOfDeletionRange(
1866 textFragmentDataAtStart
);
1867 if (replaceRangeDataAtEnd
.IsSet() && !replaceRangeDataAtEnd
.Collapsed()) {
1868 MOZ_ASSERT(rangeToDelete
.EndRef().EqualsOrIsBefore(
1869 replaceRangeDataAtEnd
.EndRef()));
1870 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode(),
1871 replaceRangeDataAtEnd
.StartRef().EqualsOrIsBefore(
1872 rangeToDelete
.EndRef()));
1873 MOZ_ASSERT(rangeToDelete
.StartRef().EqualsOrIsBefore(
1874 replaceRangeDataAtEnd
.StartRef()));
1875 if (!replaceRangeDataAtEnd
.HasReplaceString()) {
1876 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1877 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1879 AutoEditorDOMPointChildInvalidator
lockOffsetOfStart(startToDelete
);
1880 AutoEditorDOMPointChildInvalidator
lockOffsetOfEnd(endToDelete
);
1881 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1883 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1885 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1886 replaceRangeDataAtEnd
.StartRef(), replaceRangeDataAtEnd
.EndRef(),
1887 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1888 if (NS_FAILED(rv
)) {
1890 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1894 if (mayBecomeUnexpectedDOMTree
&&
1895 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1896 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1897 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1898 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1900 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1901 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1903 MOZ_ASSERT(replaceRangeDataAtEnd
.RangeRef().IsInTextNodes());
1904 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1905 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1907 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1909 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1912 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
1913 aHTMLEditor
, replaceRangeDataAtEnd
.RangeRef().AsInTexts(),
1914 replaceRangeDataAtEnd
.ReplaceStringRef());
1915 if (NS_FAILED(rv
)) {
1917 "WhiteSpaceVisibilityKeeper::"
1918 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
1923 if (mayBecomeUnexpectedDOMTree
&&
1924 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1925 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1926 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1927 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1929 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1930 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1933 if (mayBecomeUnexpectedDOMTree
) {
1934 // If focus is changed by mutation event listeners, we should stop
1935 // handling this edit action.
1936 if (editingHost
!= aHTMLEditor
.GetActiveEditingHost()) {
1937 NS_WARNING("Active editing host was changed");
1938 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1940 if (!rangeToDelete
.IsInContentNodes()) {
1941 NS_WARNING("The modified range was not in content");
1942 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1944 // If the DOM tree might be changed by mutation event listeners, we
1945 // should retrieve the latest data for avoiding to delete/replace
1946 // unexpected range.
1947 textFragmentDataAtStart
=
1948 TextFragmentData(rangeToDelete
.StartRef(), editingHost
);
1949 textFragmentDataAtEnd
=
1950 TextFragmentData(rangeToDelete
.EndRef(), editingHost
);
1953 ReplaceRangeData replaceRangeDataAtStart
=
1954 textFragmentDataAtStart
.GetReplaceRangeDataAtStartOfDeletionRange(
1955 textFragmentDataAtEnd
);
1956 if (!replaceRangeDataAtStart
.IsSet() || replaceRangeDataAtStart
.Collapsed()) {
1959 if (!replaceRangeDataAtStart
.HasReplaceString()) {
1960 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1961 replaceRangeDataAtStart
.StartRef(), replaceRangeDataAtStart
.EndRef(),
1962 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1963 // XXX Should we validate the range for making this return
1964 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
1965 NS_WARNING_ASSERTION(
1967 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1970 MOZ_ASSERT(replaceRangeDataAtStart
.RangeRef().IsInTextNodes());
1971 nsresult rv
= WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
1972 aHTMLEditor
, replaceRangeDataAtStart
.RangeRef().AsInTexts(),
1973 replaceRangeDataAtStart
.ReplaceStringRef());
1974 // XXX Should we validate the range for making this return
1975 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
1976 NS_WARNING_ASSERTION(
1978 "WhiteSpaceVisibilityKeeper::"
1979 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() failed");
1984 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
1985 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const {
1986 const EditorDOMPoint
& startToDelete
=
1987 aTextFragmentDataAtStartToDelete
.ScanStartRef();
1988 const EditorDOMPoint
& endToDelete
= mScanStartPoint
;
1990 MOZ_ASSERT(startToDelete
.IsSetAndValid());
1991 MOZ_ASSERT(endToDelete
.IsSetAndValid());
1992 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1994 if (EndRef().EqualsOrIsBefore(endToDelete
)) {
1995 return ReplaceRangeData();
1998 // If deleting range is followed by invisible trailing white-spaces, we need
1999 // to remove it for making them not visible.
2000 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
2001 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete
);
2002 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
2003 if (invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
2004 return ReplaceRangeData();
2006 // XXX Why don't we remove all invisible white-spaces?
2007 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() == endToDelete
);
2008 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd
, u
""_ns
);
2011 if (IsPreformatted()) {
2012 return ReplaceRangeData();
2015 // If end of the deleting range is followed by visible white-spaces which
2016 // is not preformatted, we might need to replace the following ASCII
2017 // white-spaces with an NBSP.
2018 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtEnd
=
2019 VisibleWhiteSpacesDataRef();
2020 if (!nonPreformattedVisibleWhiteSpacesAtEnd
.IsInitialized()) {
2021 return ReplaceRangeData();
2023 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
=
2024 nonPreformattedVisibleWhiteSpacesAtEnd
.ComparePoint(endToDelete
);
2025 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2026 PointPosition::StartOfFragment
&&
2027 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2028 PointPosition::MiddleOfFragment
) {
2029 return ReplaceRangeData();
2031 // If start of deleting range follows white-spaces or end of delete
2032 // will be start of a line, the following text cannot start with an
2033 // ASCII white-space for keeping it visible.
2034 if (!aTextFragmentDataAtStartToDelete
2035 .FollowingContentMayBecomeFirstVisibleContent(startToDelete
)) {
2036 return ReplaceRangeData();
2038 EditorRawDOMPointInText nextCharOfStartOfEnd
=
2039 GetInclusiveNextEditableCharPoint(endToDelete
);
2040 if (!nextCharOfStartOfEnd
.IsSet() ||
2041 nextCharOfStartOfEnd
.IsEndOfContainer() ||
2042 !nextCharOfStartOfEnd
.IsCharASCIISpace() ||
2043 EditorUtils::IsContentPreformatted(
2044 *nextCharOfStartOfEnd
.ContainerAsText())) {
2045 return ReplaceRangeData();
2047 if (nextCharOfStartOfEnd
.IsStartOfContainer() ||
2048 nextCharOfStartOfEnd
.IsPreviousCharASCIISpace()) {
2049 nextCharOfStartOfEnd
=
2050 aTextFragmentDataAtStartToDelete
2051 .GetFirstASCIIWhiteSpacePointCollapsedTo(nextCharOfStartOfEnd
);
2053 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2054 aTextFragmentDataAtStartToDelete
.GetEndOfCollapsibleASCIIWhiteSpaces(
2055 nextCharOfStartOfEnd
);
2056 return ReplaceRangeData(nextCharOfStartOfEnd
,
2057 endOfCollapsibleASCIIWhiteSpaces
,
2058 nsDependentSubstring(&kNBSP
, 1));
2062 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
2063 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const {
2064 const EditorDOMPoint
& startToDelete
= mScanStartPoint
;
2065 const EditorDOMPoint
& endToDelete
=
2066 aTextFragmentDataAtEndToDelete
.ScanStartRef();
2068 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2069 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2070 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2072 if (startToDelete
.EqualsOrIsBefore(StartRef())) {
2073 return ReplaceRangeData();
2076 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
2077 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete
);
2079 // If deleting range follows invisible leading white-spaces, we need to
2080 // remove them for making them not visible.
2081 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
2082 if (invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
2083 return ReplaceRangeData();
2086 // XXX Why don't we remove all leading white-spaces?
2087 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart
, u
""_ns
);
2090 if (IsPreformatted()) {
2091 return ReplaceRangeData();
2094 // If start of the deleting range follows visible white-spaces which is not
2095 // preformatted, we might need to replace previous ASCII white-spaces with
2097 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtStart
=
2098 VisibleWhiteSpacesDataRef();
2099 if (!nonPreformattedVisibleWhiteSpacesAtStart
.IsInitialized()) {
2100 return ReplaceRangeData();
2103 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
=
2104 nonPreformattedVisibleWhiteSpacesAtStart
.ComparePoint(startToDelete
);
2105 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2106 PointPosition::MiddleOfFragment
&&
2107 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2108 PointPosition::EndOfFragment
) {
2109 return ReplaceRangeData();
2111 // If end of the deleting range is (was) followed by white-spaces or
2112 // previous character of start of deleting range will be immediately
2113 // before a block boundary, the text cannot ends with an ASCII white-space
2114 // for keeping it visible.
2115 if (!aTextFragmentDataAtEndToDelete
.PrecedingContentMayBecomeInvisible(
2117 return ReplaceRangeData();
2119 EditorRawDOMPointInText atPreviousCharOfStart
=
2120 GetPreviousEditableCharPoint(startToDelete
);
2121 if (!atPreviousCharOfStart
.IsSet() ||
2122 atPreviousCharOfStart
.IsEndOfContainer() ||
2123 !atPreviousCharOfStart
.IsCharASCIISpace() ||
2124 EditorUtils::IsContentPreformatted(
2125 *atPreviousCharOfStart
.ContainerAsText())) {
2126 return ReplaceRangeData();
2128 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2129 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2130 atPreviousCharOfStart
=
2131 GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2133 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2134 GetEndOfCollapsibleASCIIWhiteSpaces(atPreviousCharOfStart
);
2135 return ReplaceRangeData(atPreviousCharOfStart
,
2136 endOfCollapsibleASCIIWhiteSpaces
,
2137 nsDependentSubstring(&kNBSP
, 1));
2142 WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
2143 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
) {
2144 TextFragmentData
textFragmentDataAtSplitPoint(
2145 aPointToSplit
, aHTMLEditor
.GetActiveEditingHost());
2147 // used to prepare white-space sequence to be split across two blocks.
2148 // The main issue here is make sure white-spaces around the split point
2149 // doesn't end up becoming non-significant leading or trailing ws after
2151 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2152 textFragmentDataAtSplitPoint
.VisibleWhiteSpacesDataRef();
2153 if (!visibleWhiteSpaces
.IsInitialized()) {
2154 return NS_OK
; // No visible white-space sequence.
2157 PointPosition pointPositionWithVisibleWhiteSpaces
=
2158 visibleWhiteSpaces
.ComparePoint(aPointToSplit
);
2160 // XXX If we split white-space sequence, the following code modify the DOM
2161 // tree twice. This is not reasonable and the latter change may touch
2162 // wrong position. We should do this once.
2164 // If we insert block boundary to start or middle of the white-space sequence,
2165 // the character at the insertion point needs to be an NBSP.
2166 EditorDOMPoint
pointToSplit(aPointToSplit
);
2167 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::StartOfFragment
||
2168 pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
) {
2169 EditorRawDOMPointInText atNextCharOfStart
=
2170 textFragmentDataAtSplitPoint
.GetInclusiveNextEditableCharPoint(
2172 if (atNextCharOfStart
.IsSet() && !atNextCharOfStart
.IsEndOfContainer() &&
2173 atNextCharOfStart
.IsCharASCIISpace() &&
2174 !EditorUtils::IsContentPreformatted(
2175 *atNextCharOfStart
.ContainerAsText())) {
2176 // pointToSplit will be referred bellow so that we need to keep
2177 // it a valid point.
2178 AutoEditorDOMPointChildInvalidator
forgetChild(pointToSplit
);
2179 if (atNextCharOfStart
.IsStartOfContainer() ||
2180 atNextCharOfStart
.IsPreviousCharASCIISpace()) {
2182 textFragmentDataAtSplitPoint
2183 .GetFirstASCIIWhiteSpacePointCollapsedTo(atNextCharOfStart
);
2185 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2186 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2189 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2191 EditorDOMRangeInTexts(atNextCharOfStart
,
2192 endOfCollapsibleASCIIWhiteSpaces
),
2193 nsDependentSubstring(&kNBSP
, 1));
2194 if (NS_FAILED(rv
)) {
2196 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2203 // If we insert block boundary to middle of or end of the white-space
2204 // sequence, the previous character at the insertion point needs to be an
2206 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
||
2207 pointPositionWithVisibleWhiteSpaces
== PointPosition::EndOfFragment
) {
2208 EditorRawDOMPointInText atPreviousCharOfStart
=
2209 textFragmentDataAtSplitPoint
.GetPreviousEditableCharPoint(pointToSplit
);
2210 if (atPreviousCharOfStart
.IsSet() &&
2211 !atPreviousCharOfStart
.IsEndOfContainer() &&
2212 atPreviousCharOfStart
.IsCharASCIISpace() &&
2213 !EditorUtils::IsContentPreformatted(
2214 *atPreviousCharOfStart
.ContainerAsText())) {
2215 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2216 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2217 atPreviousCharOfStart
=
2218 textFragmentDataAtSplitPoint
2219 .GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2221 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2222 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2223 atPreviousCharOfStart
);
2225 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2227 EditorDOMRangeInTexts(atPreviousCharOfStart
,
2228 endOfCollapsibleASCIIWhiteSpaces
),
2229 nsDependentSubstring(&kNBSP
, 1));
2230 if (NS_FAILED(rv
)) {
2232 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2241 template <typename PT
, typename CT
>
2242 EditorDOMPointInText
2243 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint(
2244 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2245 MOZ_ASSERT(aPoint
.IsSetAndValid());
2247 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2248 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2249 return EditorDOMPointInText();
2252 EditorRawDOMPoint point
;
2253 if (nsIContent
* child
=
2254 aPoint
.CanContainerHaveChildren() ? aPoint
.GetChild() : nullptr) {
2255 nsIContent
* leafContent
= child
->HasChildren()
2256 ? HTMLEditUtils::GetFirstLeafChild(
2257 *child
, ChildBlockBoundary::Ignore
)
2259 if (NS_WARN_IF(!leafContent
)) {
2260 return EditorDOMPointInText();
2262 point
.Set(leafContent
, 0);
2267 // If it points a character in a text node, return it.
2268 // XXX For the performance, this does not check whether the container
2269 // is outside of our range.
2270 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2271 !point
.IsEndOfContainer()) {
2272 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset());
2275 if (point
.GetContainer() == GetEndReasonContent()) {
2276 return EditorDOMPointInText();
2279 NS_ASSERTION(EditorUtils::IsEditableContent(
2280 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2281 "Given content is not editable");
2283 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2284 "Given content is not an element and an orphan node");
2285 nsIContent
* editableBlockParentOrTopmotEditableInlineContent
=
2286 mScanStartPoint
.ContainerAsContent() &&
2287 EditorUtils::IsEditableContent(
2288 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2290 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2291 *mScanStartPoint
.ContainerAsContent())
2293 if (NS_WARN_IF(!editableBlockParentOrTopmotEditableInlineContent
)) {
2294 // Meaning that the container of `mScanStartPoint` is not editable.
2295 editableBlockParentOrTopmotEditableInlineContent
=
2296 mScanStartPoint
.ContainerAsContent();
2299 for (nsIContent
* nextContent
=
2300 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2301 *point
.ContainerAsContent(),
2302 *editableBlockParentOrTopmotEditableInlineContent
, mEditingHost
);
2304 nextContent
= HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2305 *nextContent
, *editableBlockParentOrTopmotEditableInlineContent
,
2307 if (!nextContent
->IsText() || !nextContent
->IsEditable()) {
2308 if (nextContent
== GetEndReasonContent()) {
2309 break; // Reached end of current runs.
2313 return EditorDOMPointInText(nextContent
->AsText(), 0);
2315 return EditorDOMPointInText();
2318 template <typename PT
, typename CT
>
2319 EditorDOMPointInText
2320 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint(
2321 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2322 MOZ_ASSERT(aPoint
.IsSetAndValid());
2324 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2325 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2326 return EditorDOMPointInText();
2329 EditorRawDOMPoint point
;
2330 if (nsIContent
* previousChild
= aPoint
.CanContainerHaveChildren()
2331 ? aPoint
.GetPreviousSiblingOfChild()
2333 nsIContent
* leafContent
=
2334 previousChild
->HasChildren()
2335 ? HTMLEditUtils::GetLastLeafChild(*previousChild
,
2336 ChildBlockBoundary::Ignore
)
2338 if (NS_WARN_IF(!leafContent
)) {
2339 return EditorDOMPointInText();
2341 point
.SetToEndOf(leafContent
);
2346 // If it points a character in a text node and it's not first character
2347 // in it, return its previous point.
2348 // XXX For the performance, this does not check whether the container
2349 // is outside of our range.
2350 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2351 !point
.IsStartOfContainer()) {
2352 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset() - 1);
2355 if (point
.GetContainer() == GetStartReasonContent()) {
2356 return EditorDOMPointInText();
2359 NS_ASSERTION(EditorUtils::IsEditableContent(
2360 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2361 "Given content is not editable");
2363 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2364 "Given content is not an element and an orphan node");
2365 nsIContent
* editableBlockParentOrTopmotEditableInlineContent
=
2366 mScanStartPoint
.ContainerAsContent() &&
2367 EditorUtils::IsEditableContent(
2368 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2370 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2371 *mScanStartPoint
.ContainerAsContent())
2373 if (NS_WARN_IF(!editableBlockParentOrTopmotEditableInlineContent
)) {
2374 // Meaning that the container of `mScanStartPoint` is not editable.
2375 editableBlockParentOrTopmotEditableInlineContent
=
2376 mScanStartPoint
.ContainerAsContent();
2379 for (nsIContent
* previousContent
=
2380 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2381 *point
.ContainerAsContent(),
2382 *editableBlockParentOrTopmotEditableInlineContent
, mEditingHost
);
2385 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2387 *editableBlockParentOrTopmotEditableInlineContent
,
2389 if (!previousContent
->IsText() || !previousContent
->IsEditable()) {
2390 if (previousContent
== GetStartReasonContent()) {
2391 break; // Reached start of current runs.
2395 return EditorDOMPointInText(
2396 previousContent
->AsText(),
2397 previousContent
->AsText()->TextLength()
2398 ? previousContent
->AsText()->TextLength() - 1
2401 return EditorDOMPointInText();
2405 template <typename EditorDOMPointType
>
2406 EditorDOMPointType
WSRunScanner::GetAfterLastVisiblePoint(
2407 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2408 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2409 return EditorDOMPointType::AtEndOf(aTextNode
);
2411 TextFragmentData
textFragmentData(
2412 EditorDOMPoint(&aTextNode
,
2413 aTextNode
.Length() ? aTextNode
.Length() - 1 : 0),
2415 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2416 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
2417 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2418 invisibleWhiteSpaceRange
.Collapsed()) {
2419 return EditorDOMPointType::AtEndOf(aTextNode
);
2421 return EditorDOMPointType(invisibleWhiteSpaceRange
.StartRef());
2425 template <typename EditorDOMPointType
>
2426 EditorDOMPointType
WSRunScanner::GetFirstVisiblePoint(
2427 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2428 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2429 return EditorDOMPointType(&aTextNode
, 0);
2431 TextFragmentData
textFragmentData(EditorDOMPoint(&aTextNode
, 0),
2433 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2434 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
2435 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2436 invisibleWhiteSpaceRange
.Collapsed()) {
2437 return EditorDOMPointType(&aTextNode
, 0);
2439 return EditorDOMPointType(invisibleWhiteSpaceRange
.EndRef());
2442 EditorDOMPointInText
2443 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
2444 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2445 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2446 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2447 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2448 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2449 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2450 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2452 // If it's not the last character in the text node, let's scan following
2453 // characters in it.
2454 if (!aPointAtASCIIWhiteSpace
.IsAtLastContent()) {
2455 Maybe
<uint32_t> nextVisibleCharOffset
=
2456 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2457 aPointAtASCIIWhiteSpace
);
2458 if (nextVisibleCharOffset
.isSome()) {
2459 // There is non-white-space character in it.
2460 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2461 nextVisibleCharOffset
.value());
2465 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
2466 // the following text nodes.
2467 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2469 EditorDOMPointInText afterLastWhiteSpace
=
2470 EditorDOMPointInText::AtEndOf(*aPointAtASCIIWhiteSpace
.ContainerAsText());
2471 for (EditorDOMPointInText atEndOfPreviousTextNode
= afterLastWhiteSpace
;;) {
2472 EditorDOMPointInText atStartOfNextTextNode
=
2473 GetInclusiveNextEditableCharPoint(atEndOfPreviousTextNode
);
2474 if (!atStartOfNextTextNode
.IsSet()) {
2475 // There is no more text nodes. Return end of the previous text node.
2476 return afterLastWhiteSpace
;
2479 // We can ignore empty text nodes (even if it's preformatted).
2480 if (atStartOfNextTextNode
.IsContainerEmpty()) {
2481 atEndOfPreviousTextNode
= atStartOfNextTextNode
;
2485 // If next node starts with non-white-space character or next node is
2486 // preformatted, return end of previous text node.
2487 if (!atStartOfNextTextNode
.IsCharASCIISpace() ||
2488 EditorUtils::IsContentPreformatted(
2489 *atStartOfNextTextNode
.ContainerAsText())) {
2490 return afterLastWhiteSpace
;
2493 // Otherwise, scan the text node.
2494 Maybe
<uint32_t> nextVisibleCharOffset
=
2495 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2496 atStartOfNextTextNode
);
2497 if (nextVisibleCharOffset
.isSome()) {
2498 return EditorDOMPointInText(atStartOfNextTextNode
.ContainerAsText(),
2499 nextVisibleCharOffset
.value());
2502 // The next text nodes ends with white-space too. Try next one.
2503 afterLastWhiteSpace
= atEndOfPreviousTextNode
=
2504 EditorDOMPointInText::AtEndOf(*atStartOfNextTextNode
.ContainerAsText());
2508 EditorDOMPointInText
2509 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
2510 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2511 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2512 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2513 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2514 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2515 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2516 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2518 // If there is some characters before it, scan it in the text node first.
2519 if (!aPointAtASCIIWhiteSpace
.IsStartOfContainer()) {
2520 uint32_t firstASCIIWhiteSpaceOffset
=
2521 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2522 aPointAtASCIIWhiteSpace
);
2523 if (firstASCIIWhiteSpaceOffset
) {
2524 // There is a non-white-space character in it.
2525 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2526 firstASCIIWhiteSpaceOffset
);
2530 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
2531 // the preceding text nodes.
2532 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2534 EditorDOMPointInText atLastWhiteSpace
=
2535 EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(), 0);
2536 for (EditorDOMPointInText atStartOfPreviousTextNode
= atLastWhiteSpace
;;) {
2537 EditorDOMPointInText atLastCharOfNextTextNode
=
2538 GetPreviousEditableCharPoint(atStartOfPreviousTextNode
);
2539 if (!atLastCharOfNextTextNode
.IsSet()) {
2540 // There is no more text nodes. Return end of last text node.
2541 return atLastWhiteSpace
;
2544 // We can ignore empty text nodes (even if it's preformatted).
2545 if (atLastCharOfNextTextNode
.IsContainerEmpty()) {
2546 atStartOfPreviousTextNode
= atLastCharOfNextTextNode
;
2550 // If next node ends with non-white-space character or next node is
2551 // preformatted, return start of previous text node.
2552 if (!atLastCharOfNextTextNode
.IsCharASCIISpace() ||
2553 EditorUtils::IsContentPreformatted(
2554 *atLastCharOfNextTextNode
.ContainerAsText())) {
2555 return atLastWhiteSpace
;
2558 // Otherwise, scan the text node.
2559 uint32_t firstASCIIWhiteSpaceOffset
=
2560 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2561 atLastCharOfNextTextNode
);
2562 if (firstASCIIWhiteSpaceOffset
) {
2563 return EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(),
2564 firstASCIIWhiteSpaceOffset
);
2567 // The next text nodes starts with white-space too. Try next one.
2568 atLastWhiteSpace
= atStartOfPreviousTextNode
=
2569 EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(), 0);
2574 nsresult
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2575 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
2576 const nsAString
& aReplaceString
) {
2577 MOZ_ASSERT(aRangeToReplace
.IsPositioned());
2578 MOZ_ASSERT(aRangeToReplace
.StartRef().IsSetAndValid());
2579 MOZ_ASSERT(aRangeToReplace
.EndRef().IsSetAndValid());
2580 MOZ_ASSERT(aRangeToReplace
.StartRef().IsBefore(aRangeToReplace
.EndRef()));
2582 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2583 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2584 MOZ_KnownLive(*aRangeToReplace
.StartRef().ContainerAsText()),
2585 aRangeToReplace
.StartRef().Offset(),
2586 aRangeToReplace
.InSameContainer()
2587 ? aRangeToReplace
.EndRef().Offset() -
2588 aRangeToReplace
.StartRef().Offset()
2589 : aRangeToReplace
.StartRef().ContainerAsText()->TextLength() -
2590 aRangeToReplace
.StartRef().Offset(),
2592 if (NS_FAILED(rv
)) {
2593 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2597 if (aRangeToReplace
.InSameContainer()) {
2601 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2602 EditorDOMPointInText::AtEndOf(
2603 *aRangeToReplace
.StartRef().ContainerAsText()),
2604 aRangeToReplace
.EndRef(),
2605 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2606 NS_WARNING_ASSERTION(
2608 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2612 char16_t
WSRunScanner::GetCharAt(Text
* aTextNode
, int32_t aOffset
) const {
2613 // return 0 if we can't get a char, for whatever reason
2614 if (NS_WARN_IF(!aTextNode
) || NS_WARN_IF(aOffset
< 0) ||
2615 NS_WARN_IF(aOffset
>=
2616 static_cast<int32_t>(aTextNode
->TextDataLength()))) {
2619 return aTextNode
->TextFragment().CharAt(aOffset
);
2623 template <typename EditorDOMPointType
>
2624 nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
2625 HTMLEditor
& aHTMLEditor
, const EditorDOMPointType
& aPoint
) {
2626 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
2627 TextFragmentData
textFragmentData(aPoint
, editingHost
);
2628 // this routine examines a run of ws and tries to get rid of some unneeded
2629 // nbsp's, replacing them with regular ascii space if possible. Keeping
2630 // things simple for now and just trying to fix up the trailing ws in the run.
2631 if (!textFragmentData
.FoundNoBreakingWhiteSpaces()) {
2635 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2636 textFragmentData
.VisibleWhiteSpacesDataRef();
2637 if (!visibleWhiteSpaces
.IsInitialized()) {
2641 // Remove this block if we ship Blink-compat white-space normalization.
2642 if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
2643 // now check that what is to the left of it is compatible with replacing
2645 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
=
2646 visibleWhiteSpaces
.EndRef();
2647 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2648 textFragmentData
.GetPreviousEditableCharPoint(
2649 atEndOfVisibleWhiteSpaces
);
2650 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2651 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2652 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2656 // now check that what is to the left of it is compatible with replacing
2658 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2659 textFragmentData
.GetPreviousEditableCharPoint(
2660 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2661 bool isPreviousCharASCIIWhiteSpace
=
2662 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2663 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2664 .IsEndOfContainer() &&
2665 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2666 .IsCharASCIISpace();
2667 bool maybeNBSPFollowingVisibleContent
=
2668 (atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2669 !isPreviousCharASCIIWhiteSpace
) ||
2670 (!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2671 (visibleWhiteSpaces
.StartsFromNormalText() ||
2672 visibleWhiteSpaces
.StartsFromSpecialContent()));
2673 bool followedByVisibleContentOrBRElement
= false;
2675 // If the NBSP follows a visible content or an ASCII white-space, i.e.,
2676 // unless NBSP is first character and start of a block, we may need to
2677 // insert <br> element and restore the NBSP to an ASCII white-space.
2678 if (maybeNBSPFollowingVisibleContent
|| isPreviousCharASCIIWhiteSpace
) {
2679 followedByVisibleContentOrBRElement
=
2680 visibleWhiteSpaces
.EndsByNormalText() ||
2681 visibleWhiteSpaces
.EndsBySpecialContent() ||
2682 visibleWhiteSpaces
.EndsByBRElement();
2683 // First, try to insert <br> element if NBSP is at end of a block.
2684 // XXX We should stop this if there is a visible content.
2685 if (visibleWhiteSpaces
.EndsByBlockBoundary() &&
2686 aPoint
.IsInContentNode()) {
2687 bool insertBRElement
=
2688 HTMLEditUtils::IsBlockElement(*aPoint
.ContainerAsContent());
2689 if (!insertBRElement
) {
2690 NS_ASSERTION(EditorUtils::IsEditableContent(
2691 *aPoint
.ContainerAsContent(), EditorType::HTML
),
2692 "Given content is not editable");
2694 aPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2695 "Given content is not an element and an orphan node");
2696 nsIContent
* blockParentOrTopmostEditableInlineContent
=
2697 EditorUtils::IsEditableContent(*aPoint
.ContainerAsContent(),
2700 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2701 *aPoint
.ContainerAsContent())
2703 insertBRElement
= blockParentOrTopmostEditableInlineContent
&&
2704 HTMLEditUtils::IsBlockElement(
2705 *blockParentOrTopmostEditableInlineContent
);
2707 if (insertBRElement
) {
2708 // We are at a block boundary. Insert a <br>. Why? Well, first note
2709 // that the br will have no visible effect since it is up against a
2710 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
2711 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
2712 // this <br> addition gets us is the ability to convert a trailing
2713 // nbsp to a space. Consider: |<body>foo. '</body>|, where '
2714 // represents selection. User types space attempting to put 2 spaces
2715 // after the end of their sentence. We used to do this as:
2716 // |<body>foo.  </body>| This caused problems with soft wrapping:
2717 // the nbsp would wrap to the next line, which looked attrocious. If
2718 // you try to do: |<body>foo.  </body>| instead, the trailing
2719 // space is invisible because it is against a block boundary. If you
2721 // |<body>foo.  </body>| then you get an even uglier soft
2722 // wrapping problem, where foo is on one line until you type the final
2723 // space, and then "foo " jumps down to the next line. Ugh. The
2724 // best way I can find out of this is to throw in a harmless <br>
2725 // here, which allows us to do: |<body>foo.  <br></body>|, which
2726 // doesn't cause foo to jump lines, doesn't cause spaces to show up at
2727 // the beginning of soft wrapped lines, and lets the user see 2 spaces
2728 // when they type 2 spaces.
2730 RefPtr
<Element
> brElement
=
2731 aHTMLEditor
.InsertBRElementWithTransaction(
2732 atEndOfVisibleWhiteSpaces
);
2733 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
2734 return NS_ERROR_EDITOR_DESTROYED
;
2737 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
2738 return NS_ERROR_FAILURE
;
2741 atPreviousCharOfEndOfVisibleWhiteSpaces
=
2742 textFragmentData
.GetPreviousEditableCharPoint(
2743 atEndOfVisibleWhiteSpaces
);
2744 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2745 textFragmentData
.GetPreviousEditableCharPoint(
2746 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2747 isPreviousCharASCIIWhiteSpace
=
2748 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2749 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2750 .IsEndOfContainer() &&
2751 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2752 .IsCharASCIISpace();
2753 followedByVisibleContentOrBRElement
= true;
2757 // Next, replace the NBSP with an ASCII white-space if it's surrounded
2758 // by visible contents (or immediately before a <br> element).
2759 if (maybeNBSPFollowingVisibleContent
&&
2760 followedByVisibleContentOrBRElement
) {
2761 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2762 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2764 *atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()),
2765 atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset(), 1, u
" "_ns
);
2766 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2767 "HTMLEditor::ReplaceTextWithTransaction() failed");
2771 // If the text node is not preformatted, and the NBSP is followed by a <br>
2772 // element and following (maybe multiple) ASCII spaces, remove the NBSP,
2773 // but inserts a NBSP before the spaces. This makes a line break
2774 // opportunity to wrap the line.
2775 // XXX This is different behavior from Blink. Blink generates pairs of
2776 // an NBSP and an ASCII white-space, but put NBSP at the end of the
2777 // sequence. We should follow the behavior for web-compat.
2778 if (maybeNBSPFollowingVisibleContent
|| !isPreviousCharASCIIWhiteSpace
||
2779 !followedByVisibleContentOrBRElement
||
2780 EditorUtils::IsContentPreformatted(
2781 *atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2782 .GetContainerAsText())) {
2786 // Currently, we're at an NBSP following an ASCII space, and we need to
2787 // replace them with `" "` for avoiding collapsing white-spaces.
2788 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2789 .IsEndOfContainer());
2790 EditorDOMPointInText atFirstASCIIWhiteSpace
=
2791 textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2792 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2793 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2794 uint32_t numberOfASCIIWhiteSpacesInStartNode
=
2795 atFirstASCIIWhiteSpace
.ContainerAsText() ==
2796 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2797 ? atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset() -
2798 atFirstASCIIWhiteSpace
.Offset()
2799 : atFirstASCIIWhiteSpace
.ContainerAsText()->Length() -
2800 atFirstASCIIWhiteSpace
.Offset();
2801 // Replace all preceding ASCII white-spaces **and** the NBSP.
2802 uint32_t replaceLengthInStartNode
=
2803 numberOfASCIIWhiteSpacesInStartNode
+
2804 (atFirstASCIIWhiteSpace
.ContainerAsText() ==
2805 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2808 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2809 MOZ_KnownLive(*atFirstASCIIWhiteSpace
.ContainerAsText()),
2810 atFirstASCIIWhiteSpace
.Offset(), replaceLengthInStartNode
,
2812 if (NS_FAILED(rv
)) {
2813 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2817 if (atFirstASCIIWhiteSpace
.GetContainer() ==
2818 atPreviousCharOfEndOfVisibleWhiteSpaces
.GetContainer()) {
2822 // We need to remove the following unnecessary ASCII white-spaces and
2823 // NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
2824 // into the start node.
2825 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2826 EditorDOMPointInText::AtEndOf(
2827 *atFirstASCIIWhiteSpace
.ContainerAsText()),
2828 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint(),
2829 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2830 NS_WARNING_ASSERTION(
2832 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2836 // XXX This is called when top-level edit sub-action handling ends for
2837 // 3 points at most. However, this is not compatible with Blink.
2838 // Blink touches white-space sequence which includes new character
2839 // or following white-space sequence of new <br> element or, if and
2840 // only if deleting range is followed by white-space sequence (i.e.,
2841 // not touched previous white-space sequence of deleting range).
2842 // This should be done when we change to make each edit action
2843 // handler directly normalize white-space sequence rather than
2844 // OnEndHandlingTopLevelEditSucAction().
2846 // First, check if the last character is an NBSP. Otherwise, we don't need
2847 // to do nothing here.
2848 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
= visibleWhiteSpaces
.EndRef();
2849 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2850 textFragmentData
.GetPreviousEditableCharPoint(atEndOfVisibleWhiteSpaces
);
2851 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2852 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2853 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2857 // Next, consider the range to collapse ASCII white-spaces before there.
2858 EditorDOMPointInText startToDelete
, endToDelete
;
2860 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2861 textFragmentData
.GetPreviousEditableCharPoint(
2862 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2863 // If there are some preceding ASCII white-spaces, we need to treat them
2864 // as one white-space. I.e., we need to collapse them.
2865 if (atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP() &&
2866 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2867 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2868 .IsCharASCIISpace()) {
2869 startToDelete
= textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2870 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2871 endToDelete
= atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
;
2873 // Otherwise, we don't need to remove any white-spaces, but we may need
2874 // to normalize the white-space sequence containing the previous NBSP.
2876 startToDelete
= endToDelete
=
2877 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint();
2880 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2881 Result
<EditorDOMPoint
, nsresult
> result
=
2882 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2883 startToDelete
, endToDelete
,
2884 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
,
2885 HTMLEditor::DeleteDirection::Forward
);
2886 NS_WARNING_ASSERTION(
2888 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2889 return result
.isErr() ? result
.unwrapErr() : NS_OK
;
2892 EditorDOMPointInText
WSRunScanner::TextFragmentData::
2893 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
2894 const EditorDOMPoint
& aPointToInsert
) const {
2895 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
2896 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
2897 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2898 PointPosition::MiddleOfFragment
||
2899 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2900 PointPosition::EndOfFragment
,
2901 "Previous char of aPoint should be in the visible white-spaces");
2903 // Try to change an NBSP to a space, if possible, just to prevent NBSP
2904 // proliferation. This routine is called when we are about to make this
2905 // point in the ws abut an inserted break or text, so we don't have to worry
2906 // about what is after it. What is after it now will end up after the
2908 EditorDOMPointInText atPreviousChar
=
2909 GetPreviousEditableCharPoint(aPointToInsert
);
2910 if (!atPreviousChar
.IsSet() || atPreviousChar
.IsEndOfContainer() ||
2911 !atPreviousChar
.IsCharNBSP() ||
2912 EditorUtils::IsContentPreformatted(*atPreviousChar
.ContainerAsText())) {
2913 return EditorDOMPointInText();
2916 EditorDOMPointInText atPreviousCharOfPreviousChar
=
2917 GetPreviousEditableCharPoint(atPreviousChar
);
2918 if (atPreviousCharOfPreviousChar
.IsSet()) {
2919 // If the previous char is in different text node and it's preformatted,
2920 // we shouldn't touch it.
2921 if (atPreviousChar
.ContainerAsText() !=
2922 atPreviousCharOfPreviousChar
.ContainerAsText() &&
2923 EditorUtils::IsContentPreformatted(
2924 *atPreviousCharOfPreviousChar
.ContainerAsText())) {
2925 return EditorDOMPointInText();
2927 // If the previous char of the NBSP at previous position of aPointToInsert
2928 // is an ASCII white-space, we don't need to replace it with same character.
2929 if (!atPreviousCharOfPreviousChar
.IsEndOfContainer() &&
2930 atPreviousCharOfPreviousChar
.IsCharASCIISpace()) {
2931 return EditorDOMPointInText();
2933 return atPreviousChar
;
2936 // If previous content of the NBSP is block boundary, we cannot replace the
2937 // NBSP with an ASCII white-space to keep it rendered.
2938 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2939 VisibleWhiteSpacesDataRef();
2940 if (!visibleWhiteSpaces
.StartsFromNormalText() &&
2941 !visibleWhiteSpaces
.StartsFromSpecialContent()) {
2942 return EditorDOMPointInText();
2944 return atPreviousChar
;
2947 EditorDOMPointInText
WSRunScanner::TextFragmentData::
2948 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
2949 const EditorDOMPoint
& aPointToInsert
) const {
2950 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
2951 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
2952 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2953 PointPosition::StartOfFragment
||
2954 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2955 PointPosition::MiddleOfFragment
,
2956 "Inclusive next char of aPointToInsert should be in the visible "
2959 // Try to change an nbsp to a space, if possible, just to prevent nbsp
2960 // proliferation This routine is called when we are about to make this point
2961 // in the ws abut an inserted text, so we don't have to worry about what is
2962 // before it. What is before it now will end up before the inserted text.
2963 EditorDOMPointInText atNextChar
=
2964 GetInclusiveNextEditableCharPoint(aPointToInsert
);
2965 if (!atNextChar
.IsSet() || NS_WARN_IF(atNextChar
.IsEndOfContainer()) ||
2966 !atNextChar
.IsCharNBSP() ||
2967 EditorUtils::IsContentPreformatted(*atNextChar
.ContainerAsText())) {
2968 return EditorDOMPointInText();
2971 EditorDOMPointInText atNextCharOfNextCharOfNBSP
=
2972 GetInclusiveNextEditableCharPoint(atNextChar
.NextPoint());
2973 if (atNextCharOfNextCharOfNBSP
.IsSet()) {
2974 // If the next char is in different text node and it's preformatted,
2975 // we shouldn't touch it.
2976 if (atNextChar
.ContainerAsText() !=
2977 atNextCharOfNextCharOfNBSP
.ContainerAsText() &&
2978 EditorUtils::IsContentPreformatted(
2979 *atNextCharOfNextCharOfNBSP
.ContainerAsText())) {
2980 return EditorDOMPointInText();
2982 // If following character of an NBSP is an ASCII white-space, we don't
2983 // need to replace it with same character.
2984 if (!atNextCharOfNextCharOfNBSP
.IsEndOfContainer() &&
2985 atNextCharOfNextCharOfNBSP
.IsCharASCIISpace()) {
2986 return EditorDOMPointInText();
2991 // If the NBSP is last character in the hard line, we don't need to
2992 // replace it because it's required to render multiple white-spaces.
2993 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2994 VisibleWhiteSpacesDataRef();
2995 if (!visibleWhiteSpaces
.EndsByNormalText() &&
2996 !visibleWhiteSpaces
.EndsBySpecialContent() &&
2997 !visibleWhiteSpaces
.EndsByBRElement()) {
2998 return EditorDOMPointInText();
3005 nsresult
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
3006 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
3007 MOZ_ASSERT(aPoint
.IsSet());
3008 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3009 TextFragmentData
textFragmentData(aPoint
, editingHost
);
3010 const EditorDOMRange
& leadingWhiteSpaceRange
=
3011 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
3012 // XXX Getting trailing white-space range now must be wrong because
3013 // mutation event listener may invalidate it.
3014 const EditorDOMRange
& trailingWhiteSpaceRange
=
3015 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
3016 DebugOnly
<bool> leadingWhiteSpacesDeleted
= false;
3017 if (leadingWhiteSpaceRange
.IsPositioned() &&
3018 !leadingWhiteSpaceRange
.Collapsed()) {
3019 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3020 leadingWhiteSpaceRange
.StartRef(), leadingWhiteSpaceRange
.EndRef(),
3021 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3022 if (NS_FAILED(rv
)) {
3024 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3025 "delete leading white-spaces");
3028 leadingWhiteSpacesDeleted
= true;
3030 if (trailingWhiteSpaceRange
.IsPositioned() &&
3031 !trailingWhiteSpaceRange
.Collapsed() &&
3032 leadingWhiteSpaceRange
!= trailingWhiteSpaceRange
) {
3033 NS_ASSERTION(!leadingWhiteSpacesDeleted
,
3034 "We're trying to remove trailing white-spaces with maybe "
3036 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3037 trailingWhiteSpaceRange
.StartRef(), trailingWhiteSpaceRange
.EndRef(),
3038 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3039 if (NS_FAILED(rv
)) {
3041 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3042 "delete trailing white-spaces");
3049 /*****************************************************************************
3050 * Implementation for new white-space normalizer
3051 *****************************************************************************/
3054 EditorDOMRangeInTexts
3055 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3056 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
) {
3057 // Corresponding to handling invisible white-spaces part of
3058 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
3059 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
3061 MOZ_ASSERT(aStart
.ScanStartRef().IsSetAndValid());
3062 MOZ_ASSERT(aEnd
.ScanStartRef().IsSetAndValid());
3063 MOZ_ASSERT(aStart
.ScanStartRef().EqualsOrIsBefore(aEnd
.ScanStartRef()));
3064 MOZ_ASSERT(aStart
.ScanStartRef().IsInTextNode());
3065 MOZ_ASSERT(aEnd
.ScanStartRef().IsInTextNode());
3067 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
3068 // `GetReplaceRangeDataAtStartOfDeletionRange()` use
3069 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
3070 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
3071 // However, they are really odd as mentioned with "XXX" comments
3072 // in them. For the new white-space normalizer, we need to treat
3073 // invisible white-spaces stricter because the legacy path handles
3074 // white-spaces multiple times (e.g., calling `HTMLEditor::
3075 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
3076 // the bug, but in the new path, we should stop doing same things
3077 // multiple times for both performance and footprint. Therefore,
3078 // even though the result might be different in some edge cases,
3079 // we should use clean path for now. Perhaps, we should fix the odd
3080 // cases before shipping `beforeinput` event in release channel.
3082 const EditorDOMRange
& invisibleLeadingWhiteSpaceRange
=
3083 aStart
.InvisibleLeadingWhiteSpaceRangeRef();
3084 const EditorDOMRange
& invisibleTrailingWhiteSpaceRange
=
3085 aEnd
.InvisibleTrailingWhiteSpaceRangeRef();
3086 const bool hasInvisibleLeadingWhiteSpaces
=
3087 invisibleLeadingWhiteSpaceRange
.IsPositioned() &&
3088 !invisibleLeadingWhiteSpaceRange
.Collapsed();
3089 const bool hasInvisibleTrailingWhiteSpaces
=
3090 invisibleLeadingWhiteSpaceRange
!= invisibleTrailingWhiteSpaceRange
&&
3091 invisibleTrailingWhiteSpaceRange
.IsPositioned() &&
3092 !invisibleTrailingWhiteSpaceRange
.Collapsed();
3094 EditorDOMRangeInTexts
result(aStart
.ScanStartRef().AsInText(),
3095 aEnd
.ScanStartRef().AsInText());
3096 MOZ_ASSERT(result
.IsPositionedAndValid());
3097 if (!hasInvisibleLeadingWhiteSpaces
&& !hasInvisibleTrailingWhiteSpaces
) {
3102 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3103 invisibleLeadingWhiteSpaceRange
.StartRef().IsBefore(
3104 invisibleTrailingWhiteSpaceRange
.StartRef()));
3105 const EditorDOMPoint
& aroundFirstInvisibleWhiteSpace
=
3106 hasInvisibleLeadingWhiteSpaces
3107 ? invisibleLeadingWhiteSpaceRange
.StartRef()
3108 : invisibleTrailingWhiteSpaceRange
.StartRef();
3109 if (aroundFirstInvisibleWhiteSpace
.IsBefore(result
.StartRef())) {
3110 if (aroundFirstInvisibleWhiteSpace
.IsInTextNode()) {
3111 result
.SetStart(aroundFirstInvisibleWhiteSpace
.AsInText());
3112 MOZ_ASSERT(result
.IsPositionedAndValid());
3114 const EditorDOMPointInText atFirstInvisibleWhiteSpace
=
3115 hasInvisibleLeadingWhiteSpaces
3116 ? aStart
.GetInclusiveNextEditableCharPoint(
3117 aroundFirstInvisibleWhiteSpace
)
3118 : aEnd
.GetInclusiveNextEditableCharPoint(
3119 aroundFirstInvisibleWhiteSpace
);
3120 MOZ_ASSERT(atFirstInvisibleWhiteSpace
.IsSet());
3122 atFirstInvisibleWhiteSpace
.EqualsOrIsBefore(result
.StartRef()));
3123 result
.SetStart(atFirstInvisibleWhiteSpace
);
3124 MOZ_ASSERT(result
.IsPositionedAndValid());
3128 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3129 invisibleLeadingWhiteSpaceRange
.EndRef().IsBefore(
3130 invisibleTrailingWhiteSpaceRange
.EndRef()));
3131 const EditorDOMPoint
& afterLastInvisibleWhiteSpace
=
3132 hasInvisibleTrailingWhiteSpaces
3133 ? invisibleTrailingWhiteSpaceRange
.EndRef()
3134 : invisibleLeadingWhiteSpaceRange
.EndRef();
3135 if (afterLastInvisibleWhiteSpace
.EqualsOrIsBefore(result
.EndRef())) {
3136 MOZ_ASSERT(result
.IsPositionedAndValid());
3139 if (afterLastInvisibleWhiteSpace
.IsInTextNode()) {
3140 result
.SetEnd(afterLastInvisibleWhiteSpace
.AsInText());
3141 MOZ_ASSERT(result
.IsPositionedAndValid());
3144 const EditorDOMPointInText atLastInvisibleWhiteSpace
=
3145 hasInvisibleTrailingWhiteSpaces
3146 ? aEnd
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
)
3147 : aStart
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
);
3148 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsSet());
3149 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsContainerEmpty() ||
3150 atLastInvisibleWhiteSpace
.IsAtLastContent());
3151 MOZ_ASSERT(result
.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace
));
3152 result
.SetEnd(atLastInvisibleWhiteSpace
.IsEndOfContainer()
3153 ? atLastInvisibleWhiteSpace
3154 : atLastInvisibleWhiteSpace
.NextPoint());
3155 MOZ_ASSERT(result
.IsPositionedAndValid());
3160 Result
<EditorDOMRangeInTexts
, nsresult
>
3161 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(const HTMLEditor
& aHTMLEditor
,
3162 const EditorDOMPoint
& aPoint
) {
3163 // Corresponding to computing delete range part of
3164 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
3165 MOZ_ASSERT(aPoint
.IsSetAndValid());
3167 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3168 TextFragmentData
textFragmentDataAtCaret(aPoint
, editingHost
);
3169 EditorDOMPointInText atPreviousChar
=
3170 textFragmentDataAtCaret
.GetPreviousEditableCharPoint(aPoint
);
3171 if (!atPreviousChar
.IsSet()) {
3172 return EditorDOMRangeInTexts(); // There is no content in the block.
3175 // XXX When previous char point is in an empty text node, we do nothing,
3176 // but this must look odd from point of user view. We should delete
3177 // something before aPoint.
3178 if (atPreviousChar
.IsEndOfContainer()) {
3179 return EditorDOMRangeInTexts();
3182 // Extend delete range if previous char is a low surrogate following
3183 // a high surrogate.
3184 EditorDOMPointInText atNextChar
= atPreviousChar
.NextPoint();
3185 if (!atPreviousChar
.IsStartOfContainer()) {
3186 if (atPreviousChar
.IsCharLowSurrogateFollowingHighSurrogate()) {
3187 atPreviousChar
= atPreviousChar
.PreviousPoint();
3189 // If caret is in middle of a surrogate pair, delete the surrogate pair
3191 else if (atPreviousChar
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3192 atNextChar
= atNextChar
.NextPoint();
3196 // If the text node is preformatted, just remove the previous character.
3197 if (textFragmentDataAtCaret
.IsPreformatted()) {
3198 return EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3201 // If previous char is an ASCII white-spaces, delete all adjcent ASCII
3203 EditorDOMRangeInTexts rangeToDelete
;
3204 if (atPreviousChar
.IsCharASCIISpace()) {
3205 EditorDOMPointInText startToDelete
=
3206 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3208 if (!startToDelete
.IsSet()) {
3210 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3211 return Err(NS_ERROR_FAILURE
);
3213 EditorDOMPointInText endToDelete
=
3214 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(
3216 if (!endToDelete
.IsSet()) {
3217 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3218 return Err(NS_ERROR_FAILURE
);
3220 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3222 // if previous char is not an ASCII white-space, remove it.
3224 rangeToDelete
= EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3227 // If there is no removable and visible content, we should do nothing.
3228 if (rangeToDelete
.Collapsed()) {
3229 return EditorDOMRangeInTexts();
3232 // And also delete invisible white-spaces if they become visible.
3233 TextFragmentData textFragmentDataAtStart
=
3234 rangeToDelete
.StartRef() != aPoint
3235 ? TextFragmentData(rangeToDelete
.StartRef(), editingHost
)
3236 : textFragmentDataAtCaret
;
3237 TextFragmentData textFragmentDataAtEnd
=
3238 rangeToDelete
.EndRef() != aPoint
3239 ? TextFragmentData(rangeToDelete
.EndRef(), editingHost
)
3240 : textFragmentDataAtCaret
;
3241 EditorDOMRangeInTexts extendedRangeToDelete
=
3242 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3243 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3244 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3245 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3250 Result
<EditorDOMRangeInTexts
, nsresult
>
3251 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
3252 const HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
3253 // Corresponding to computing delete range part of
3254 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
3255 MOZ_ASSERT(aPoint
.IsSetAndValid());
3257 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3258 TextFragmentData
textFragmentDataAtCaret(aPoint
, editingHost
);
3259 EditorDOMPointInText atCaret
=
3260 textFragmentDataAtCaret
.GetInclusiveNextEditableCharPoint(aPoint
);
3261 if (!atCaret
.IsSet()) {
3262 return EditorDOMRangeInTexts(); // There is no content in the block.
3264 // If caret is in middle of a surrogate pair, we should remove next
3265 // character (blink-compat).
3266 if (!atCaret
.IsEndOfContainer() &&
3267 atCaret
.IsCharLowSurrogateFollowingHighSurrogate()) {
3268 atCaret
= atCaret
.NextPoint();
3271 // XXX When next char point is in an empty text node, we do nothing,
3272 // but this must look odd from point of user view. We should delete
3273 // something after aPoint.
3274 if (atCaret
.IsEndOfContainer()) {
3275 return EditorDOMRangeInTexts();
3278 // Extend delete range if previous char is a low surrogate following
3279 // a high surrogate.
3280 EditorDOMPointInText atNextChar
= atCaret
.NextPoint();
3281 if (atCaret
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3282 atNextChar
= atNextChar
.NextPoint();
3285 // If the text node is preformatted, just remove the previous character.
3286 if (textFragmentDataAtCaret
.IsPreformatted()) {
3287 return EditorDOMRangeInTexts(atCaret
, atNextChar
);
3290 // If next char is an ASCII whitespaces, delete all adjcent ASCII
3292 EditorDOMRangeInTexts rangeToDelete
;
3293 if (atCaret
.IsCharASCIISpace()) {
3294 EditorDOMPointInText startToDelete
=
3295 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3297 if (!startToDelete
.IsSet()) {
3299 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3300 return Err(NS_ERROR_FAILURE
);
3302 EditorDOMPointInText endToDelete
=
3303 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(atCaret
);
3304 if (!endToDelete
.IsSet()) {
3305 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3306 return Err(NS_ERROR_FAILURE
);
3308 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3310 // if next char is not an ASCII white-space, remove it.
3312 rangeToDelete
= EditorDOMRangeInTexts(atCaret
, atNextChar
);
3315 // If there is no removable and visible content, we should do nothing.
3316 if (rangeToDelete
.Collapsed()) {
3317 return EditorDOMRangeInTexts();
3320 // And also delete invisible white-spaces if they become visible.
3321 TextFragmentData textFragmentDataAtStart
=
3322 rangeToDelete
.StartRef() != aPoint
3323 ? TextFragmentData(rangeToDelete
.StartRef(), editingHost
)
3324 : textFragmentDataAtCaret
;
3325 TextFragmentData textFragmentDataAtEnd
=
3326 rangeToDelete
.EndRef() != aPoint
3327 ? TextFragmentData(rangeToDelete
.EndRef(), editingHost
)
3328 : textFragmentDataAtCaret
;
3329 EditorDOMRangeInTexts extendedRangeToDelete
=
3330 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3331 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3332 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3333 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3338 EditorDOMRange
WSRunScanner::GetRangesForDeletingAtomicContent(
3339 const HTMLEditor
& aHTMLEditor
, const nsIContent
& aAtomicContent
) {
3340 if (aAtomicContent
.IsHTMLElement(nsGkAtoms::br
)) {
3341 // Preceding white-spaces should be preserved, but the following
3342 // white-spaces should be invisible around `<br>` element.
3343 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3344 TextFragmentData
textFragmentDataAfterBRElement(
3345 EditorDOMPoint::After(aAtomicContent
), editingHost
);
3346 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3347 textFragmentDataAfterBRElement
.GetNonCollapsedRangeInTexts(
3348 textFragmentDataAfterBRElement
3349 .InvisibleLeadingWhiteSpaceRangeRef());
3350 return followingInvisibleWhiteSpaces
.IsPositioned() &&
3351 !followingInvisibleWhiteSpaces
.Collapsed()
3353 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3354 followingInvisibleWhiteSpaces
.EndRef())
3356 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3357 EditorDOMPoint::After(aAtomicContent
));
3360 if (!HTMLEditUtils::IsBlockElement(aAtomicContent
)) {
3361 // Both preceding and following white-spaces around it should be preserved
3362 // around inline elements like `<img>`.
3363 return EditorDOMRange(
3364 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3365 EditorDOMPoint::After(aAtomicContent
));
3368 // Both preceding and following white-spaces can be invisible around a
3370 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3371 TextFragmentData
textFragmentDataBeforeAtomicContent(
3372 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)), editingHost
);
3373 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces
=
3374 textFragmentDataBeforeAtomicContent
.GetNonCollapsedRangeInTexts(
3375 textFragmentDataBeforeAtomicContent
3376 .InvisibleTrailingWhiteSpaceRangeRef());
3377 TextFragmentData
textFragmentDataAfterAtomicContent(
3378 EditorDOMPoint::After(aAtomicContent
), editingHost
);
3379 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3380 textFragmentDataAfterAtomicContent
.GetNonCollapsedRangeInTexts(
3381 textFragmentDataAfterAtomicContent
3382 .InvisibleLeadingWhiteSpaceRangeRef());
3383 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet() &&
3384 followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3385 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3386 followingInvisibleWhiteSpaces
.EndRef());
3388 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet()) {
3389 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3390 EditorDOMPoint::After(aAtomicContent
));
3392 if (followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3393 return EditorDOMRange(
3394 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3395 followingInvisibleWhiteSpaces
.EndRef());
3397 return EditorDOMRange(
3398 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3399 EditorDOMPoint::After(aAtomicContent
));
3403 EditorDOMRange
WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
3404 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
3405 const Element
& aRightBlockElement
,
3406 const EditorDOMPoint
& aPointContainingTheOtherBlock
) {
3407 MOZ_ASSERT(&aLeftBlockElement
!= &aRightBlockElement
);
3409 aPointContainingTheOtherBlock
.IsSet(),
3410 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
||
3411 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
);
3413 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
,
3414 aRightBlockElement
.IsInclusiveDescendantOf(
3415 aPointContainingTheOtherBlock
.GetChild()));
3417 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
,
3418 aLeftBlockElement
.IsInclusiveDescendantOf(
3419 aPointContainingTheOtherBlock
.GetChild()));
3421 !aPointContainingTheOtherBlock
.IsSet(),
3422 !aRightBlockElement
.IsInclusiveDescendantOf(&aLeftBlockElement
));
3424 !aPointContainingTheOtherBlock
.IsSet(),
3425 !aLeftBlockElement
.IsInclusiveDescendantOf(&aRightBlockElement
));
3426 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock
.IsSet(),
3427 EditorRawDOMPoint(const_cast<Element
*>(&aLeftBlockElement
))
3428 .IsBefore(EditorRawDOMPoint(
3429 const_cast<Element
*>(&aRightBlockElement
))));
3431 const Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3433 EditorDOMRange range
;
3434 // Include trailing invisible white-spaces in aLeftBlockElement.
3435 TextFragmentData
textFragmentDataAtEndOfLeftBlockElement(
3436 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
3437 ? aPointContainingTheOtherBlock
3438 : EditorDOMPoint::AtEndOf(const_cast<Element
&>(aLeftBlockElement
)),
3440 if (textFragmentDataAtEndOfLeftBlockElement
.StartsFromBRElement() &&
3441 !aHTMLEditor
.IsVisibleBRElement(
3442 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr())) {
3443 // If the left block element ends with an invisible `<br>` element,
3444 // it'll be deleted (and it means there is no invisible trailing
3445 // white-spaces). Therefore, the range should start from the invisible
3447 range
.SetStart(EditorDOMPoint(
3448 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr()));
3450 const EditorDOMRange
& trailingWhiteSpaceRange
=
3451 textFragmentDataAtEndOfLeftBlockElement
3452 .InvisibleTrailingWhiteSpaceRangeRef();
3453 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
3454 range
.SetStart(trailingWhiteSpaceRange
.StartRef());
3456 range
.SetStart(textFragmentDataAtEndOfLeftBlockElement
.ScanStartRef());
3459 // Include leading invisible white-spaces in aRightBlockElement.
3460 TextFragmentData
textFragmentDataAtStartOfRightBlockElement(
3461 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
&&
3462 !aPointContainingTheOtherBlock
.IsEndOfContainer()
3463 ? aPointContainingTheOtherBlock
.NextPoint()
3464 : EditorDOMPoint(const_cast<Element
*>(&aRightBlockElement
), 0),
3466 const EditorDOMRange
& leadingWhiteSpaceRange
=
3467 textFragmentDataAtStartOfRightBlockElement
3468 .InvisibleLeadingWhiteSpaceRangeRef();
3469 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
3470 range
.SetEnd(leadingWhiteSpaceRange
.EndRef());
3472 range
.SetEnd(textFragmentDataAtStartOfRightBlockElement
.ScanStartRef());
3479 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3480 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
) {
3481 MOZ_ASSERT(aRange
.IsPositionedAndValid());
3482 MOZ_ASSERT(aRange
.EndRef().IsSetAndValid());
3483 MOZ_ASSERT(aRange
.StartRef().IsSetAndValid());
3485 const Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3487 EditorDOMRange result
;
3488 TextFragmentData
textFragmentDataAtStart(aRange
.StartRef(), editingHost
);
3489 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart
=
3490 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3491 textFragmentDataAtStart
.InvisibleLeadingWhiteSpaceRangeRef());
3492 if (invisibleLeadingWhiteSpacesAtStart
.IsPositioned() &&
3493 !invisibleLeadingWhiteSpacesAtStart
.Collapsed()) {
3494 result
.SetStart(invisibleLeadingWhiteSpacesAtStart
.StartRef());
3496 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart
=
3497 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3498 textFragmentDataAtStart
.InvisibleTrailingWhiteSpaceRangeRef());
3499 if (invisibleTrailingWhiteSpacesAtStart
.IsPositioned() &&
3500 !invisibleTrailingWhiteSpacesAtStart
.Collapsed()) {
3502 invisibleTrailingWhiteSpacesAtStart
.StartRef().EqualsOrIsBefore(
3503 aRange
.StartRef()));
3504 result
.SetStart(invisibleTrailingWhiteSpacesAtStart
.StartRef());
3506 // If there is no invisible white-space and the line starts with a
3507 // text node, shrink the range to start of the text node.
3508 else if (!aRange
.StartRef().IsInTextNode() &&
3509 textFragmentDataAtStart
.StartsFromBlockBoundary() &&
3510 textFragmentDataAtStart
.EndRef().IsInTextNode()) {
3511 result
.SetStart(textFragmentDataAtStart
.EndRef());
3514 if (!result
.StartRef().IsSet()) {
3515 result
.SetStart(aRange
.StartRef());
3518 TextFragmentData
textFragmentDataAtEnd(aRange
.EndRef(), editingHost
);
3519 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3520 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3521 textFragmentDataAtEnd
.InvisibleTrailingWhiteSpaceRangeRef());
3522 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3523 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3524 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3526 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3527 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3528 textFragmentDataAtEnd
.InvisibleLeadingWhiteSpaceRangeRef());
3529 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3530 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3531 MOZ_ASSERT(aRange
.EndRef().EqualsOrIsBefore(
3532 invisibleLeadingWhiteSpacesAtEnd
.EndRef()));
3533 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3535 // If there is no invisible white-space and the line ends with a text
3536 // node, shrink the range to end of the text node.
3537 else if (!aRange
.EndRef().IsInTextNode() &&
3538 textFragmentDataAtEnd
.EndsByBlockBoundary() &&
3539 textFragmentDataAtEnd
.StartRef().IsInTextNode()) {
3540 result
.SetEnd(EditorDOMPoint::AtEndOf(
3541 *textFragmentDataAtEnd
.StartRef().ContainerAsText()));
3544 if (!result
.EndRef().IsSet()) {
3545 result
.SetEnd(aRange
.EndRef());
3547 MOZ_ASSERT(result
.IsPositionedAndValid());
3551 /******************************************************************************
3552 * Utilities for other things.
3553 ******************************************************************************/
3556 Result
<bool, nsresult
>
3557 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
3558 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
3559 const Element
* aEditingHost
) {
3560 MOZ_ASSERT(aRange
.IsPositioned());
3561 MOZ_ASSERT(!aRange
.IsInSelection(),
3562 "Changing range in selection may cause running script");
3564 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
3565 NS_WARN_IF(!aRange
.GetEndContainer())) {
3566 return Err(NS_ERROR_FAILURE
);
3569 if (!aRange
.GetStartContainer()->IsContent() ||
3570 !aRange
.GetEndContainer()->IsContent()) {
3574 // If the range crosses a block boundary, we should do nothing for now
3575 // because it hits a bug of inserting a padding `<br>` element after
3576 // joining the blocks.
3577 if (HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3578 *aRange
.GetStartContainer()->AsContent(), aEditingHost
) !=
3579 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3580 *aRange
.GetEndContainer()->AsContent(), aEditingHost
)) {
3584 nsIContent
* startContent
= nullptr;
3585 if (aRange
.GetStartContainer() && aRange
.GetStartContainer()->IsText() &&
3586 aRange
.GetStartContainer()->AsText()->Length() == aRange
.StartOffset()) {
3587 // If next content is a visible `<br>` element, special inline content
3588 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3589 // element like `<hr>`, the range should start with it.
3590 TextFragmentData
textFragmentDataAtStart(
3591 EditorRawDOMPoint(aRange
.StartRef()), aEditingHost
);
3592 if (textFragmentDataAtStart
.EndsByBRElement()) {
3593 if (aHTMLEditor
.IsVisibleBRElement(
3594 textFragmentDataAtStart
.EndReasonBRElementPtr())) {
3595 startContent
= textFragmentDataAtStart
.EndReasonBRElementPtr();
3597 } else if (textFragmentDataAtStart
.EndsBySpecialContent() ||
3598 (textFragmentDataAtStart
.EndsByOtherBlockElement() &&
3599 !HTMLEditUtils::IsContainerNode(
3600 *textFragmentDataAtStart
3601 .EndReasonOtherBlockElementPtr()))) {
3602 startContent
= textFragmentDataAtStart
.GetEndReasonContent();
3606 nsIContent
* endContent
= nullptr;
3607 if (aRange
.GetEndContainer() && aRange
.GetEndContainer()->IsText() &&
3608 !aRange
.EndOffset()) {
3609 // If previous content is a visible `<br>` element, special inline content
3610 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3611 // element like `<hr>`, the range should end after it.
3612 TextFragmentData
textFragmentDataAtEnd(EditorRawDOMPoint(aRange
.EndRef()),
3614 if (textFragmentDataAtEnd
.StartsFromBRElement()) {
3615 if (aHTMLEditor
.IsVisibleBRElement(
3616 textFragmentDataAtEnd
.StartReasonBRElementPtr())) {
3617 endContent
= textFragmentDataAtEnd
.StartReasonBRElementPtr();
3619 } else if (textFragmentDataAtEnd
.StartsFromSpecialContent() ||
3620 (textFragmentDataAtEnd
.StartsFromOtherBlockElement() &&
3621 !HTMLEditUtils::IsContainerNode(
3622 *textFragmentDataAtEnd
3623 .StartReasonOtherBlockElementPtr()))) {
3624 endContent
= textFragmentDataAtEnd
.GetStartReasonContent();
3628 if (!startContent
&& !endContent
) {
3632 nsresult rv
= aRange
.SetStartAndEnd(
3633 startContent
? RangeBoundary(
3634 startContent
->GetParentNode(),
3635 startContent
->GetPreviousSibling()) // at startContent
3636 : aRange
.StartRef(),
3637 endContent
? RangeBoundary(endContent
->GetParentNode(),
3638 endContent
) // after endContent
3640 if (NS_FAILED(rv
)) {
3641 NS_WARNING("nsRange::SetStartAndEnd() failed");
3647 } // namespace mozilla