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 LeafNodeType
= HTMLEditUtils::LeafNodeType
;
40 using LeafNodeTypes
= HTMLEditUtils::LeafNodeTypes
;
41 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
43 const char16_t kNBSP
= 160;
45 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
46 const EditorDOMPoint
& aPoint
) const;
47 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
48 const EditorRawDOMPoint
& aPoint
) const;
49 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
50 const EditorDOMPoint
& aPoint
) const;
51 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
52 const EditorRawDOMPoint
& aPoint
) const;
53 template EditorDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
54 Text
& aTextNode
, const Element
* aAncestorLimiter
);
55 template EditorRawDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
56 Text
& aTextNode
, const Element
* aAncestorLimiter
);
57 template EditorDOMPoint
WSRunScanner::GetFirstVisiblePoint(
58 Text
& aTextNode
, const Element
* aAncestorLimiter
);
59 template EditorRawDOMPoint
WSRunScanner::GetFirstVisiblePoint(
60 Text
& aTextNode
, const Element
* aAncestorLimiter
);
62 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
63 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aScanStartPoint
);
64 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
65 HTMLEditor
& aHTMLEditor
, const EditorRawDOMPoint
& aScanStartPoint
);
66 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
67 HTMLEditor
& aHTMLEditor
, const EditorDOMPointInText
& aScanStartPoint
);
69 template WSRunScanner::TextFragmentData::TextFragmentData(
70 const EditorDOMPoint
& aPoint
, const Element
* aEditingHost
);
71 template WSRunScanner::TextFragmentData::TextFragmentData(
72 const EditorRawDOMPoint
& aPoint
, const Element
* aEditingHost
);
73 template WSRunScanner::TextFragmentData::TextFragmentData(
74 const EditorDOMPointInText
& aPoint
, const Element
* aEditingHost
);
76 nsresult
WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
77 HTMLEditor
& aHTMLEditor
, nsCOMPtr
<nsINode
>* aSplitNode
,
78 int32_t* aSplitOffset
) {
79 if (NS_WARN_IF(!aSplitNode
) || NS_WARN_IF(!*aSplitNode
) ||
80 NS_WARN_IF(!aSplitOffset
)) {
81 return NS_ERROR_INVALID_ARG
;
84 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), aSplitNode
,
87 nsresult rv
= WhiteSpaceVisibilityKeeper::
88 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
89 aHTMLEditor
, EditorDOMPoint(*aSplitNode
, *aSplitOffset
));
90 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
91 "WhiteSpaceVisibilityKeeper::"
92 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() "
98 EditActionResult
WhiteSpaceVisibilityKeeper::
99 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
100 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
101 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
102 const Maybe
<nsAtom
*>& aListElementTagName
,
103 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
105 EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
106 MOZ_ASSERT(&aRightBlockElement
== aAtRightBlockChild
.GetContainer());
108 // NOTE: This method may extend deletion range:
109 // - to delete invisible white-spaces at end of aLeftBlockElement
110 // - to delete invisible white-spaces at start of
111 // afterRightBlockChild.GetChild()
112 // - to delete invisible white-spaces before afterRightBlockChild.GetChild()
113 // - to delete invisible `<br>` element at end of aLeftBlockElement
115 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
117 EditorDOMPoint afterRightBlockChild
= aAtRightBlockChild
.NextPoint();
118 MOZ_ASSERT(afterRightBlockChild
.IsSetAndValid());
119 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
120 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
123 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
124 "failed at left block");
125 return EditActionResult(rv
);
127 if (!afterRightBlockChild
.IsSetAndValid()) {
129 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
130 "running script and the point to be modified was changed");
131 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
134 OwningNonNull
<Element
> rightBlockElement
= aRightBlockElement
;
136 // We can't just track rightBlockElement because it's an Element.
137 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(),
138 &afterRightBlockChild
);
139 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
140 aHTMLEditor
, afterRightBlockChild
);
143 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
144 "failed at right block child");
145 return EditActionResult(rv
);
148 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
149 // Do we really need to do update rightBlockElement here??
150 // XXX And afterRightBlockChild.GetContainerAsElement() always returns
151 // an element pointer so that probably here should not use
152 // accessors of EditorDOMPoint, should use DOM API directly instead.
153 if (afterRightBlockChild
.GetContainerAsElement()) {
154 rightBlockElement
= *afterRightBlockChild
.GetContainerAsElement();
155 } else if (NS_WARN_IF(
156 !afterRightBlockChild
.GetContainerParentAsElement())) {
157 return EditActionResult(NS_ERROR_UNEXPECTED
);
159 rightBlockElement
= *afterRightBlockChild
.GetContainerParentAsElement();
164 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
165 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
166 aHTMLEditor
.GetActiveEditingHost(),
167 EditorDOMPoint::AtEndOf(aLeftBlockElement
));
169 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
170 "The preceding invisible BR element computation was different");
171 EditActionResult
ret(NS_OK
);
172 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
173 // AutoInclusiveAncestorBlockElementsJoiner.
174 if (NS_WARN_IF(aListElementTagName
.isSome())) {
175 // Since 2002, here was the following comment:
176 // > The idea here is to take all children in rightListElement that are
177 // > past offset, and pull them into leftlistElement.
178 // However, this has never been performed because we are here only when
179 // neither left list nor right list is a descendant of the other but
180 // in such case, getting a list item in the right list node almost
181 // always failed since a variable for offset of
182 // rightListElement->GetChildAt() was not initialized. So, it might be
183 // a bug, but we should keep this traditional behavior for now. If you
184 // find when we get here, please remove this comment if we don't need to
185 // do it. Otherwise, please move children of the right list node to the
186 // end of the left list node.
188 // XXX Although, we do nothing here, but for keeping traditional
189 // behavior, we should mark as handled.
192 // XXX Why do we ignore the result of MoveOneHardLineContents()?
193 NS_ASSERTION(rightBlockElement
== afterRightBlockChild
.GetContainer(),
194 "The relation is not guaranteed but assumed");
196 Result
<bool, nsresult
> firstLineHasContent
=
197 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(EditorRawDOMPoint(
198 rightBlockElement
, afterRightBlockChild
.Offset()));
199 #endif // #ifdef DEBUG
200 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
201 EditorDOMPoint(rightBlockElement
, afterRightBlockChild
.Offset()),
202 EditorDOMPoint(&aLeftBlockElement
, 0),
203 HTMLEditor::MoveToEndOfContainer::Yes
);
204 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
205 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
207 NS_WARNING_ASSERTION(moveNodeResult
.Succeeded(),
208 "HTMLEditor::MoveOneHardLineContents("
209 "MoveToEndOfContainer::Yes) failed, but ignored");
210 if (moveNodeResult
.Succeeded()) {
212 MOZ_ASSERT(!firstLineHasContent
.isErr());
213 if (firstLineHasContent
.inspect()) {
214 NS_ASSERTION(moveNodeResult
.Handled(),
215 "Failed to consider whether moving or not something");
217 NS_ASSERTION(moveNodeResult
.Ignored(),
218 "Failed to consider whether moving or not something");
220 #endif // #ifdef DEBUG
221 ret
|= moveNodeResult
;
223 // Now, all children of rightBlockElement were moved to leftBlockElement.
224 // So, afterRightBlockChild is now invalid.
225 afterRightBlockChild
.Clear();
228 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
232 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
233 *invisibleBRElementAtEndOfLeftBlockElement
);
234 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
235 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
238 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
239 return EditActionResult(rv
);
241 return EditActionHandled();
245 EditActionResult
WhiteSpaceVisibilityKeeper::
246 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
247 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
248 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
249 nsIContent
& aLeftContentInBlock
,
250 const Maybe
<nsAtom
*>& aListElementTagName
,
251 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
253 EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
255 &aLeftBlockElement
== &aLeftContentInBlock
||
256 EditorUtils::IsDescendantOf(aLeftContentInBlock
, aLeftBlockElement
));
257 MOZ_ASSERT(&aLeftBlockElement
== aAtLeftBlockChild
.GetContainer());
259 // NOTE: This method may extend deletion range:
260 // - to delete invisible white-spaces at start of aRightBlockElement
261 // - to delete invisible white-spaces before aRightBlockElement
262 // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild()
263 // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild()
264 // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
266 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
268 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
269 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0));
272 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() failed "
274 return EditActionResult(rv
);
276 if (!aAtLeftBlockChild
.IsSetAndValid()) {
278 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
279 "running script and the point to be modified was changed");
280 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
283 OwningNonNull
<Element
> originalLeftBlockElement
= aLeftBlockElement
;
284 OwningNonNull
<Element
> leftBlockElement
= aLeftBlockElement
;
285 EditorDOMPoint
atLeftBlockChild(aAtLeftBlockChild
);
287 // We can't just track leftBlockElement because it's an Element, so track
289 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &atLeftBlockChild
);
290 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
291 aHTMLEditor
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
292 atLeftBlockChild
.Offset()));
295 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
296 "failed at left block child");
297 return EditActionResult(rv
);
299 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
300 // Do we really need to do update aRightBlockElement here??
301 // XXX And atLeftBlockChild.GetContainerAsElement() always returns
302 // an element pointer so that probably here should not use
303 // accessors of EditorDOMPoint, should use DOM API directly instead.
304 if (atLeftBlockChild
.GetContainerAsElement()) {
305 leftBlockElement
= *atLeftBlockChild
.GetContainerAsElement();
306 } else if (NS_WARN_IF(!atLeftBlockChild
.GetContainerParentAsElement())) {
307 return EditActionResult(NS_ERROR_UNEXPECTED
);
309 leftBlockElement
= *atLeftBlockChild
.GetContainerParentAsElement();
314 RefPtr
<HTMLBRElement
> invisibleBRElementBeforeLeftBlockElement
=
315 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
316 aHTMLEditor
.GetActiveEditingHost(), atLeftBlockChild
);
318 aPrecedingInvisibleBRElement
== invisibleBRElementBeforeLeftBlockElement
,
319 "The preceding invisible BR element computation was different");
320 EditActionResult
ret(NS_OK
);
321 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
322 // AutoInclusiveAncestorBlockElementsJoiner.
323 if (aListElementTagName
.isSome()) {
324 // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
325 MOZ_ASSERT(originalLeftBlockElement
== atLeftBlockChild
.GetContainer(),
326 "This is not guaranteed, but assumed");
328 Result
<bool, nsresult
> rightBlockHasContent
=
329 aHTMLEditor
.CanMoveChildren(aRightBlockElement
, aLeftBlockElement
);
330 #endif // #ifdef DEBUG
331 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveChildrenWithTransaction(
332 aRightBlockElement
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
333 atLeftBlockChild
.Offset()));
334 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
335 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
337 NS_WARNING_ASSERTION(
338 moveNodeResult
.Succeeded(),
339 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
340 if (moveNodeResult
.Succeeded()) {
341 ret
|= moveNodeResult
;
343 MOZ_ASSERT(!rightBlockHasContent
.isErr());
344 if (rightBlockHasContent
.inspect()) {
345 NS_ASSERTION(moveNodeResult
.Handled(),
346 "Failed to consider whether moving or not children");
348 NS_ASSERTION(moveNodeResult
.Ignored(),
349 "Failed to consider whether moving or not children");
351 #endif // #ifdef DEBUG
353 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
354 atLeftBlockChild
.Clear();
356 // Left block is a parent of right block, and the parent of the previous
357 // visible content. Right block is a child and contains the contents we
360 EditorDOMPoint atPreviousContent
;
361 if (&aLeftContentInBlock
== leftBlockElement
) {
362 // We are working with valid HTML, aLeftContentInBlock is a block node,
363 // and is therefore allowed to contain aRightBlockElement. This is the
364 // simple case, we will simply move the content in aRightBlockElement
366 atPreviousContent
= atLeftBlockChild
;
368 // We try to work as well as possible with HTML that's already invalid.
369 // Although "right block" is a block, and a block must not be contained
370 // in inline elements, reality is that broken documents do exist. The
371 // DIRECT parent of "left NODE" might be an inline element. Previous
372 // versions of this code skipped inline parents until the first block
373 // parent was found (and used "left block" as the destination).
374 // However, in some situations this strategy moves the content to an
375 // unexpected position. (see bug 200416) The new idea is to make the
376 // moving content a sibling, next to the previous visible content.
377 atPreviousContent
.Set(&aLeftContentInBlock
);
379 // We want to move our content just after the previous visible node.
380 atPreviousContent
.AdvanceOffset();
383 MOZ_ASSERT(atPreviousContent
.IsSet());
385 // Because we don't want the moving content to receive the style of the
386 // previous content, we split the previous content's style.
389 Result
<bool, nsresult
> firstLineHasContent
=
390 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
391 EditorRawDOMPoint(&aRightBlockElement
, 0));
392 #endif // #ifdef DEBUG
394 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
395 // XXX It's odd to continue handling this edit action if there is no
397 if (!editingHost
|| &aLeftContentInBlock
!= editingHost
) {
398 SplitNodeResult splitResult
=
399 aHTMLEditor
.SplitAncestorStyledInlineElementsAt(atPreviousContent
,
401 if (splitResult
.Failed()) {
402 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
403 return EditActionResult(splitResult
.Rv());
406 if (splitResult
.Handled()) {
407 if (splitResult
.GetNextNode()) {
408 atPreviousContent
.Set(splitResult
.GetNextNode());
409 if (!atPreviousContent
.IsSet()) {
410 NS_WARNING("Next node of split point was orphaned");
411 return EditActionResult(NS_ERROR_NULL_POINTER
);
414 atPreviousContent
= splitResult
.SplitPoint();
415 if (!atPreviousContent
.IsSet()) {
416 NS_WARNING("Split node was orphaned");
417 return EditActionResult(NS_ERROR_NULL_POINTER
);
423 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
424 EditorDOMPoint(&aRightBlockElement
, 0), atPreviousContent
);
425 if (moveNodeResult
.Failed()) {
426 NS_WARNING("HTMLEditor::MoveOneHardLineContents() failed");
427 return EditActionResult(moveNodeResult
.Rv());
431 MOZ_ASSERT(!firstLineHasContent
.isErr());
432 if (firstLineHasContent
.inspect()) {
433 NS_ASSERTION(moveNodeResult
.Handled(),
434 "Failed to consider whether moving or not something");
436 NS_ASSERTION(moveNodeResult
.Ignored(),
437 "Failed to consider whether moving or not something");
439 #endif // #ifdef DEBUG
441 ret
|= moveNodeResult
;
444 if (!invisibleBRElementBeforeLeftBlockElement
) {
448 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
449 *invisibleBRElementBeforeLeftBlockElement
);
450 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
451 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
454 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
455 return EditActionResult(rv
);
457 return EditActionHandled();
461 EditActionResult
WhiteSpaceVisibilityKeeper::
462 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
463 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
464 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
465 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
467 !EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
469 !EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
471 // NOTE: This method may extend deletion range:
472 // - to delete invisible white-spaces at end of aLeftBlockElement
473 // - to delete invisible white-spaces at start of aRightBlockElement
474 // - to delete invisible `<br>` element at end of aLeftBlockElement
476 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
478 // Adjust white-space at block boundaries
479 nsresult rv
= WhiteSpaceVisibilityKeeper::
480 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
482 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement
),
483 EditorDOMPoint(&aRightBlockElement
, 0)));
486 "WhiteSpaceVisibilityKeeper::"
487 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
488 return EditActionResult(rv
);
491 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
492 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
493 aHTMLEditor
.GetActiveEditingHost(),
494 EditorDOMPoint::AtEndOf(aLeftBlockElement
));
496 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
497 "The preceding invisible BR element computation was different");
498 EditActionResult
ret(NS_OK
);
499 if (aListElementTagName
.isSome() ||
500 aLeftBlockElement
.NodeInfo()->NameAtom() ==
501 aRightBlockElement
.NodeInfo()->NameAtom()) {
502 // Nodes are same type. merge them.
503 EditorDOMPoint atFirstChildOfRightNode
;
504 nsresult rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
505 aLeftBlockElement
, aRightBlockElement
, &atFirstChildOfRightNode
);
506 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
507 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
509 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
510 "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
511 " failed, but ignored");
512 if (aListElementTagName
.isSome() && atFirstChildOfRightNode
.IsSet()) {
513 CreateElementResult convertListTypeResult
=
514 aHTMLEditor
.ChangeListElementType(
515 aRightBlockElement
, MOZ_KnownLive(*aListElementTagName
.ref()),
517 if (NS_WARN_IF(convertListTypeResult
.EditorDestroyed())) {
518 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
520 NS_WARNING_ASSERTION(
521 convertListTypeResult
.Succeeded(),
522 "HTMLEditor::ChangeListElementType() failed, but ignored");
527 Result
<bool, nsresult
> firstLineHasContent
=
528 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
529 EditorRawDOMPoint(&aRightBlockElement
, 0));
530 #endif // #ifdef DEBUG
532 // Nodes are dissimilar types.
533 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
534 EditorDOMPoint(&aRightBlockElement
, 0),
535 EditorDOMPoint(&aLeftBlockElement
, 0),
536 HTMLEditor::MoveToEndOfContainer::Yes
);
537 if (moveNodeResult
.Failed()) {
539 "HTMLEditor::MoveOneHardLineContents(MoveToEndOfContainer::Yes) "
541 return EditActionResult(moveNodeResult
.Rv());
545 MOZ_ASSERT(!firstLineHasContent
.isErr());
546 if (firstLineHasContent
.inspect()) {
547 NS_ASSERTION(moveNodeResult
.Handled(),
548 "Failed to consider whether moving or not something");
550 NS_ASSERTION(moveNodeResult
.Ignored(),
551 "Failed to consider whether moving or not something");
553 #endif // #ifdef DEBUG
554 ret
|= moveNodeResult
;
557 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
558 return ret
.MarkAsHandled();
561 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
562 *invisibleBRElementAtEndOfLeftBlockElement
);
563 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
564 return ret
.SetResult(NS_ERROR_EDITOR_DESTROYED
);
566 // XXX In other top level if blocks, the result of
567 // DeleteNodeWithTransaction() is ignored. Why does only this result
570 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
571 return EditActionResult(rv
);
573 return EditActionHandled();
577 Result
<RefPtr
<Element
>, nsresult
> WhiteSpaceVisibilityKeeper::InsertBRElement(
578 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
) {
579 if (NS_WARN_IF(!aPointToInsert
.IsSet())) {
580 return Err(NS_ERROR_INVALID_ARG
);
583 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
584 // meanwhile, the pre case is handled in HandleInsertText() in
585 // HTMLEditSubActionHandler.cpp
587 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
588 TextFragmentData
textFragmentDataAtInsertionPoint(aPointToInsert
,
590 if (NS_WARN_IF(!textFragmentDataAtInsertionPoint
.IsInitialized())) {
591 return Err(NS_ERROR_FAILURE
);
593 const EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine
=
594 textFragmentDataAtInsertionPoint
595 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
596 const EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine
=
597 textFragmentDataAtInsertionPoint
598 .GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
599 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpaces
=
600 !invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned() ||
601 !invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()
602 ? Some(textFragmentDataAtInsertionPoint
.VisibleWhiteSpacesDataRef())
604 const PointPosition pointPositionWithVisibleWhiteSpaces
=
605 visibleWhiteSpaces
.isSome() && visibleWhiteSpaces
.ref().IsInitialized()
606 ? visibleWhiteSpaces
.ref().ComparePoint(aPointToInsert
)
607 : PointPosition::NotInSameDOMTree
;
609 EditorDOMPoint
pointToInsert(aPointToInsert
);
611 // Some scoping for AutoTrackDOMPoint. This will track our insertion
612 // point while we tweak any surrounding white-space
613 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToInsert
);
615 if (invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()) {
616 if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Collapsed()) {
617 // XXX Why don't we remove all of the invisible white-spaces?
618 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef() ==
620 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
621 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef(),
622 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.EndRef(),
623 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
626 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
631 // If new line will start with visible white-spaces, it needs to be start
633 else if (pointPositionWithVisibleWhiteSpaces
==
634 PointPosition::StartOfFragment
||
635 pointPositionWithVisibleWhiteSpaces
==
636 PointPosition::MiddleOfFragment
) {
637 EditorRawDOMPointInText atNextCharOfInsertionPoint
=
638 textFragmentDataAtInsertionPoint
.GetInclusiveNextEditableCharPoint(
640 if (atNextCharOfInsertionPoint
.IsSet() &&
641 !atNextCharOfInsertionPoint
.IsEndOfContainer() &&
642 atNextCharOfInsertionPoint
.IsCharASCIISpace() &&
643 !EditorUtils::IsContentPreformatted(
644 *atNextCharOfInsertionPoint
.ContainerAsText())) {
645 EditorRawDOMPointInText atPreviousCharOfNextCharOfInsertionPoint
=
646 textFragmentDataAtInsertionPoint
.GetPreviousEditableCharPoint(
647 atNextCharOfInsertionPoint
);
648 if (!atPreviousCharOfNextCharOfInsertionPoint
.IsSet() ||
649 atPreviousCharOfNextCharOfInsertionPoint
.IsEndOfContainer() ||
650 !atPreviousCharOfNextCharOfInsertionPoint
.IsCharASCIISpace()) {
651 // We are at start of non-nbsps. Convert to a single nbsp.
652 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
653 textFragmentDataAtInsertionPoint
654 .GetEndOfCollapsibleASCIIWhiteSpaces(
655 atNextCharOfInsertionPoint
);
657 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
659 EditorDOMRangeInTexts(atNextCharOfInsertionPoint
,
660 endOfCollapsibleASCIIWhiteSpaces
),
661 nsDependentSubstring(&kNBSP
, 1));
664 "WhiteSpaceVisibilityKeeper::"
665 "ReplaceTextAndRemoveEmptyTextNodes() failed");
672 if (invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned()) {
673 if (!invisibleLeadingWhiteSpaceRangeOfNewLine
.Collapsed()) {
674 // XXX Why don't we remove all of the invisible white-spaces?
675 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef() ==
677 // XXX If the DOM tree has been changed above,
678 // invisibleLeadingWhiteSpaceRangeOfNewLine may be invalid now.
679 // So, we may do something wrong here.
680 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
681 invisibleLeadingWhiteSpaceRangeOfNewLine
.StartRef(),
682 invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef(),
683 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
686 "WhiteSpaceVisibilityKeeper::"
687 "DeleteTextAndTextNodesWithTransaction() failed");
692 // If the `<br>` element is put immediately after an NBSP, it should be
693 // replaced with an ASCII white-space.
694 else if (pointPositionWithVisibleWhiteSpaces
==
695 PointPosition::MiddleOfFragment
||
696 pointPositionWithVisibleWhiteSpaces
==
697 PointPosition::EndOfFragment
) {
698 // XXX If the DOM tree has been changed above, pointToInsert` and/or
699 // `visibleWhiteSpaces` may be invalid. So, we may do
700 // something wrong here.
701 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
702 textFragmentDataAtInsertionPoint
703 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
705 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
706 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
707 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
708 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
709 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
711 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
718 Result
<RefPtr
<Element
>, nsresult
> resultOfInsertingBRElement
=
719 aHTMLEditor
.InsertBRElementWithTransaction(pointToInsert
,
721 NS_WARNING_ASSERTION(
722 resultOfInsertingBRElement
.isOk(),
723 "HTMLEditor::InsertBRElementWithTransaction(eNone) failed");
724 MOZ_ASSERT_IF(resultOfInsertingBRElement
.isOk(),
725 resultOfInsertingBRElement
.inspect());
726 return resultOfInsertingBRElement
;
730 nsresult
WhiteSpaceVisibilityKeeper::ReplaceText(
731 HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
732 const EditorDOMRange
& aRangeToBeReplaced
,
733 EditorRawDOMPoint
* aPointAfterInsertedString
/* = nullptr */) {
734 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
735 // meanwhile, the pre case is handled in HandleInsertText() in
736 // HTMLEditSubActionHandler.cpp
738 // MOOSE: for now, just getting the ws logic straight. This implementation
739 // is very slow. Will need to replace edit rules impl with a more efficient
740 // text sink here that does the minimal amount of searching/replacing/copying
742 if (aStringToInsert
.IsEmpty()) {
743 MOZ_ASSERT(aRangeToBeReplaced
.Collapsed());
744 if (aPointAfterInsertedString
) {
745 *aPointAfterInsertedString
= aRangeToBeReplaced
.StartRef();
750 RefPtr
<Element
> editingHost
= aHTMLEditor
.GetActiveEditingHost();
751 TextFragmentData
textFragmentDataAtStart(aRangeToBeReplaced
.StartRef(),
753 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
754 return NS_ERROR_FAILURE
;
756 const bool isInsertionPointEqualsOrIsBeforeStartOfText
=
757 aRangeToBeReplaced
.StartRef().EqualsOrIsBefore(
758 textFragmentDataAtStart
.StartRef());
759 TextFragmentData textFragmentDataAtEnd
=
760 aRangeToBeReplaced
.Collapsed()
761 ? textFragmentDataAtStart
762 : TextFragmentData(aRangeToBeReplaced
.EndRef(), editingHost
);
763 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
764 return NS_ERROR_FAILURE
;
766 const bool isInsertionPointEqualsOrAfterEndOfText
=
767 textFragmentDataAtEnd
.EndRef().EqualsOrIsBefore(
768 aRangeToBeReplaced
.EndRef());
770 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
771 textFragmentDataAtStart
772 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
773 aRangeToBeReplaced
.StartRef());
774 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
775 textFragmentDataAtEnd
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
776 aRangeToBeReplaced
.EndRef());
777 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtStart
=
778 !invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()
779 ? Some(textFragmentDataAtStart
.VisibleWhiteSpacesDataRef())
781 const PointPosition pointPositionWithVisibleWhiteSpacesAtStart
=
782 visibleWhiteSpacesAtStart
.isSome() &&
783 visibleWhiteSpacesAtStart
.ref().IsInitialized()
784 ? visibleWhiteSpacesAtStart
.ref().ComparePoint(
785 aRangeToBeReplaced
.StartRef())
786 : PointPosition::NotInSameDOMTree
;
787 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtEnd
=
788 !invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()
789 ? Some(textFragmentDataAtEnd
.VisibleWhiteSpacesDataRef())
791 const PointPosition pointPositionWithVisibleWhiteSpacesAtEnd
=
792 visibleWhiteSpacesAtEnd
.isSome() &&
793 visibleWhiteSpacesAtEnd
.ref().IsInitialized()
794 ? visibleWhiteSpacesAtEnd
.ref().ComparePoint(
795 aRangeToBeReplaced
.EndRef())
796 : PointPosition::NotInSameDOMTree
;
798 EditorDOMPoint
pointToInsert(aRangeToBeReplaced
.StartRef());
799 nsAutoString
theString(aStringToInsert
);
801 // Some scoping for AutoTrackDOMPoint. This will track our insertion
802 // point while we tweak any surrounding white-space
803 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToInsert
);
805 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
806 if (!invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
807 // XXX Why don't we remove all of the invisible white-spaces?
808 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() ==
810 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
811 invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef(),
812 invisibleTrailingWhiteSpaceRangeAtEnd
.EndRef(),
813 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
816 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
821 // Replace an NBSP at inclusive next character of replacing range to an
822 // ASCII white-space if inserting into a visible white-space sequence.
823 // XXX With modifying the inserting string later, this creates a line break
824 // opportunity after the inserting string, but this causes
825 // inconsistent result with inserting order. E.g., type white-space
826 // n times with various order.
827 else if (pointPositionWithVisibleWhiteSpacesAtEnd
==
828 PointPosition::StartOfFragment
||
829 pointPositionWithVisibleWhiteSpacesAtEnd
==
830 PointPosition::MiddleOfFragment
) {
831 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
832 textFragmentDataAtEnd
833 .GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
835 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
836 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
837 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
838 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
839 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
841 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
847 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
848 if (!invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
849 // XXX Why don't we remove all of the invisible white-spaces?
850 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeAtStart
.EndRef() ==
852 // XXX If the DOM tree has been changed above,
853 // invisibleLeadingWhiteSpaceRangeAtStart may be invalid now.
854 // So, we may do something wrong here.
855 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
856 invisibleLeadingWhiteSpaceRangeAtStart
.StartRef(),
857 invisibleLeadingWhiteSpaceRangeAtStart
.EndRef(),
858 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
861 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
866 // Replace an NBSP at previous character of insertion point to an ASCII
867 // white-space if inserting into a visible white-space sequence.
868 // XXX With modifying the inserting string later, this creates a line break
869 // opportunity before the inserting string, but this causes
870 // inconsistent result with inserting order. E.g., type white-space
871 // n times with various order.
872 else if (pointPositionWithVisibleWhiteSpacesAtStart
==
873 PointPosition::MiddleOfFragment
||
874 pointPositionWithVisibleWhiteSpacesAtStart
==
875 PointPosition::EndOfFragment
) {
876 // XXX If the DOM tree has been changed above, pointToInsert` and/or
877 // `visibleWhiteSpaces` may be invalid. So, we may do
878 // something wrong here.
879 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
880 textFragmentDataAtStart
881 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
883 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
884 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
885 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
886 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
887 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
889 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
895 // After this block, pointToInsert is modified by AutoTrackDOMPoint.
898 // Next up, tweak head and tail of string as needed. First the head: there
899 // are a variety of circumstances that would require us to convert a leading
900 // ws char into an nbsp:
902 if (nsCRT::IsAsciiSpace(theString
[0])) {
903 // If inserting string will follow some invisible leading white-spaces, the
904 // string needs to start with an NBSP.
905 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
906 theString
.SetCharAt(kNBSP
, 0);
908 // If inserting around visible white-spaces, check whether the previous
909 // character of insertion point is an NBSP or an ASCII white-space.
910 else if (pointPositionWithVisibleWhiteSpacesAtStart
==
911 PointPosition::MiddleOfFragment
||
912 pointPositionWithVisibleWhiteSpacesAtStart
==
913 PointPosition::EndOfFragment
) {
914 EditorDOMPointInText atPreviousChar
=
915 textFragmentDataAtStart
.GetPreviousEditableCharPoint(pointToInsert
);
916 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsEndOfContainer() &&
917 atPreviousChar
.IsCharASCIISpace()) {
918 theString
.SetCharAt(kNBSP
, 0);
921 // If the insertion point is (was) before the start of text and it's
922 // immediately after a hard line break, the first ASCII white-space should
923 // be replaced with an NBSP for making it visible.
924 else if (textFragmentDataAtStart
.StartsFromHardLineBreak() &&
925 isInsertionPointEqualsOrIsBeforeStartOfText
) {
926 theString
.SetCharAt(kNBSP
, 0);
931 uint32_t lastCharIndex
= theString
.Length() - 1;
933 if (nsCRT::IsAsciiSpace(theString
[lastCharIndex
])) {
934 // If inserting string will be followed by some invisible trailing
935 // white-spaces, the string needs to end with an NBSP.
936 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
937 theString
.SetCharAt(kNBSP
, lastCharIndex
);
939 // If inserting around visible white-spaces, check whether the inclusive
940 // next character of end of replaced range is an NBSP or an ASCII
942 if (pointPositionWithVisibleWhiteSpacesAtEnd
==
943 PointPosition::StartOfFragment
||
944 pointPositionWithVisibleWhiteSpacesAtEnd
==
945 PointPosition::MiddleOfFragment
) {
946 EditorDOMPointInText atNextChar
=
947 textFragmentDataAtEnd
.GetInclusiveNextEditableCharPoint(
949 if (atNextChar
.IsSet() && !atNextChar
.IsEndOfContainer() &&
950 atNextChar
.IsCharASCIISpace()) {
951 theString
.SetCharAt(kNBSP
, lastCharIndex
);
954 // If the end of replacing range is (was) after the end of text and it's
955 // immediately before block boundary, the last ASCII white-space should
956 // be replaced with an NBSP for making it visible.
957 else if (textFragmentDataAtEnd
.EndsByBlockBoundary() &&
958 isInsertionPointEqualsOrAfterEndOfText
) {
959 theString
.SetCharAt(kNBSP
, lastCharIndex
);
963 // Next, scan string for adjacent ws and convert to nbsp/space combos
964 // MOOSE: don't need to convert tabs here since that is done by
965 // WillInsertText() before we are called. Eventually, all that logic will be
966 // pushed down into here and made more efficient.
968 for (uint32_t i
= 0; i
<= lastCharIndex
; i
++) {
969 if (nsCRT::IsAsciiSpace(theString
[i
])) {
971 // i - 1 can't be negative because prevWS starts out false
972 theString
.SetCharAt(kNBSP
, i
- 1);
981 // XXX If the point is not editable, InsertTextWithTransaction() returns
982 // error, but we keep handling it. But I think that it wastes the
983 // runtime cost. So, perhaps, we should return error code which couldn't
984 // modify it and make each caller of this method decide whether it should
985 // keep or stop handling the edit action.
986 if (!aHTMLEditor
.GetDocument()) {
988 "WhiteSpaceVisibilityKeeper::ReplaceText() lost proper document");
989 return NS_ERROR_UNEXPECTED
;
991 OwningNonNull
<Document
> document
= *aHTMLEditor
.GetDocument();
992 nsresult rv
= aHTMLEditor
.InsertTextWithTransaction(
993 document
, theString
, pointToInsert
, aPointAfterInsertedString
);
994 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
995 return NS_ERROR_EDITOR_DESTROYED
;
997 if (NS_SUCCEEDED(rv
)) {
1001 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed, but ignored");
1003 // XXX Temporarily, set new insertion point to the original point.
1004 if (aPointAfterInsertedString
) {
1005 *aPointAfterInsertedString
= pointToInsert
;
1011 nsresult
WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
1012 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
1013 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
1014 TextFragmentData
textFragmentDataAtDeletion(aPoint
, editingHost
);
1015 if (NS_WARN_IF(!textFragmentDataAtDeletion
.IsInitialized())) {
1016 return NS_ERROR_FAILURE
;
1018 EditorDOMPointInText atPreviousCharOfStart
=
1019 textFragmentDataAtDeletion
.GetPreviousEditableCharPoint(aPoint
);
1020 if (!atPreviousCharOfStart
.IsSet() ||
1021 atPreviousCharOfStart
.IsEndOfContainer()) {
1025 // Easy case, preformatted ws.
1026 if (EditorUtils::IsContentPreformatted(
1027 *atPreviousCharOfStart
.ContainerAsText())) {
1028 if (!atPreviousCharOfStart
.IsCharASCIISpace() &&
1029 !atPreviousCharOfStart
.IsCharNBSP()) {
1032 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1033 atPreviousCharOfStart
, atPreviousCharOfStart
.NextPoint(),
1034 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1035 NS_WARNING_ASSERTION(
1037 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1041 // Caller's job to ensure that previous char is really ws. If it is normal
1042 // ws, we need to delete the whole run.
1043 if (atPreviousCharOfStart
.IsCharASCIISpace()) {
1044 EditorDOMPoint startToDelete
=
1045 textFragmentDataAtDeletion
.GetFirstASCIIWhiteSpacePointCollapsedTo(
1046 atPreviousCharOfStart
);
1047 EditorDOMPoint endToDelete
=
1048 textFragmentDataAtDeletion
.GetEndOfCollapsibleASCIIWhiteSpaces(
1049 atPreviousCharOfStart
);
1051 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1052 aHTMLEditor
, &startToDelete
, &endToDelete
);
1053 if (NS_FAILED(rv
)) {
1055 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1060 // finally, delete that ws
1061 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1062 startToDelete
, endToDelete
,
1063 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1064 NS_WARNING_ASSERTION(
1066 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1070 if (atPreviousCharOfStart
.IsCharNBSP()) {
1071 EditorDOMPoint
startToDelete(atPreviousCharOfStart
);
1072 EditorDOMPoint
endToDelete(startToDelete
.NextPoint());
1074 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1075 aHTMLEditor
, &startToDelete
, &endToDelete
);
1076 if (NS_FAILED(rv
)) {
1078 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1083 // finally, delete that ws
1084 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1085 startToDelete
, endToDelete
,
1086 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1087 NS_WARNING_ASSERTION(
1089 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1097 nsresult
WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
1098 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
1099 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
1100 TextFragmentData
textFragmentDataAtDeletion(aPoint
, editingHost
);
1101 if (NS_WARN_IF(!textFragmentDataAtDeletion
.IsInitialized())) {
1102 return NS_ERROR_FAILURE
;
1104 EditorDOMPointInText atNextCharOfStart
=
1105 textFragmentDataAtDeletion
.GetInclusiveNextEditableCharPoint(aPoint
);
1106 if (!atNextCharOfStart
.IsSet() || atNextCharOfStart
.IsEndOfContainer()) {
1110 // Easy case, preformatted ws.
1111 if (EditorUtils::IsContentPreformatted(
1112 *atNextCharOfStart
.ContainerAsText())) {
1113 if (!atNextCharOfStart
.IsCharASCIISpace() &&
1114 !atNextCharOfStart
.IsCharNBSP()) {
1117 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1118 atNextCharOfStart
, atNextCharOfStart
.NextPoint(),
1119 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1120 NS_WARNING_ASSERTION(
1122 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1126 // Caller's job to ensure that next char is really ws. If it is normal ws,
1127 // we need to delete the whole run.
1128 if (atNextCharOfStart
.IsCharASCIISpace()) {
1129 EditorDOMPoint startToDelete
=
1130 textFragmentDataAtDeletion
.GetFirstASCIIWhiteSpacePointCollapsedTo(
1132 EditorDOMPoint endToDelete
=
1133 textFragmentDataAtDeletion
.GetEndOfCollapsibleASCIIWhiteSpaces(
1136 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1137 aHTMLEditor
, &startToDelete
, &endToDelete
);
1138 if (NS_FAILED(rv
)) {
1140 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1145 // Finally, delete that ws
1146 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1147 startToDelete
, endToDelete
,
1148 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1149 NS_WARNING_ASSERTION(
1151 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1155 if (atNextCharOfStart
.IsCharNBSP()) {
1156 EditorDOMPoint
startToDelete(atNextCharOfStart
);
1157 EditorDOMPoint
endToDelete(startToDelete
.NextPoint());
1159 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1160 aHTMLEditor
, &startToDelete
, &endToDelete
);
1161 if (NS_FAILED(rv
)) {
1163 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1168 // Finally, delete that ws
1169 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1170 startToDelete
, endToDelete
,
1171 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1172 NS_WARNING_ASSERTION(
1174 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1182 nsresult
WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
1183 HTMLEditor
& aHTMLEditor
, nsIContent
& aContentToDelete
,
1184 const EditorDOMPoint
& aCaretPoint
) {
1185 EditorDOMPoint
atContent(&aContentToDelete
);
1186 if (!atContent
.IsSet()) {
1187 NS_WARNING("Deleting content node was an orphan node");
1188 return NS_ERROR_FAILURE
;
1190 if (!HTMLEditUtils::IsRemovableNode(aContentToDelete
)) {
1191 NS_WARNING("Deleting content node wasn't removable");
1192 return NS_ERROR_FAILURE
;
1194 nsresult rv
= WhiteSpaceVisibilityKeeper::
1195 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1196 aHTMLEditor
, EditorDOMRange(atContent
, atContent
.NextPoint()));
1197 if (NS_FAILED(rv
)) {
1199 "WhiteSpaceVisibilityKeeper::"
1200 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
1204 nsCOMPtr
<nsIContent
> previousEditableSibling
=
1205 HTMLEditUtils::GetPreviousSibling(
1206 aContentToDelete
, {WalkTreeOption::IgnoreNonEditableNode
});
1207 // Delete the node, and join like nodes if appropriate
1208 rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContentToDelete
);
1209 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1210 return NS_ERROR_EDITOR_DESTROYED
;
1212 if (NS_FAILED(rv
)) {
1213 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1216 // Are they both text nodes? If so, join them!
1217 // XXX This may cause odd behavior if there is non-editable nodes
1218 // around the atomic content.
1219 if (!aCaretPoint
.IsInTextNode() || !previousEditableSibling
||
1220 !previousEditableSibling
->IsText()) {
1224 nsIContent
* nextEditableSibling
= HTMLEditUtils::GetNextSibling(
1225 *previousEditableSibling
, {WalkTreeOption::IgnoreNonEditableNode
});
1226 if (aCaretPoint
.GetContainer() != nextEditableSibling
) {
1229 EditorDOMPoint atFirstChildOfRightNode
;
1230 rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
1231 *previousEditableSibling
,
1232 MOZ_KnownLive(*aCaretPoint
.GetContainerAsText()),
1233 &atFirstChildOfRightNode
);
1234 if (NS_FAILED(rv
)) {
1235 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1238 if (!atFirstChildOfRightNode
.IsSet()) {
1240 "HTMLEditor::JoinNearestEditableNodesWithTransaction() didn't return "
1241 "right node position");
1242 return NS_ERROR_FAILURE
;
1245 rv
= aHTMLEditor
.CollapseSelectionTo(atFirstChildOfRightNode
);
1246 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1247 "HTMLEditor::CollapseSelectionTo() failed");
1251 template <typename PT
, typename CT
>
1252 WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1253 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1254 MOZ_ASSERT(aPoint
.IsSet());
1256 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1257 return WSScanResult(nullptr, WSType::UnexpectedError
);
1260 // If the range has visible text and start of the visible text is before
1261 // aPoint, return previous character in the text.
1262 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1263 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1264 if (visibleWhiteSpaces
.IsInitialized() &&
1265 visibleWhiteSpaces
.StartRef().IsBefore(aPoint
)) {
1266 // If the visible things are not editable, we shouldn't scan "editable"
1267 // things now. Whether keep scanning editable things or not should be
1268 // considered by the caller.
1269 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1270 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
);
1272 EditorDOMPointInText atPreviousChar
= GetPreviousEditableCharPoint(aPoint
);
1273 // When it's a non-empty text node, return it.
1274 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsContainerEmpty()) {
1275 MOZ_ASSERT(!atPreviousChar
.IsEndOfContainer());
1276 return WSScanResult(atPreviousChar
.NextPoint(),
1277 atPreviousChar
.IsCharASCIISpaceOrNBSP()
1278 ? WSType::NormalWhiteSpaces
1279 : WSType::NormalText
);
1283 // Otherwise, return the start of the range.
1284 if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
1285 TextFragmentDataAtStartRef().StartRef().GetContainer()) {
1286 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
1288 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1289 TextFragmentDataAtStartRef().StartRawReason());
1291 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1292 TextFragmentDataAtStartRef().StartRawReason());
1295 template <typename PT
, typename CT
>
1296 WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
1297 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1298 MOZ_ASSERT(aPoint
.IsSet());
1300 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1301 return WSScanResult(nullptr, WSType::UnexpectedError
);
1304 // If the range has visible text and aPoint equals or is before the end of the
1305 // visible text, return inclusive next character in the text.
1306 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1307 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1308 if (visibleWhiteSpaces
.IsInitialized() &&
1309 aPoint
.EqualsOrIsBefore(visibleWhiteSpaces
.EndRef())) {
1310 // If the visible things are not editable, we shouldn't scan "editable"
1311 // things now. Whether keep scanning editable things or not should be
1312 // considered by the caller.
1313 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1314 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
);
1316 EditorDOMPointInText atNextChar
= GetInclusiveNextEditableCharPoint(aPoint
);
1317 // When it's a non-empty text node, return it.
1318 if (atNextChar
.IsSet() && !atNextChar
.IsContainerEmpty()) {
1319 return WSScanResult(
1321 !atNextChar
.IsEndOfContainer() && atNextChar
.IsCharASCIISpaceOrNBSP()
1322 ? WSType::NormalWhiteSpaces
1323 : WSType::NormalText
);
1327 // Otherwise, return the end of the range.
1328 if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
1329 TextFragmentDataAtStartRef().EndRef().GetContainer()) {
1330 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
1332 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1333 TextFragmentDataAtStartRef().EndRawReason());
1335 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1336 TextFragmentDataAtStartRef().EndRawReason());
1339 template <typename EditorDOMPointType
>
1340 WSRunScanner::TextFragmentData::TextFragmentData(
1341 const EditorDOMPointType
& aPoint
, const Element
* aEditingHost
)
1342 : mEditingHost(aEditingHost
), mIsPreformatted(false) {
1343 if (!aPoint
.IsSetAndValid()) {
1344 NS_WARNING("aPoint was invalid");
1347 if (!aPoint
.IsInContentNode()) {
1348 NS_WARNING("aPoint was in Document or DocumentFragment");
1349 // I.e., we're try to modify outside of root element. We don't need to
1350 // support such odd case because web apps cannot append text nodes as
1351 // direct child of Document node.
1355 mScanStartPoint
= aPoint
;
1356 NS_ASSERTION(EditorUtils::IsEditableContent(
1357 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
1358 "Given content is not editable");
1360 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
1361 "Given content is not an element and an orphan node");
1362 if (NS_WARN_IF(!EditorUtils::IsEditableContent(
1363 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
))) {
1366 Element
* editableBlockParentOrTopmostEditableInlineElement
= HTMLEditUtils::
1367 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
1368 *mScanStartPoint
.ContainerAsContent());
1369 if (!editableBlockParentOrTopmostEditableInlineElement
) {
1372 "GetInclusiveAncestorEditableBlockElementOrInlineEditingHost() "
1373 "couldn't find editing host");
1377 mStart
= BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1378 mScanStartPoint
, *editableBlockParentOrTopmostEditableInlineElement
,
1379 mEditingHost
, &mNBSPData
);
1380 mEnd
= BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1381 mScanStartPoint
, *editableBlockParentOrTopmostEditableInlineElement
,
1382 mEditingHost
, &mNBSPData
);
1383 // If scan start point is start/end of preformatted text node, only
1384 // mEnd/mStart crosses a preformatted character so that when one of
1385 // them crosses a preformatted character, this fragment's range is
1387 // Additionally, if the scan start point is preformatted, and there is
1388 // no text node around it, the range is also preformatted.
1389 mIsPreformatted
= mStart
.AcrossPreformattedCharacter() ||
1390 mEnd
.AcrossPreformattedCharacter() ||
1391 (EditorUtils::IsContentPreformatted(
1392 *mScanStartPoint
.ContainerAsContent()) &&
1393 !mStart
.IsNormalText() && !mEnd
.IsNormalText());
1397 template <typename EditorDOMPointType
>
1398 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1399 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1400 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1401 MOZ_ASSERT(aPoint
.IsSetAndValid());
1402 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1403 MOZ_DIAGNOSTIC_ASSERT(
1404 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1406 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1407 for (uint32_t i
= std::min(aPoint
.Offset(), textFragment
.GetLength()); i
;
1409 char16_t ch
= textFragment
.CharAt(i
- 1);
1410 if (nsCRT::IsAsciiSpace(ch
)) {
1414 if (ch
== HTMLEditUtils::kNBSP
) {
1416 aNBSPData
->NotifyNBSP(
1417 EditorDOMPointInText(aPoint
.ContainerAsText(), i
- 1),
1418 NoBreakingSpaceData::Scanning::Backward
);
1423 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1424 *aPoint
.ContainerAsText(), WSType::NormalText
,
1432 template <typename EditorDOMPointType
>
1433 WSRunScanner::TextFragmentData::BoundaryData
WSRunScanner::TextFragmentData::
1434 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1435 const EditorDOMPointType
& aPoint
,
1436 const Element
& aEditableBlockParentOrTopmostEditableInlineContent
,
1437 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1438 MOZ_ASSERT(aPoint
.IsSetAndValid());
1440 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer()) {
1441 // If the point is in a text node which is preformatted, we should return
1442 // the point as a visible character point.
1443 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1444 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1447 // If the text node is not preformatted, we should look for its preceding
1449 Maybe
<BoundaryData
> startInTextNode
=
1450 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint
,
1452 if (startInTextNode
.isSome()) {
1453 return startInTextNode
.ref();
1455 // The text node does not have visible character, let's keep scanning
1457 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1458 EditorDOMPoint(aPoint
.ContainerAsText(), 0),
1459 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1463 // Then, we need to check previous leaf node.
1464 nsIContent
* previousLeafContentOrBlock
=
1465 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
1466 aPoint
, aEditableBlockParentOrTopmostEditableInlineContent
,
1467 {LeafNodeType::LeafNodeOrNonEditableNode
}, aEditingHost
);
1468 if (!previousLeafContentOrBlock
) {
1469 // no prior node means we exhausted
1470 // aEditableBlockParentOrTopmostEditableInlineContent
1471 // mReasonContent can be either a block element or any non-editable
1472 // content in this case.
1473 return BoundaryData(aPoint
,
1474 const_cast<Element
&>(
1475 aEditableBlockParentOrTopmostEditableInlineContent
),
1476 WSType::CurrentBlockBoundary
, Preformatted::No
);
1479 if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock
)) {
1480 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1481 WSType::OtherBlockBoundary
, Preformatted::No
);
1484 if (!previousLeafContentOrBlock
->IsText() ||
1485 !previousLeafContentOrBlock
->IsEditable()) {
1486 // it's a break or a special node, like <img>, that is not a block and
1487 // not a break but still serves as a terminator to ws runs.
1488 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1489 previousLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1491 : WSType::SpecialContent
,
1495 if (!previousLeafContentOrBlock
->AsText()->TextLength()) {
1496 // If it's an empty text node, keep looking for its previous leaf content.
1497 // Note that even if the empty text node is preformatted, we should keep
1498 // looking for the previous one.
1499 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1500 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1501 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1505 if (EditorUtils::IsContentPreformatted(*previousLeafContentOrBlock
)) {
1506 // If the previous text node is preformatted and not empty, we should return
1507 // its end as found a visible character. Note that we stop scanning
1508 // collapsible white-spaces due to reaching preformatted non-empty text
1509 // node. I.e., the following text node might be not preformatted.
1510 return BoundaryData(EditorDOMPoint::AtEndOf(*previousLeafContentOrBlock
),
1511 *previousLeafContentOrBlock
, WSType::NormalText
,
1515 Maybe
<BoundaryData
> startInTextNode
=
1516 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1517 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock
->AsText()),
1519 if (startInTextNode
.isSome()) {
1520 return startInTextNode
.ref();
1523 // The text node does not have visible character, let's keep scanning
1525 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1526 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1527 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1532 template <typename EditorDOMPointType
>
1533 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1534 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1535 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1536 MOZ_ASSERT(aPoint
.IsSetAndValid());
1537 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1538 MOZ_DIAGNOSTIC_ASSERT(
1539 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1541 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1542 for (uint32_t i
= aPoint
.Offset(); i
< textFragment
.GetLength(); i
++) {
1543 char16_t ch
= textFragment
.CharAt(i
);
1544 if (nsCRT::IsAsciiSpace(ch
)) {
1548 if (ch
== HTMLEditUtils::kNBSP
) {
1550 aNBSPData
->NotifyNBSP(EditorDOMPointInText(aPoint
.ContainerAsText(), i
),
1551 NoBreakingSpaceData::Scanning::Forward
);
1556 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1557 *aPoint
.ContainerAsText(), WSType::NormalText
,
1565 template <typename EditorDOMPointType
>
1566 WSRunScanner::TextFragmentData::BoundaryData
1567 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1568 const EditorDOMPointType
& aPoint
,
1569 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
1570 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1571 MOZ_ASSERT(aPoint
.IsSetAndValid());
1573 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer()) {
1574 // If the point is in a text node which is preformatted, we should return
1575 // the point as a visible character point.
1576 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1577 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1580 // If the text node is not preformatted, we should look for inclusive
1582 Maybe
<BoundaryData
> endInTextNode
=
1583 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint
, aNBSPData
);
1584 if (endInTextNode
.isSome()) {
1585 return endInTextNode
.ref();
1587 // The text node does not have visible character, let's keep scanning
1589 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1590 EditorDOMPointInText::AtEndOf(*aPoint
.ContainerAsText()),
1591 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1595 // Then, we need to check next leaf node.
1596 nsIContent
* nextLeafContentOrBlock
=
1597 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1598 aPoint
, aEditableBlockParentOrTopmostEditableInlineElement
,
1599 {LeafNodeType::LeafNodeOrNonEditableNode
}, aEditingHost
);
1600 if (!nextLeafContentOrBlock
) {
1601 // no next node means we exhausted
1602 // aEditableBlockParentOrTopmostEditableInlineElement
1603 // mReasonContent can be either a block element or any non-editable
1604 // content in this case.
1605 return BoundaryData(aPoint
,
1606 const_cast<Element
&>(
1607 aEditableBlockParentOrTopmostEditableInlineElement
),
1608 WSType::CurrentBlockBoundary
, Preformatted::No
);
1611 if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock
)) {
1612 // we encountered a new block. therefore no more ws.
1613 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1614 WSType::OtherBlockBoundary
, Preformatted::No
);
1617 if (!nextLeafContentOrBlock
->IsText() ||
1618 !nextLeafContentOrBlock
->IsEditable()) {
1619 // we encountered a break or a special node, like <img>,
1620 // that is not a block and not a break but still
1621 // serves as a terminator to ws runs.
1622 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1623 nextLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1625 : WSType::SpecialContent
,
1629 if (!nextLeafContentOrBlock
->AsText()->TextFragment().GetLength()) {
1630 // If it's an empty text node, keep looking for its next leaf content.
1631 // Note that even if the empty text node is preformatted, we should keep
1632 // looking for the next one.
1633 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1634 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0),
1635 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1639 if (EditorUtils::IsContentPreformatted(*nextLeafContentOrBlock
)) {
1640 // If the next text node is preformatted and not empty, we should return
1641 // its start as found a visible character. Note that we stop scanning
1642 // collapsible white-spaces due to reaching preformatted non-empty text
1643 // node. I.e., the following text node might be not preformatted.
1644 return BoundaryData(EditorDOMPoint(nextLeafContentOrBlock
, 0),
1645 *nextLeafContentOrBlock
, WSType::NormalText
,
1649 Maybe
<BoundaryData
> endInTextNode
=
1650 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1651 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0), aNBSPData
);
1652 if (endInTextNode
.isSome()) {
1653 return endInTextNode
.ref();
1656 // The text node does not have visible character, let's keep scanning
1658 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1659 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock
->AsText()),
1660 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1664 const EditorDOMRange
&
1665 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
1666 if (mLeadingWhiteSpaceRange
.isSome()) {
1667 return mLeadingWhiteSpaceRange
.ref();
1670 // If it's preformatted or not start of line, the range is not invisible
1671 // leading white-spaces.
1672 if (!StartsFromHardLineBreak()) {
1673 mLeadingWhiteSpaceRange
.emplace();
1674 return mLeadingWhiteSpaceRange
.ref();
1677 // If there is no NBSP, all of the given range is leading white-spaces.
1678 // Note that this result may be collapsed if there is no leading white-spaces.
1679 if (!mNBSPData
.FoundNBSP()) {
1680 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1681 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1682 return mLeadingWhiteSpaceRange
.ref();
1685 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1687 // Even if the first NBSP is the start, i.e., there is no invisible leading
1688 // white-space, return collapsed range.
1689 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mNBSPData
.FirstPointRef());
1690 return mLeadingWhiteSpaceRange
.ref();
1693 const EditorDOMRange
&
1694 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
1695 if (mTrailingWhiteSpaceRange
.isSome()) {
1696 return mTrailingWhiteSpaceRange
.ref();
1699 // If it's preformatted or not immediately before block boundary, the range is
1700 // not invisible trailing white-spaces. Note that collapsible white-spaces
1701 // before a `<br>` element is visible.
1702 if (!EndsByBlockBoundary()) {
1703 mTrailingWhiteSpaceRange
.emplace();
1704 return mTrailingWhiteSpaceRange
.ref();
1707 // If there is no NBSP, all of the given range is trailing white-spaces.
1708 // Note that this result may be collapsed if there is no trailing white-
1710 if (!mNBSPData
.FoundNBSP()) {
1711 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1712 mTrailingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1713 return mTrailingWhiteSpaceRange
.ref();
1716 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1718 // If last NBSP is immediately before the end, there is no trailing white-
1720 if (mEnd
.PointRef().IsSet() &&
1721 mNBSPData
.LastPointRef().GetContainer() ==
1722 mEnd
.PointRef().GetContainer() &&
1723 mNBSPData
.LastPointRef().Offset() == mEnd
.PointRef().Offset() - 1) {
1724 mTrailingWhiteSpaceRange
.emplace();
1725 return mTrailingWhiteSpaceRange
.ref();
1728 // Otherwise, the may be some trailing white-spaces.
1729 MOZ_ASSERT(!mNBSPData
.LastPointRef().IsEndOfContainer());
1730 mTrailingWhiteSpaceRange
.emplace(mNBSPData
.LastPointRef().NextPoint(),
1732 return mTrailingWhiteSpaceRange
.ref();
1735 EditorDOMRangeInTexts
1736 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
1737 const EditorDOMRange
& aRange
) const {
1738 if (!aRange
.IsPositioned()) {
1739 return EditorDOMRangeInTexts();
1741 if (aRange
.Collapsed()) {
1742 // If collapsed, we can do nothing.
1743 return EditorDOMRangeInTexts();
1745 if (aRange
.IsInTextNodes()) {
1746 // Note that this may return a range which don't include any invisible
1747 // white-spaces due to empty text nodes.
1748 return aRange
.GetAsInTexts();
1751 EditorDOMPointInText firstPoint
=
1752 aRange
.StartRef().IsInTextNode()
1753 ? aRange
.StartRef().AsInText()
1754 : GetInclusiveNextEditableCharPoint(aRange
.StartRef());
1755 if (!firstPoint
.IsSet()) {
1756 return EditorDOMRangeInTexts();
1758 EditorDOMPointInText endPoint
;
1759 if (aRange
.EndRef().IsInTextNode()) {
1760 endPoint
= aRange
.EndRef().AsInText();
1762 // FYI: GetPreviousEditableCharPoint() returns last character's point
1763 // of preceding text node if it's not empty, but we need end of
1764 // the text node here.
1765 endPoint
= GetPreviousEditableCharPoint(aRange
.EndRef());
1766 if (endPoint
.IsSet() && endPoint
.IsAtLastContent()) {
1767 MOZ_ALWAYS_TRUE(endPoint
.AdvanceOffset());
1770 if (!endPoint
.IsSet() || firstPoint
== endPoint
) {
1771 return EditorDOMRangeInTexts();
1773 return EditorDOMRangeInTexts(firstPoint
, endPoint
);
1776 const WSRunScanner::VisibleWhiteSpacesData
&
1777 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
1778 if (mVisibleWhiteSpacesData
.isSome()) {
1779 return mVisibleWhiteSpacesData
.ref();
1782 if (IsPreformattedOrSurrondedByVisibleContent()) {
1783 VisibleWhiteSpacesData visibleWhiteSpaces
;
1784 if (mStart
.PointRef().IsSet()) {
1785 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1787 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1788 if (mEnd
.PointRef().IsSet()) {
1789 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1791 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1792 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1793 return mVisibleWhiteSpacesData
.ref();
1796 // If all of the range is invisible leading or trailing white-spaces,
1797 // there is no visible content.
1798 const EditorDOMRange
& leadingWhiteSpaceRange
=
1799 InvisibleLeadingWhiteSpaceRangeRef();
1800 const bool maybeHaveLeadingWhiteSpaces
=
1801 leadingWhiteSpaceRange
.StartRef().IsSet() ||
1802 leadingWhiteSpaceRange
.EndRef().IsSet();
1803 if (maybeHaveLeadingWhiteSpaces
&&
1804 leadingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1805 leadingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1806 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1807 return mVisibleWhiteSpacesData
.ref();
1809 const EditorDOMRange
& trailingWhiteSpaceRange
=
1810 InvisibleTrailingWhiteSpaceRangeRef();
1811 const bool maybeHaveTrailingWhiteSpaces
=
1812 trailingWhiteSpaceRange
.StartRef().IsSet() ||
1813 trailingWhiteSpaceRange
.EndRef().IsSet();
1814 if (maybeHaveTrailingWhiteSpaces
&&
1815 trailingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1816 trailingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1817 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1818 return mVisibleWhiteSpacesData
.ref();
1821 if (!StartsFromHardLineBreak()) {
1822 VisibleWhiteSpacesData visibleWhiteSpaces
;
1823 if (mStart
.PointRef().IsSet()) {
1824 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1826 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1827 if (!maybeHaveTrailingWhiteSpaces
) {
1828 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1829 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1830 mVisibleWhiteSpacesData
= Some(visibleWhiteSpaces
);
1831 return mVisibleWhiteSpacesData
.ref();
1833 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1834 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1836 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1837 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1838 return mVisibleWhiteSpacesData
.ref();
1841 MOZ_ASSERT(StartsFromHardLineBreak());
1842 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces
);
1844 VisibleWhiteSpacesData visibleWhiteSpaces
;
1845 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
1846 visibleWhiteSpaces
.SetStartPoint(leadingWhiteSpaceRange
.EndRef());
1848 visibleWhiteSpaces
.SetStartFromLeadingWhiteSpaces();
1849 if (!EndsByBlockBoundary()) {
1850 // then no trailing ws. this normal run ends the overall ws run.
1851 if (mEnd
.PointRef().IsSet()) {
1852 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1854 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1855 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1856 return mVisibleWhiteSpacesData
.ref();
1859 MOZ_ASSERT(EndsByBlockBoundary());
1861 if (!maybeHaveTrailingWhiteSpaces
) {
1862 // normal ws runs right up to adjacent block (nbsp next to block)
1863 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1864 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1865 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1866 return mVisibleWhiteSpacesData
.ref();
1869 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1870 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1872 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1873 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1874 return mVisibleWhiteSpacesData
.ref();
1878 nsresult
WhiteSpaceVisibilityKeeper::
1879 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1880 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
) {
1881 if (NS_WARN_IF(!aRangeToDelete
.IsPositionedAndValid()) ||
1882 NS_WARN_IF(!aRangeToDelete
.IsInContentNodes())) {
1883 return NS_ERROR_INVALID_ARG
;
1886 EditorDOMRange
rangeToDelete(aRangeToDelete
);
1887 bool mayBecomeUnexpectedDOMTree
= aHTMLEditor
.MayHaveMutationEventListeners(
1888 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
1889 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
1890 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
1891 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
);
1893 RefPtr
<Element
> editingHost
= aHTMLEditor
.GetActiveEditingHost();
1894 TextFragmentData
textFragmentDataAtStart(rangeToDelete
.StartRef(),
1896 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
1897 return NS_ERROR_FAILURE
;
1899 TextFragmentData
textFragmentDataAtEnd(rangeToDelete
.EndRef(), editingHost
);
1900 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
1901 return NS_ERROR_FAILURE
;
1903 ReplaceRangeData replaceRangeDataAtEnd
=
1904 textFragmentDataAtEnd
.GetReplaceRangeDataAtEndOfDeletionRange(
1905 textFragmentDataAtStart
);
1906 if (replaceRangeDataAtEnd
.IsSet() && !replaceRangeDataAtEnd
.Collapsed()) {
1907 MOZ_ASSERT(rangeToDelete
.EndRef().EqualsOrIsBefore(
1908 replaceRangeDataAtEnd
.EndRef()));
1909 // If there is some text after deleting range, replacing range start must
1910 // equal or be before end of the deleting range.
1911 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
1912 !rangeToDelete
.EndRef().IsEndOfContainer(),
1913 replaceRangeDataAtEnd
.StartRef().EqualsOrIsBefore(
1914 rangeToDelete
.EndRef()));
1915 // If the deleting range end is end of a text node, the replacing range
1916 // starts with another node if the following text node starts with white-
1918 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
1919 rangeToDelete
.EndRef().IsEndOfContainer(),
1920 rangeToDelete
.EndRef() == replaceRangeDataAtEnd
.StartRef() ||
1921 replaceRangeDataAtEnd
.StartRef().IsStartOfContainer());
1922 MOZ_ASSERT(rangeToDelete
.StartRef().EqualsOrIsBefore(
1923 replaceRangeDataAtEnd
.StartRef()));
1924 if (!replaceRangeDataAtEnd
.HasReplaceString()) {
1925 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1926 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1928 AutoEditorDOMPointChildInvalidator
lockOffsetOfStart(startToDelete
);
1929 AutoEditorDOMPointChildInvalidator
lockOffsetOfEnd(endToDelete
);
1930 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1932 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1934 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1935 replaceRangeDataAtEnd
.StartRef(), replaceRangeDataAtEnd
.EndRef(),
1936 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1937 if (NS_FAILED(rv
)) {
1939 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1943 if (mayBecomeUnexpectedDOMTree
&&
1944 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1945 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1946 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1947 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1949 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1950 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1952 MOZ_ASSERT(replaceRangeDataAtEnd
.RangeRef().IsInTextNodes());
1953 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1954 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1956 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1958 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1961 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
1962 aHTMLEditor
, replaceRangeDataAtEnd
.RangeRef().AsInTexts(),
1963 replaceRangeDataAtEnd
.ReplaceStringRef());
1964 if (NS_FAILED(rv
)) {
1966 "WhiteSpaceVisibilityKeeper::"
1967 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
1972 if (mayBecomeUnexpectedDOMTree
&&
1973 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1974 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1975 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1976 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1978 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1979 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1982 if (mayBecomeUnexpectedDOMTree
) {
1983 // If focus is changed by mutation event listeners, we should stop
1984 // handling this edit action.
1985 if (editingHost
!= aHTMLEditor
.GetActiveEditingHost()) {
1986 NS_WARNING("Active editing host was changed");
1987 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1989 if (!rangeToDelete
.IsInContentNodes()) {
1990 NS_WARNING("The modified range was not in content");
1991 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1993 // If the DOM tree might be changed by mutation event listeners, we
1994 // should retrieve the latest data for avoiding to delete/replace
1995 // unexpected range.
1996 textFragmentDataAtStart
=
1997 TextFragmentData(rangeToDelete
.StartRef(), editingHost
);
1998 textFragmentDataAtEnd
=
1999 TextFragmentData(rangeToDelete
.EndRef(), editingHost
);
2002 ReplaceRangeData replaceRangeDataAtStart
=
2003 textFragmentDataAtStart
.GetReplaceRangeDataAtStartOfDeletionRange(
2004 textFragmentDataAtEnd
);
2005 if (!replaceRangeDataAtStart
.IsSet() || replaceRangeDataAtStart
.Collapsed()) {
2008 if (!replaceRangeDataAtStart
.HasReplaceString()) {
2009 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2010 replaceRangeDataAtStart
.StartRef(), replaceRangeDataAtStart
.EndRef(),
2011 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2012 // XXX Should we validate the range for making this return
2013 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2014 NS_WARNING_ASSERTION(
2016 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2019 MOZ_ASSERT(replaceRangeDataAtStart
.RangeRef().IsInTextNodes());
2020 nsresult rv
= WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2021 aHTMLEditor
, replaceRangeDataAtStart
.RangeRef().AsInTexts(),
2022 replaceRangeDataAtStart
.ReplaceStringRef());
2023 // XXX Should we validate the range for making this return
2024 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2025 NS_WARNING_ASSERTION(
2027 "WhiteSpaceVisibilityKeeper::"
2028 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() failed");
2033 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
2034 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const {
2035 const EditorDOMPoint
& startToDelete
=
2036 aTextFragmentDataAtStartToDelete
.ScanStartRef();
2037 const EditorDOMPoint
& endToDelete
= mScanStartPoint
;
2039 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2040 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2041 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2043 if (EndRef().EqualsOrIsBefore(endToDelete
)) {
2044 return ReplaceRangeData();
2047 // If deleting range is followed by invisible trailing white-spaces, we need
2048 // to remove it for making them not visible.
2049 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
2050 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete
);
2051 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
2052 if (invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
2053 return ReplaceRangeData();
2055 // XXX Why don't we remove all invisible white-spaces?
2056 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() == endToDelete
);
2057 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd
, u
""_ns
);
2060 if (IsPreformatted()) {
2061 return ReplaceRangeData();
2064 // If end of the deleting range is followed by visible white-spaces which
2065 // is not preformatted, we might need to replace the following ASCII
2066 // white-spaces with an NBSP.
2067 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtEnd
=
2068 VisibleWhiteSpacesDataRef();
2069 if (!nonPreformattedVisibleWhiteSpacesAtEnd
.IsInitialized()) {
2070 return ReplaceRangeData();
2072 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
=
2073 nonPreformattedVisibleWhiteSpacesAtEnd
.ComparePoint(endToDelete
);
2074 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2075 PointPosition::StartOfFragment
&&
2076 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2077 PointPosition::MiddleOfFragment
) {
2078 return ReplaceRangeData();
2080 // If start of deleting range follows white-spaces or end of delete
2081 // will be start of a line, the following text cannot start with an
2082 // ASCII white-space for keeping it visible.
2083 if (!aTextFragmentDataAtStartToDelete
2084 .FollowingContentMayBecomeFirstVisibleContent(startToDelete
)) {
2085 return ReplaceRangeData();
2087 EditorRawDOMPointInText nextCharOfStartOfEnd
=
2088 GetInclusiveNextEditableCharPoint(endToDelete
);
2089 if (!nextCharOfStartOfEnd
.IsSet() ||
2090 nextCharOfStartOfEnd
.IsEndOfContainer() ||
2091 !nextCharOfStartOfEnd
.IsCharASCIISpace() ||
2092 EditorUtils::IsContentPreformatted(
2093 *nextCharOfStartOfEnd
.ContainerAsText())) {
2094 return ReplaceRangeData();
2096 if (nextCharOfStartOfEnd
.IsStartOfContainer() ||
2097 nextCharOfStartOfEnd
.IsPreviousCharASCIISpace()) {
2098 nextCharOfStartOfEnd
=
2099 aTextFragmentDataAtStartToDelete
2100 .GetFirstASCIIWhiteSpacePointCollapsedTo(nextCharOfStartOfEnd
);
2102 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2103 aTextFragmentDataAtStartToDelete
.GetEndOfCollapsibleASCIIWhiteSpaces(
2104 nextCharOfStartOfEnd
);
2105 return ReplaceRangeData(nextCharOfStartOfEnd
,
2106 endOfCollapsibleASCIIWhiteSpaces
,
2107 nsDependentSubstring(&kNBSP
, 1));
2111 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
2112 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const {
2113 const EditorDOMPoint
& startToDelete
= mScanStartPoint
;
2114 const EditorDOMPoint
& endToDelete
=
2115 aTextFragmentDataAtEndToDelete
.ScanStartRef();
2117 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2118 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2119 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2121 if (startToDelete
.EqualsOrIsBefore(StartRef())) {
2122 return ReplaceRangeData();
2125 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
2126 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete
);
2128 // If deleting range follows invisible leading white-spaces, we need to
2129 // remove them for making them not visible.
2130 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
2131 if (invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
2132 return ReplaceRangeData();
2135 // XXX Why don't we remove all leading white-spaces?
2136 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart
, u
""_ns
);
2139 if (IsPreformatted()) {
2140 return ReplaceRangeData();
2143 // If start of the deleting range follows visible white-spaces which is not
2144 // preformatted, we might need to replace previous ASCII white-spaces with
2146 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtStart
=
2147 VisibleWhiteSpacesDataRef();
2148 if (!nonPreformattedVisibleWhiteSpacesAtStart
.IsInitialized()) {
2149 return ReplaceRangeData();
2152 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
=
2153 nonPreformattedVisibleWhiteSpacesAtStart
.ComparePoint(startToDelete
);
2154 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2155 PointPosition::MiddleOfFragment
&&
2156 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2157 PointPosition::EndOfFragment
) {
2158 return ReplaceRangeData();
2160 // If end of the deleting range is (was) followed by white-spaces or
2161 // previous character of start of deleting range will be immediately
2162 // before a block boundary, the text cannot ends with an ASCII white-space
2163 // for keeping it visible.
2164 if (!aTextFragmentDataAtEndToDelete
.PrecedingContentMayBecomeInvisible(
2166 return ReplaceRangeData();
2168 EditorRawDOMPointInText atPreviousCharOfStart
=
2169 GetPreviousEditableCharPoint(startToDelete
);
2170 if (!atPreviousCharOfStart
.IsSet() ||
2171 atPreviousCharOfStart
.IsEndOfContainer() ||
2172 !atPreviousCharOfStart
.IsCharASCIISpace() ||
2173 EditorUtils::IsContentPreformatted(
2174 *atPreviousCharOfStart
.ContainerAsText())) {
2175 return ReplaceRangeData();
2177 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2178 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2179 atPreviousCharOfStart
=
2180 GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2182 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2183 GetEndOfCollapsibleASCIIWhiteSpaces(atPreviousCharOfStart
);
2184 return ReplaceRangeData(atPreviousCharOfStart
,
2185 endOfCollapsibleASCIIWhiteSpaces
,
2186 nsDependentSubstring(&kNBSP
, 1));
2191 WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
2192 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
) {
2193 TextFragmentData
textFragmentDataAtSplitPoint(
2194 aPointToSplit
, aHTMLEditor
.GetActiveEditingHost());
2195 if (NS_WARN_IF(!textFragmentDataAtSplitPoint
.IsInitialized())) {
2196 return NS_ERROR_FAILURE
;
2199 // used to prepare white-space sequence to be split across two blocks.
2200 // The main issue here is make sure white-spaces around the split point
2201 // doesn't end up becoming non-significant leading or trailing ws after
2203 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2204 textFragmentDataAtSplitPoint
.VisibleWhiteSpacesDataRef();
2205 if (!visibleWhiteSpaces
.IsInitialized()) {
2206 return NS_OK
; // No visible white-space sequence.
2209 PointPosition pointPositionWithVisibleWhiteSpaces
=
2210 visibleWhiteSpaces
.ComparePoint(aPointToSplit
);
2212 // XXX If we split white-space sequence, the following code modify the DOM
2213 // tree twice. This is not reasonable and the latter change may touch
2214 // wrong position. We should do this once.
2216 // If we insert block boundary to start or middle of the white-space sequence,
2217 // the character at the insertion point needs to be an NBSP.
2218 EditorDOMPoint
pointToSplit(aPointToSplit
);
2219 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::StartOfFragment
||
2220 pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
) {
2221 EditorRawDOMPointInText atNextCharOfStart
=
2222 textFragmentDataAtSplitPoint
.GetInclusiveNextEditableCharPoint(
2224 if (atNextCharOfStart
.IsSet() && !atNextCharOfStart
.IsEndOfContainer() &&
2225 atNextCharOfStart
.IsCharASCIISpace() &&
2226 !EditorUtils::IsContentPreformatted(
2227 *atNextCharOfStart
.ContainerAsText())) {
2228 // pointToSplit will be referred bellow so that we need to keep
2229 // it a valid point.
2230 AutoEditorDOMPointChildInvalidator
forgetChild(pointToSplit
);
2231 if (atNextCharOfStart
.IsStartOfContainer() ||
2232 atNextCharOfStart
.IsPreviousCharASCIISpace()) {
2234 textFragmentDataAtSplitPoint
2235 .GetFirstASCIIWhiteSpacePointCollapsedTo(atNextCharOfStart
);
2237 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2238 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2241 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2243 EditorDOMRangeInTexts(atNextCharOfStart
,
2244 endOfCollapsibleASCIIWhiteSpaces
),
2245 nsDependentSubstring(&kNBSP
, 1));
2246 if (NS_FAILED(rv
)) {
2248 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2255 // If we insert block boundary to middle of or end of the white-space
2256 // sequence, the previous character at the insertion point needs to be an
2258 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
||
2259 pointPositionWithVisibleWhiteSpaces
== PointPosition::EndOfFragment
) {
2260 EditorRawDOMPointInText atPreviousCharOfStart
=
2261 textFragmentDataAtSplitPoint
.GetPreviousEditableCharPoint(pointToSplit
);
2262 if (atPreviousCharOfStart
.IsSet() &&
2263 !atPreviousCharOfStart
.IsEndOfContainer() &&
2264 atPreviousCharOfStart
.IsCharASCIISpace() &&
2265 !EditorUtils::IsContentPreformatted(
2266 *atPreviousCharOfStart
.ContainerAsText())) {
2267 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2268 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2269 atPreviousCharOfStart
=
2270 textFragmentDataAtSplitPoint
2271 .GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2273 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2274 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2275 atPreviousCharOfStart
);
2277 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2279 EditorDOMRangeInTexts(atPreviousCharOfStart
,
2280 endOfCollapsibleASCIIWhiteSpaces
),
2281 nsDependentSubstring(&kNBSP
, 1));
2282 if (NS_FAILED(rv
)) {
2284 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2293 template <typename PT
, typename CT
>
2294 EditorDOMPointInText
2295 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint(
2296 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2297 MOZ_ASSERT(aPoint
.IsSetAndValid());
2299 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2300 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2301 return EditorDOMPointInText();
2304 EditorRawDOMPoint point
;
2305 if (nsIContent
* child
=
2306 aPoint
.CanContainerHaveChildren() ? aPoint
.GetChild() : nullptr) {
2307 nsIContent
* leafContent
= child
->HasChildren()
2308 ? HTMLEditUtils::GetFirstLeafContent(
2309 *child
, {LeafNodeType::OnlyLeafNode
})
2311 if (NS_WARN_IF(!leafContent
)) {
2312 return EditorDOMPointInText();
2314 point
.Set(leafContent
, 0);
2319 // If it points a character in a text node, return it.
2320 // XXX For the performance, this does not check whether the container
2321 // is outside of our range.
2322 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2323 !point
.IsEndOfContainer()) {
2324 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset());
2327 if (point
.GetContainer() == GetEndReasonContent()) {
2328 return EditorDOMPointInText();
2331 NS_ASSERTION(EditorUtils::IsEditableContent(
2332 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2333 "Given content is not editable");
2335 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2336 "Given content is not an element and an orphan node");
2337 nsIContent
* editableBlockParentOrTopmostEditableInlineContent
=
2338 mScanStartPoint
.ContainerAsContent() &&
2339 EditorUtils::IsEditableContent(
2340 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2342 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2343 *mScanStartPoint
.ContainerAsContent())
2345 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent
)) {
2346 // Meaning that the container of `mScanStartPoint` is not editable.
2347 editableBlockParentOrTopmostEditableInlineContent
=
2348 mScanStartPoint
.ContainerAsContent();
2351 for (nsIContent
* nextContent
=
2352 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2353 *point
.ContainerAsContent(),
2354 *editableBlockParentOrTopmostEditableInlineContent
,
2355 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
);
2357 nextContent
= HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2358 *nextContent
, *editableBlockParentOrTopmostEditableInlineContent
,
2359 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
)) {
2360 if (!nextContent
->IsText() || !nextContent
->IsEditable()) {
2361 if (nextContent
== GetEndReasonContent()) {
2362 break; // Reached end of current runs.
2366 return EditorDOMPointInText(nextContent
->AsText(), 0);
2368 return EditorDOMPointInText();
2371 template <typename PT
, typename CT
>
2372 EditorDOMPointInText
2373 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint(
2374 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2375 MOZ_ASSERT(aPoint
.IsSetAndValid());
2377 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2378 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2379 return EditorDOMPointInText();
2382 EditorRawDOMPoint point
;
2383 if (nsIContent
* previousChild
= aPoint
.CanContainerHaveChildren()
2384 ? aPoint
.GetPreviousSiblingOfChild()
2386 nsIContent
* leafContent
=
2387 previousChild
->HasChildren()
2388 ? HTMLEditUtils::GetLastLeafContent(*previousChild
,
2389 {LeafNodeType::OnlyLeafNode
})
2391 if (NS_WARN_IF(!leafContent
)) {
2392 return EditorDOMPointInText();
2394 point
.SetToEndOf(leafContent
);
2399 // If it points a character in a text node and it's not first character
2400 // in it, return its previous point.
2401 // XXX For the performance, this does not check whether the container
2402 // is outside of our range.
2403 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2404 !point
.IsStartOfContainer()) {
2405 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset() - 1);
2408 if (point
.GetContainer() == GetStartReasonContent()) {
2409 return EditorDOMPointInText();
2412 NS_ASSERTION(EditorUtils::IsEditableContent(
2413 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2414 "Given content is not editable");
2416 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2417 "Given content is not an element and an orphan node");
2418 nsIContent
* editableBlockParentOrTopmostEditableInlineContent
=
2419 mScanStartPoint
.ContainerAsContent() &&
2420 EditorUtils::IsEditableContent(
2421 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2423 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2424 *mScanStartPoint
.ContainerAsContent())
2426 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent
)) {
2427 // Meaning that the container of `mScanStartPoint` is not editable.
2428 editableBlockParentOrTopmostEditableInlineContent
=
2429 mScanStartPoint
.ContainerAsContent();
2432 for (nsIContent
* previousContent
=
2433 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2434 *point
.ContainerAsContent(),
2435 *editableBlockParentOrTopmostEditableInlineContent
,
2436 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
);
2439 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2441 *editableBlockParentOrTopmostEditableInlineContent
,
2442 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
)) {
2443 if (!previousContent
->IsText() || !previousContent
->IsEditable()) {
2444 if (previousContent
== GetStartReasonContent()) {
2445 break; // Reached start of current runs.
2449 return EditorDOMPointInText(
2450 previousContent
->AsText(),
2451 previousContent
->AsText()->TextLength()
2452 ? previousContent
->AsText()->TextLength() - 1
2455 return EditorDOMPointInText();
2459 template <typename EditorDOMPointType
>
2460 EditorDOMPointType
WSRunScanner::GetAfterLastVisiblePoint(
2461 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2462 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2463 return EditorDOMPointType::AtEndOf(aTextNode
);
2465 TextFragmentData
textFragmentData(
2466 EditorDOMPoint(&aTextNode
,
2467 aTextNode
.Length() ? aTextNode
.Length() - 1 : 0),
2469 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2470 return EditorDOMPointType(); // TODO: Make here return error with Err.
2472 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2473 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
2474 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2475 invisibleWhiteSpaceRange
.Collapsed()) {
2476 return EditorDOMPointType::AtEndOf(aTextNode
);
2478 return EditorDOMPointType(invisibleWhiteSpaceRange
.StartRef());
2482 template <typename EditorDOMPointType
>
2483 EditorDOMPointType
WSRunScanner::GetFirstVisiblePoint(
2484 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2485 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2486 return EditorDOMPointType(&aTextNode
, 0);
2488 TextFragmentData
textFragmentData(EditorDOMPoint(&aTextNode
, 0),
2490 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2491 return EditorDOMPointType(); // TODO: Make here return error with Err.
2493 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2494 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
2495 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2496 invisibleWhiteSpaceRange
.Collapsed()) {
2497 return EditorDOMPointType(&aTextNode
, 0);
2499 return EditorDOMPointType(invisibleWhiteSpaceRange
.EndRef());
2502 EditorDOMPointInText
2503 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
2504 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2505 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2506 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2507 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2508 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2509 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2510 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2512 // If it's not the last character in the text node, let's scan following
2513 // characters in it.
2514 if (!aPointAtASCIIWhiteSpace
.IsAtLastContent()) {
2515 Maybe
<uint32_t> nextVisibleCharOffset
=
2516 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2517 aPointAtASCIIWhiteSpace
);
2518 if (nextVisibleCharOffset
.isSome()) {
2519 // There is non-white-space character in it.
2520 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2521 nextVisibleCharOffset
.value());
2525 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
2526 // the following text nodes.
2527 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2529 EditorDOMPointInText afterLastWhiteSpace
=
2530 EditorDOMPointInText::AtEndOf(*aPointAtASCIIWhiteSpace
.ContainerAsText());
2531 for (EditorDOMPointInText atEndOfPreviousTextNode
= afterLastWhiteSpace
;;) {
2532 EditorDOMPointInText atStartOfNextTextNode
=
2533 GetInclusiveNextEditableCharPoint(atEndOfPreviousTextNode
);
2534 if (!atStartOfNextTextNode
.IsSet()) {
2535 // There is no more text nodes. Return end of the previous text node.
2536 return afterLastWhiteSpace
;
2539 // We can ignore empty text nodes (even if it's preformatted).
2540 if (atStartOfNextTextNode
.IsContainerEmpty()) {
2541 atEndOfPreviousTextNode
= atStartOfNextTextNode
;
2545 // If next node starts with non-white-space character or next node is
2546 // preformatted, return end of previous text node.
2547 if (!atStartOfNextTextNode
.IsCharASCIISpace() ||
2548 EditorUtils::IsContentPreformatted(
2549 *atStartOfNextTextNode
.ContainerAsText())) {
2550 return afterLastWhiteSpace
;
2553 // Otherwise, scan the text node.
2554 Maybe
<uint32_t> nextVisibleCharOffset
=
2555 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2556 atStartOfNextTextNode
);
2557 if (nextVisibleCharOffset
.isSome()) {
2558 return EditorDOMPointInText(atStartOfNextTextNode
.ContainerAsText(),
2559 nextVisibleCharOffset
.value());
2562 // The next text nodes ends with white-space too. Try next one.
2563 afterLastWhiteSpace
= atEndOfPreviousTextNode
=
2564 EditorDOMPointInText::AtEndOf(*atStartOfNextTextNode
.ContainerAsText());
2568 EditorDOMPointInText
2569 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
2570 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2571 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2572 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2573 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2574 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2575 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2576 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2578 // If there is some characters before it, scan it in the text node first.
2579 if (!aPointAtASCIIWhiteSpace
.IsStartOfContainer()) {
2580 uint32_t firstASCIIWhiteSpaceOffset
=
2581 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2582 aPointAtASCIIWhiteSpace
);
2583 if (firstASCIIWhiteSpaceOffset
) {
2584 // There is a non-white-space character in it.
2585 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2586 firstASCIIWhiteSpaceOffset
);
2590 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
2591 // the preceding text nodes.
2592 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2594 EditorDOMPointInText atLastWhiteSpace
=
2595 EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(), 0);
2596 for (EditorDOMPointInText atStartOfPreviousTextNode
= atLastWhiteSpace
;;) {
2597 EditorDOMPointInText atLastCharOfNextTextNode
=
2598 GetPreviousEditableCharPoint(atStartOfPreviousTextNode
);
2599 if (!atLastCharOfNextTextNode
.IsSet()) {
2600 // There is no more text nodes. Return end of last text node.
2601 return atLastWhiteSpace
;
2604 // We can ignore empty text nodes (even if it's preformatted).
2605 if (atLastCharOfNextTextNode
.IsContainerEmpty()) {
2606 atStartOfPreviousTextNode
= atLastCharOfNextTextNode
;
2610 // If next node ends with non-white-space character or next node is
2611 // preformatted, return start of previous text node.
2612 if (!atLastCharOfNextTextNode
.IsCharASCIISpace() ||
2613 EditorUtils::IsContentPreformatted(
2614 *atLastCharOfNextTextNode
.ContainerAsText())) {
2615 return atLastWhiteSpace
;
2618 // Otherwise, scan the text node.
2619 uint32_t firstASCIIWhiteSpaceOffset
=
2620 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2621 atLastCharOfNextTextNode
);
2622 if (firstASCIIWhiteSpaceOffset
) {
2623 return EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(),
2624 firstASCIIWhiteSpaceOffset
);
2627 // The next text nodes starts with white-space too. Try next one.
2628 atLastWhiteSpace
= atStartOfPreviousTextNode
=
2629 EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(), 0);
2634 nsresult
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2635 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
2636 const nsAString
& aReplaceString
) {
2637 MOZ_ASSERT(aRangeToReplace
.IsPositioned());
2638 MOZ_ASSERT(aRangeToReplace
.StartRef().IsSetAndValid());
2639 MOZ_ASSERT(aRangeToReplace
.EndRef().IsSetAndValid());
2640 MOZ_ASSERT(aRangeToReplace
.StartRef().IsBefore(aRangeToReplace
.EndRef()));
2642 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2643 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2644 MOZ_KnownLive(*aRangeToReplace
.StartRef().ContainerAsText()),
2645 aRangeToReplace
.StartRef().Offset(),
2646 aRangeToReplace
.InSameContainer()
2647 ? aRangeToReplace
.EndRef().Offset() -
2648 aRangeToReplace
.StartRef().Offset()
2649 : aRangeToReplace
.StartRef().ContainerAsText()->TextLength() -
2650 aRangeToReplace
.StartRef().Offset(),
2652 if (NS_FAILED(rv
)) {
2653 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2657 if (aRangeToReplace
.InSameContainer()) {
2661 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2662 EditorDOMPointInText::AtEndOf(
2663 *aRangeToReplace
.StartRef().ContainerAsText()),
2664 aRangeToReplace
.EndRef(),
2665 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2666 NS_WARNING_ASSERTION(
2668 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2672 char16_t
WSRunScanner::GetCharAt(Text
* aTextNode
, int32_t aOffset
) const {
2673 // return 0 if we can't get a char, for whatever reason
2674 if (NS_WARN_IF(!aTextNode
) || NS_WARN_IF(aOffset
< 0) ||
2675 NS_WARN_IF(aOffset
>=
2676 static_cast<int32_t>(aTextNode
->TextDataLength()))) {
2679 return aTextNode
->TextFragment().CharAt(aOffset
);
2683 template <typename EditorDOMPointType
>
2684 nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
2685 HTMLEditor
& aHTMLEditor
, const EditorDOMPointType
& aPoint
) {
2686 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
2687 TextFragmentData
textFragmentData(aPoint
, editingHost
);
2688 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2689 return NS_ERROR_FAILURE
;
2692 // this routine examines a run of ws and tries to get rid of some unneeded
2693 // nbsp's, replacing them with regular ascii space if possible. Keeping
2694 // things simple for now and just trying to fix up the trailing ws in the run.
2695 if (!textFragmentData
.FoundNoBreakingWhiteSpaces()) {
2699 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2700 textFragmentData
.VisibleWhiteSpacesDataRef();
2701 if (!visibleWhiteSpaces
.IsInitialized()) {
2705 // Remove this block if we ship Blink-compat white-space normalization.
2706 if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
2707 // now check that what is to the left of it is compatible with replacing
2709 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
=
2710 visibleWhiteSpaces
.EndRef();
2711 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2712 textFragmentData
.GetPreviousEditableCharPoint(
2713 atEndOfVisibleWhiteSpaces
);
2714 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2715 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2716 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2720 // now check that what is to the left of it is compatible with replacing
2722 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2723 textFragmentData
.GetPreviousEditableCharPoint(
2724 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2725 bool isPreviousCharASCIIWhiteSpace
=
2726 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2727 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2728 .IsEndOfContainer() &&
2729 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2730 .IsCharASCIISpace();
2731 bool maybeNBSPFollowingVisibleContent
=
2732 (atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2733 !isPreviousCharASCIIWhiteSpace
) ||
2734 (!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2735 (visibleWhiteSpaces
.StartsFromNormalText() ||
2736 visibleWhiteSpaces
.StartsFromSpecialContent()));
2737 bool followedByVisibleContentOrBRElement
= false;
2739 // If the NBSP follows a visible content or an ASCII white-space, i.e.,
2740 // unless NBSP is first character and start of a block, we may need to
2741 // insert <br> element and restore the NBSP to an ASCII white-space.
2742 if (maybeNBSPFollowingVisibleContent
|| isPreviousCharASCIIWhiteSpace
) {
2743 followedByVisibleContentOrBRElement
=
2744 visibleWhiteSpaces
.EndsByNormalText() ||
2745 visibleWhiteSpaces
.EndsBySpecialContent() ||
2746 visibleWhiteSpaces
.EndsByBRElement();
2747 // First, try to insert <br> element if NBSP is at end of a block.
2748 // XXX We should stop this if there is a visible content.
2749 if (visibleWhiteSpaces
.EndsByBlockBoundary() &&
2750 aPoint
.IsInContentNode()) {
2751 bool insertBRElement
=
2752 HTMLEditUtils::IsBlockElement(*aPoint
.ContainerAsContent());
2753 if (!insertBRElement
) {
2754 NS_ASSERTION(EditorUtils::IsEditableContent(
2755 *aPoint
.ContainerAsContent(), EditorType::HTML
),
2756 "Given content is not editable");
2758 aPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2759 "Given content is not an element and an orphan node");
2760 nsIContent
* blockParentOrTopmostEditableInlineContent
=
2761 EditorUtils::IsEditableContent(*aPoint
.ContainerAsContent(),
2764 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2765 *aPoint
.ContainerAsContent())
2767 insertBRElement
= blockParentOrTopmostEditableInlineContent
&&
2768 HTMLEditUtils::IsBlockElement(
2769 *blockParentOrTopmostEditableInlineContent
);
2771 if (insertBRElement
) {
2772 // We are at a block boundary. Insert a <br>. Why? Well, first note
2773 // that the br will have no visible effect since it is up against a
2774 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
2775 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
2776 // this <br> addition gets us is the ability to convert a trailing
2777 // nbsp to a space. Consider: |<body>foo. '</body>|, where '
2778 // represents selection. User types space attempting to put 2 spaces
2779 // after the end of their sentence. We used to do this as:
2780 // |<body>foo.  </body>| This caused problems with soft wrapping:
2781 // the nbsp would wrap to the next line, which looked attrocious. If
2782 // you try to do: |<body>foo.  </body>| instead, the trailing
2783 // space is invisible because it is against a block boundary. If you
2785 // |<body>foo.  </body>| then you get an even uglier soft
2786 // wrapping problem, where foo is on one line until you type the final
2787 // space, and then "foo " jumps down to the next line. Ugh. The
2788 // best way I can find out of this is to throw in a harmless <br>
2789 // here, which allows us to do: |<body>foo.  <br></body>|, which
2790 // doesn't cause foo to jump lines, doesn't cause spaces to show up at
2791 // the beginning of soft wrapped lines, and lets the user see 2 spaces
2792 // when they type 2 spaces.
2794 Result
<RefPtr
<Element
>, nsresult
> resultOfInsertingBRElement
=
2795 aHTMLEditor
.InsertBRElementWithTransaction(
2796 atEndOfVisibleWhiteSpaces
);
2797 if (resultOfInsertingBRElement
.isErr()) {
2798 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
2799 return resultOfInsertingBRElement
.unwrapErr();
2801 MOZ_ASSERT(resultOfInsertingBRElement
.inspect());
2803 atPreviousCharOfEndOfVisibleWhiteSpaces
=
2804 textFragmentData
.GetPreviousEditableCharPoint(
2805 atEndOfVisibleWhiteSpaces
);
2806 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2807 textFragmentData
.GetPreviousEditableCharPoint(
2808 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2809 isPreviousCharASCIIWhiteSpace
=
2810 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2811 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2812 .IsEndOfContainer() &&
2813 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2814 .IsCharASCIISpace();
2815 followedByVisibleContentOrBRElement
= true;
2819 // Next, replace the NBSP with an ASCII white-space if it's surrounded
2820 // by visible contents (or immediately before a <br> element).
2821 if (maybeNBSPFollowingVisibleContent
&&
2822 followedByVisibleContentOrBRElement
) {
2823 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2824 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2826 *atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()),
2827 atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset(), 1, u
" "_ns
);
2828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2829 "HTMLEditor::ReplaceTextWithTransaction() failed");
2833 // If the text node is not preformatted, and the NBSP is followed by a <br>
2834 // element and following (maybe multiple) ASCII spaces, remove the NBSP,
2835 // but inserts a NBSP before the spaces. This makes a line break
2836 // opportunity to wrap the line.
2837 // XXX This is different behavior from Blink. Blink generates pairs of
2838 // an NBSP and an ASCII white-space, but put NBSP at the end of the
2839 // sequence. We should follow the behavior for web-compat.
2840 if (maybeNBSPFollowingVisibleContent
|| !isPreviousCharASCIIWhiteSpace
||
2841 !followedByVisibleContentOrBRElement
||
2842 EditorUtils::IsContentPreformatted(
2843 *atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2844 .GetContainerAsText())) {
2848 // Currently, we're at an NBSP following an ASCII space, and we need to
2849 // replace them with `" "` for avoiding collapsing white-spaces.
2850 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2851 .IsEndOfContainer());
2852 EditorDOMPointInText atFirstASCIIWhiteSpace
=
2853 textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2854 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2855 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2856 uint32_t numberOfASCIIWhiteSpacesInStartNode
=
2857 atFirstASCIIWhiteSpace
.ContainerAsText() ==
2858 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2859 ? atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset() -
2860 atFirstASCIIWhiteSpace
.Offset()
2861 : atFirstASCIIWhiteSpace
.ContainerAsText()->Length() -
2862 atFirstASCIIWhiteSpace
.Offset();
2863 // Replace all preceding ASCII white-spaces **and** the NBSP.
2864 uint32_t replaceLengthInStartNode
=
2865 numberOfASCIIWhiteSpacesInStartNode
+
2866 (atFirstASCIIWhiteSpace
.ContainerAsText() ==
2867 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2870 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2871 MOZ_KnownLive(*atFirstASCIIWhiteSpace
.ContainerAsText()),
2872 atFirstASCIIWhiteSpace
.Offset(), replaceLengthInStartNode
,
2874 if (NS_FAILED(rv
)) {
2875 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2879 if (atFirstASCIIWhiteSpace
.GetContainer() ==
2880 atPreviousCharOfEndOfVisibleWhiteSpaces
.GetContainer()) {
2884 // We need to remove the following unnecessary ASCII white-spaces and
2885 // NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
2886 // into the start node.
2887 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2888 EditorDOMPointInText::AtEndOf(
2889 *atFirstASCIIWhiteSpace
.ContainerAsText()),
2890 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint(),
2891 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2892 NS_WARNING_ASSERTION(
2894 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2898 // XXX This is called when top-level edit sub-action handling ends for
2899 // 3 points at most. However, this is not compatible with Blink.
2900 // Blink touches white-space sequence which includes new character
2901 // or following white-space sequence of new <br> element or, if and
2902 // only if deleting range is followed by white-space sequence (i.e.,
2903 // not touched previous white-space sequence of deleting range).
2904 // This should be done when we change to make each edit action
2905 // handler directly normalize white-space sequence rather than
2906 // OnEndHandlingTopLevelEditSucAction().
2908 // First, check if the last character is an NBSP. Otherwise, we don't need
2909 // to do nothing here.
2910 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
= visibleWhiteSpaces
.EndRef();
2911 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2912 textFragmentData
.GetPreviousEditableCharPoint(atEndOfVisibleWhiteSpaces
);
2913 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2914 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2915 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2919 // Next, consider the range to collapse ASCII white-spaces before there.
2920 EditorDOMPointInText startToDelete
, endToDelete
;
2922 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2923 textFragmentData
.GetPreviousEditableCharPoint(
2924 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2925 // If there are some preceding ASCII white-spaces, we need to treat them
2926 // as one white-space. I.e., we need to collapse them.
2927 if (atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP() &&
2928 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2929 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2930 .IsCharASCIISpace()) {
2931 startToDelete
= textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2932 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2933 endToDelete
= atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
;
2935 // Otherwise, we don't need to remove any white-spaces, but we may need
2936 // to normalize the white-space sequence containing the previous NBSP.
2938 startToDelete
= endToDelete
=
2939 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint();
2942 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2943 Result
<EditorDOMPoint
, nsresult
> result
=
2944 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2945 startToDelete
, endToDelete
,
2946 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
,
2947 HTMLEditor::DeleteDirection::Forward
);
2948 NS_WARNING_ASSERTION(
2950 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2951 return result
.isErr() ? result
.unwrapErr() : NS_OK
;
2954 EditorDOMPointInText
WSRunScanner::TextFragmentData::
2955 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
2956 const EditorDOMPoint
& aPointToInsert
) const {
2957 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
2958 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
2959 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2960 PointPosition::MiddleOfFragment
||
2961 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2962 PointPosition::EndOfFragment
,
2963 "Previous char of aPoint should be in the visible white-spaces");
2965 // Try to change an NBSP to a space, if possible, just to prevent NBSP
2966 // proliferation. This routine is called when we are about to make this
2967 // point in the ws abut an inserted break or text, so we don't have to worry
2968 // about what is after it. What is after it now will end up after the
2970 EditorDOMPointInText atPreviousChar
=
2971 GetPreviousEditableCharPoint(aPointToInsert
);
2972 if (!atPreviousChar
.IsSet() || atPreviousChar
.IsEndOfContainer() ||
2973 !atPreviousChar
.IsCharNBSP() ||
2974 EditorUtils::IsContentPreformatted(*atPreviousChar
.ContainerAsText())) {
2975 return EditorDOMPointInText();
2978 EditorDOMPointInText atPreviousCharOfPreviousChar
=
2979 GetPreviousEditableCharPoint(atPreviousChar
);
2980 if (atPreviousCharOfPreviousChar
.IsSet()) {
2981 // If the previous char is in different text node and it's preformatted,
2982 // we shouldn't touch it.
2983 if (atPreviousChar
.ContainerAsText() !=
2984 atPreviousCharOfPreviousChar
.ContainerAsText() &&
2985 EditorUtils::IsContentPreformatted(
2986 *atPreviousCharOfPreviousChar
.ContainerAsText())) {
2987 return EditorDOMPointInText();
2989 // If the previous char of the NBSP at previous position of aPointToInsert
2990 // is an ASCII white-space, we don't need to replace it with same character.
2991 if (!atPreviousCharOfPreviousChar
.IsEndOfContainer() &&
2992 atPreviousCharOfPreviousChar
.IsCharASCIISpace()) {
2993 return EditorDOMPointInText();
2995 return atPreviousChar
;
2998 // If previous content of the NBSP is block boundary, we cannot replace the
2999 // NBSP with an ASCII white-space to keep it rendered.
3000 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3001 VisibleWhiteSpacesDataRef();
3002 if (!visibleWhiteSpaces
.StartsFromNormalText() &&
3003 !visibleWhiteSpaces
.StartsFromSpecialContent()) {
3004 return EditorDOMPointInText();
3006 return atPreviousChar
;
3009 EditorDOMPointInText
WSRunScanner::TextFragmentData::
3010 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
3011 const EditorDOMPoint
& aPointToInsert
) const {
3012 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
3013 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
3014 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3015 PointPosition::StartOfFragment
||
3016 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3017 PointPosition::MiddleOfFragment
,
3018 "Inclusive next char of aPointToInsert should be in the visible "
3021 // Try to change an nbsp to a space, if possible, just to prevent nbsp
3022 // proliferation This routine is called when we are about to make this point
3023 // in the ws abut an inserted text, so we don't have to worry about what is
3024 // before it. What is before it now will end up before the inserted text.
3025 EditorDOMPointInText atNextChar
=
3026 GetInclusiveNextEditableCharPoint(aPointToInsert
);
3027 if (!atNextChar
.IsSet() || NS_WARN_IF(atNextChar
.IsEndOfContainer()) ||
3028 !atNextChar
.IsCharNBSP() ||
3029 EditorUtils::IsContentPreformatted(*atNextChar
.ContainerAsText())) {
3030 return EditorDOMPointInText();
3033 EditorDOMPointInText atNextCharOfNextCharOfNBSP
=
3034 GetInclusiveNextEditableCharPoint(atNextChar
.NextPoint());
3035 if (atNextCharOfNextCharOfNBSP
.IsSet()) {
3036 // If the next char is in different text node and it's preformatted,
3037 // we shouldn't touch it.
3038 if (atNextChar
.ContainerAsText() !=
3039 atNextCharOfNextCharOfNBSP
.ContainerAsText() &&
3040 EditorUtils::IsContentPreformatted(
3041 *atNextCharOfNextCharOfNBSP
.ContainerAsText())) {
3042 return EditorDOMPointInText();
3044 // If following character of an NBSP is an ASCII white-space, we don't
3045 // need to replace it with same character.
3046 if (!atNextCharOfNextCharOfNBSP
.IsEndOfContainer() &&
3047 atNextCharOfNextCharOfNBSP
.IsCharASCIISpace()) {
3048 return EditorDOMPointInText();
3053 // If the NBSP is last character in the hard line, we don't need to
3054 // replace it because it's required to render multiple white-spaces.
3055 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3056 VisibleWhiteSpacesDataRef();
3057 if (!visibleWhiteSpaces
.EndsByNormalText() &&
3058 !visibleWhiteSpaces
.EndsBySpecialContent() &&
3059 !visibleWhiteSpaces
.EndsByBRElement()) {
3060 return EditorDOMPointInText();
3067 nsresult
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
3068 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
3069 MOZ_ASSERT(aPoint
.IsSet());
3070 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3071 TextFragmentData
textFragmentData(aPoint
, editingHost
);
3072 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3073 return NS_ERROR_FAILURE
;
3075 const EditorDOMRange
& leadingWhiteSpaceRange
=
3076 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
3077 // XXX Getting trailing white-space range now must be wrong because
3078 // mutation event listener may invalidate it.
3079 const EditorDOMRange
& trailingWhiteSpaceRange
=
3080 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
3081 DebugOnly
<bool> leadingWhiteSpacesDeleted
= false;
3082 if (leadingWhiteSpaceRange
.IsPositioned() &&
3083 !leadingWhiteSpaceRange
.Collapsed()) {
3084 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3085 leadingWhiteSpaceRange
.StartRef(), leadingWhiteSpaceRange
.EndRef(),
3086 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3087 if (NS_FAILED(rv
)) {
3089 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3090 "delete leading white-spaces");
3093 leadingWhiteSpacesDeleted
= true;
3095 if (trailingWhiteSpaceRange
.IsPositioned() &&
3096 !trailingWhiteSpaceRange
.Collapsed() &&
3097 leadingWhiteSpaceRange
!= trailingWhiteSpaceRange
) {
3098 NS_ASSERTION(!leadingWhiteSpacesDeleted
,
3099 "We're trying to remove trailing white-spaces with maybe "
3101 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3102 trailingWhiteSpaceRange
.StartRef(), trailingWhiteSpaceRange
.EndRef(),
3103 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3104 if (NS_FAILED(rv
)) {
3106 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3107 "delete trailing white-spaces");
3114 /*****************************************************************************
3115 * Implementation for new white-space normalizer
3116 *****************************************************************************/
3119 EditorDOMRangeInTexts
3120 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3121 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
) {
3122 // Corresponding to handling invisible white-spaces part of
3123 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
3124 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
3126 MOZ_ASSERT(aStart
.ScanStartRef().IsSetAndValid());
3127 MOZ_ASSERT(aEnd
.ScanStartRef().IsSetAndValid());
3128 MOZ_ASSERT(aStart
.ScanStartRef().EqualsOrIsBefore(aEnd
.ScanStartRef()));
3129 MOZ_ASSERT(aStart
.ScanStartRef().IsInTextNode());
3130 MOZ_ASSERT(aEnd
.ScanStartRef().IsInTextNode());
3132 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
3133 // `GetReplaceRangeDataAtStartOfDeletionRange()` use
3134 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
3135 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
3136 // However, they are really odd as mentioned with "XXX" comments
3137 // in them. For the new white-space normalizer, we need to treat
3138 // invisible white-spaces stricter because the legacy path handles
3139 // white-spaces multiple times (e.g., calling `HTMLEditor::
3140 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
3141 // the bug, but in the new path, we should stop doing same things
3142 // multiple times for both performance and footprint. Therefore,
3143 // even though the result might be different in some edge cases,
3144 // we should use clean path for now. Perhaps, we should fix the odd
3145 // cases before shipping `beforeinput` event in release channel.
3147 const EditorDOMRange
& invisibleLeadingWhiteSpaceRange
=
3148 aStart
.InvisibleLeadingWhiteSpaceRangeRef();
3149 const EditorDOMRange
& invisibleTrailingWhiteSpaceRange
=
3150 aEnd
.InvisibleTrailingWhiteSpaceRangeRef();
3151 const bool hasInvisibleLeadingWhiteSpaces
=
3152 invisibleLeadingWhiteSpaceRange
.IsPositioned() &&
3153 !invisibleLeadingWhiteSpaceRange
.Collapsed();
3154 const bool hasInvisibleTrailingWhiteSpaces
=
3155 invisibleLeadingWhiteSpaceRange
!= invisibleTrailingWhiteSpaceRange
&&
3156 invisibleTrailingWhiteSpaceRange
.IsPositioned() &&
3157 !invisibleTrailingWhiteSpaceRange
.Collapsed();
3159 EditorDOMRangeInTexts
result(aStart
.ScanStartRef().AsInText(),
3160 aEnd
.ScanStartRef().AsInText());
3161 MOZ_ASSERT(result
.IsPositionedAndValid());
3162 if (!hasInvisibleLeadingWhiteSpaces
&& !hasInvisibleTrailingWhiteSpaces
) {
3167 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3168 invisibleLeadingWhiteSpaceRange
.StartRef().IsBefore(
3169 invisibleTrailingWhiteSpaceRange
.StartRef()));
3170 const EditorDOMPoint
& aroundFirstInvisibleWhiteSpace
=
3171 hasInvisibleLeadingWhiteSpaces
3172 ? invisibleLeadingWhiteSpaceRange
.StartRef()
3173 : invisibleTrailingWhiteSpaceRange
.StartRef();
3174 if (aroundFirstInvisibleWhiteSpace
.IsBefore(result
.StartRef())) {
3175 if (aroundFirstInvisibleWhiteSpace
.IsInTextNode()) {
3176 result
.SetStart(aroundFirstInvisibleWhiteSpace
.AsInText());
3177 MOZ_ASSERT(result
.IsPositionedAndValid());
3179 const EditorDOMPointInText atFirstInvisibleWhiteSpace
=
3180 hasInvisibleLeadingWhiteSpaces
3181 ? aStart
.GetInclusiveNextEditableCharPoint(
3182 aroundFirstInvisibleWhiteSpace
)
3183 : aEnd
.GetInclusiveNextEditableCharPoint(
3184 aroundFirstInvisibleWhiteSpace
);
3185 MOZ_ASSERT(atFirstInvisibleWhiteSpace
.IsSet());
3187 atFirstInvisibleWhiteSpace
.EqualsOrIsBefore(result
.StartRef()));
3188 result
.SetStart(atFirstInvisibleWhiteSpace
);
3189 MOZ_ASSERT(result
.IsPositionedAndValid());
3193 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3194 invisibleLeadingWhiteSpaceRange
.EndRef().IsBefore(
3195 invisibleTrailingWhiteSpaceRange
.EndRef()));
3196 const EditorDOMPoint
& afterLastInvisibleWhiteSpace
=
3197 hasInvisibleTrailingWhiteSpaces
3198 ? invisibleTrailingWhiteSpaceRange
.EndRef()
3199 : invisibleLeadingWhiteSpaceRange
.EndRef();
3200 if (afterLastInvisibleWhiteSpace
.EqualsOrIsBefore(result
.EndRef())) {
3201 MOZ_ASSERT(result
.IsPositionedAndValid());
3204 if (afterLastInvisibleWhiteSpace
.IsInTextNode()) {
3205 result
.SetEnd(afterLastInvisibleWhiteSpace
.AsInText());
3206 MOZ_ASSERT(result
.IsPositionedAndValid());
3209 const EditorDOMPointInText atLastInvisibleWhiteSpace
=
3210 hasInvisibleTrailingWhiteSpaces
3211 ? aEnd
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
)
3212 : aStart
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
);
3213 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsSet());
3214 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsContainerEmpty() ||
3215 atLastInvisibleWhiteSpace
.IsAtLastContent());
3216 MOZ_ASSERT(result
.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace
));
3217 result
.SetEnd(atLastInvisibleWhiteSpace
.IsEndOfContainer()
3218 ? atLastInvisibleWhiteSpace
3219 : atLastInvisibleWhiteSpace
.NextPoint());
3220 MOZ_ASSERT(result
.IsPositionedAndValid());
3225 Result
<EditorDOMRangeInTexts
, nsresult
>
3226 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(Element
* aEditingHost
,
3227 const EditorDOMPoint
& aPoint
) {
3228 // Corresponding to computing delete range part of
3229 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
3230 MOZ_ASSERT(aPoint
.IsSetAndValid());
3232 TextFragmentData
textFragmentDataAtCaret(aPoint
, aEditingHost
);
3233 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
3234 return Err(NS_ERROR_FAILURE
);
3236 EditorDOMPointInText atPreviousChar
=
3237 textFragmentDataAtCaret
.GetPreviousEditableCharPoint(aPoint
);
3238 if (!atPreviousChar
.IsSet()) {
3239 return EditorDOMRangeInTexts(); // There is no content in the block.
3242 // XXX When previous char point is in an empty text node, we do nothing,
3243 // but this must look odd from point of user view. We should delete
3244 // something before aPoint.
3245 if (atPreviousChar
.IsEndOfContainer()) {
3246 return EditorDOMRangeInTexts();
3249 // Extend delete range if previous char is a low surrogate following
3250 // a high surrogate.
3251 EditorDOMPointInText atNextChar
= atPreviousChar
.NextPoint();
3252 if (!atPreviousChar
.IsStartOfContainer()) {
3253 if (atPreviousChar
.IsCharLowSurrogateFollowingHighSurrogate()) {
3254 atPreviousChar
= atPreviousChar
.PreviousPoint();
3256 // If caret is in middle of a surrogate pair, delete the surrogate pair
3258 else if (atPreviousChar
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3259 atNextChar
= atNextChar
.NextPoint();
3263 // If the text node is preformatted, just remove the previous character.
3264 if (textFragmentDataAtCaret
.IsPreformatted()) {
3265 return EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3268 // If previous char is an ASCII white-spaces, delete all adjcent ASCII
3270 EditorDOMRangeInTexts rangeToDelete
;
3271 if (atPreviousChar
.IsCharASCIISpace()) {
3272 EditorDOMPointInText startToDelete
=
3273 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3275 if (!startToDelete
.IsSet()) {
3277 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3278 return Err(NS_ERROR_FAILURE
);
3280 EditorDOMPointInText endToDelete
=
3281 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(
3283 if (!endToDelete
.IsSet()) {
3284 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3285 return Err(NS_ERROR_FAILURE
);
3287 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3289 // if previous char is not an ASCII white-space, remove it.
3291 rangeToDelete
= EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3294 // If there is no removable and visible content, we should do nothing.
3295 if (rangeToDelete
.Collapsed()) {
3296 return EditorDOMRangeInTexts();
3299 // And also delete invisible white-spaces if they become visible.
3300 TextFragmentData textFragmentDataAtStart
=
3301 rangeToDelete
.StartRef() != aPoint
3302 ? TextFragmentData(rangeToDelete
.StartRef(), aEditingHost
)
3303 : textFragmentDataAtCaret
;
3304 TextFragmentData textFragmentDataAtEnd
=
3305 rangeToDelete
.EndRef() != aPoint
3306 ? TextFragmentData(rangeToDelete
.EndRef(), aEditingHost
)
3307 : textFragmentDataAtCaret
;
3308 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
3309 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3310 return Err(NS_ERROR_FAILURE
);
3312 EditorDOMRangeInTexts extendedRangeToDelete
=
3313 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3314 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3315 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3316 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3321 Result
<EditorDOMRangeInTexts
, nsresult
>
3322 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
3323 Element
* aEditingHost
, const EditorDOMPoint
& aPoint
) {
3324 // Corresponding to computing delete range part of
3325 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
3326 MOZ_ASSERT(aPoint
.IsSetAndValid());
3328 TextFragmentData
textFragmentDataAtCaret(aPoint
, aEditingHost
);
3329 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
3330 return Err(NS_ERROR_FAILURE
);
3332 EditorDOMPointInText atCaret
=
3333 textFragmentDataAtCaret
.GetInclusiveNextEditableCharPoint(aPoint
);
3334 if (!atCaret
.IsSet()) {
3335 return EditorDOMRangeInTexts(); // There is no content in the block.
3337 // If caret is in middle of a surrogate pair, we should remove next
3338 // character (blink-compat).
3339 if (!atCaret
.IsEndOfContainer() &&
3340 atCaret
.IsCharLowSurrogateFollowingHighSurrogate()) {
3341 atCaret
= atCaret
.NextPoint();
3344 // XXX When next char point is in an empty text node, we do nothing,
3345 // but this must look odd from point of user view. We should delete
3346 // something after aPoint.
3347 if (atCaret
.IsEndOfContainer()) {
3348 return EditorDOMRangeInTexts();
3351 // Extend delete range if previous char is a low surrogate following
3352 // a high surrogate.
3353 EditorDOMPointInText atNextChar
= atCaret
.NextPoint();
3354 if (atCaret
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3355 atNextChar
= atNextChar
.NextPoint();
3358 // If the text node is preformatted, just remove the previous character.
3359 if (textFragmentDataAtCaret
.IsPreformatted()) {
3360 return EditorDOMRangeInTexts(atCaret
, atNextChar
);
3363 // If next char is an ASCII whitespaces, delete all adjcent ASCII
3365 EditorDOMRangeInTexts rangeToDelete
;
3366 if (atCaret
.IsCharASCIISpace()) {
3367 EditorDOMPointInText startToDelete
=
3368 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3370 if (!startToDelete
.IsSet()) {
3372 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3373 return Err(NS_ERROR_FAILURE
);
3375 EditorDOMPointInText endToDelete
=
3376 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(atCaret
);
3377 if (!endToDelete
.IsSet()) {
3378 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3379 return Err(NS_ERROR_FAILURE
);
3381 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3383 // if next char is not an ASCII white-space, remove it.
3385 rangeToDelete
= EditorDOMRangeInTexts(atCaret
, atNextChar
);
3388 // If there is no removable and visible content, we should do nothing.
3389 if (rangeToDelete
.Collapsed()) {
3390 return EditorDOMRangeInTexts();
3393 // And also delete invisible white-spaces if they become visible.
3394 TextFragmentData textFragmentDataAtStart
=
3395 rangeToDelete
.StartRef() != aPoint
3396 ? TextFragmentData(rangeToDelete
.StartRef(), aEditingHost
)
3397 : textFragmentDataAtCaret
;
3398 TextFragmentData textFragmentDataAtEnd
=
3399 rangeToDelete
.EndRef() != aPoint
3400 ? TextFragmentData(rangeToDelete
.EndRef(), aEditingHost
)
3401 : textFragmentDataAtCaret
;
3402 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
3403 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3404 return Err(NS_ERROR_FAILURE
);
3406 EditorDOMRangeInTexts extendedRangeToDelete
=
3407 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3408 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3409 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3410 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3415 EditorDOMRange
WSRunScanner::GetRangesForDeletingAtomicContent(
3416 Element
* aEditingHost
, const nsIContent
& aAtomicContent
) {
3417 if (aAtomicContent
.IsHTMLElement(nsGkAtoms::br
)) {
3418 // Preceding white-spaces should be preserved, but the following
3419 // white-spaces should be invisible around `<br>` element.
3420 TextFragmentData
textFragmentDataAfterBRElement(
3421 EditorDOMPoint::After(aAtomicContent
), aEditingHost
);
3422 if (NS_WARN_IF(!textFragmentDataAfterBRElement
.IsInitialized())) {
3423 return EditorDOMRange(); // TODO: Make here return error with Err.
3425 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3426 textFragmentDataAfterBRElement
.GetNonCollapsedRangeInTexts(
3427 textFragmentDataAfterBRElement
3428 .InvisibleLeadingWhiteSpaceRangeRef());
3429 return followingInvisibleWhiteSpaces
.IsPositioned() &&
3430 !followingInvisibleWhiteSpaces
.Collapsed()
3432 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3433 followingInvisibleWhiteSpaces
.EndRef())
3435 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3436 EditorDOMPoint::After(aAtomicContent
));
3439 if (!HTMLEditUtils::IsBlockElement(aAtomicContent
)) {
3440 // Both preceding and following white-spaces around it should be preserved
3441 // around inline elements like `<img>`.
3442 return EditorDOMRange(
3443 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3444 EditorDOMPoint::After(aAtomicContent
));
3447 // Both preceding and following white-spaces can be invisible around a
3449 TextFragmentData
textFragmentDataBeforeAtomicContent(
3450 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)), aEditingHost
);
3451 if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent
.IsInitialized())) {
3452 return EditorDOMRange(); // TODO: Make here return error with Err.
3454 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces
=
3455 textFragmentDataBeforeAtomicContent
.GetNonCollapsedRangeInTexts(
3456 textFragmentDataBeforeAtomicContent
3457 .InvisibleTrailingWhiteSpaceRangeRef());
3458 TextFragmentData
textFragmentDataAfterAtomicContent(
3459 EditorDOMPoint::After(aAtomicContent
), aEditingHost
);
3460 if (NS_WARN_IF(!textFragmentDataAfterAtomicContent
.IsInitialized())) {
3461 return EditorDOMRange(); // TODO: Make here return error with Err.
3463 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3464 textFragmentDataAfterAtomicContent
.GetNonCollapsedRangeInTexts(
3465 textFragmentDataAfterAtomicContent
3466 .InvisibleLeadingWhiteSpaceRangeRef());
3467 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet() &&
3468 followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3469 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3470 followingInvisibleWhiteSpaces
.EndRef());
3472 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet()) {
3473 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3474 EditorDOMPoint::After(aAtomicContent
));
3476 if (followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3477 return EditorDOMRange(
3478 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3479 followingInvisibleWhiteSpaces
.EndRef());
3481 return EditorDOMRange(
3482 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3483 EditorDOMPoint::After(aAtomicContent
));
3487 EditorDOMRange
WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
3488 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
3489 const Element
& aRightBlockElement
,
3490 const EditorDOMPoint
& aPointContainingTheOtherBlock
) {
3491 MOZ_ASSERT(&aLeftBlockElement
!= &aRightBlockElement
);
3493 aPointContainingTheOtherBlock
.IsSet(),
3494 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
||
3495 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
);
3497 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
,
3498 aRightBlockElement
.IsInclusiveDescendantOf(
3499 aPointContainingTheOtherBlock
.GetChild()));
3501 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
,
3502 aLeftBlockElement
.IsInclusiveDescendantOf(
3503 aPointContainingTheOtherBlock
.GetChild()));
3505 !aPointContainingTheOtherBlock
.IsSet(),
3506 !aRightBlockElement
.IsInclusiveDescendantOf(&aLeftBlockElement
));
3508 !aPointContainingTheOtherBlock
.IsSet(),
3509 !aLeftBlockElement
.IsInclusiveDescendantOf(&aRightBlockElement
));
3510 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock
.IsSet(),
3511 EditorRawDOMPoint(const_cast<Element
*>(&aLeftBlockElement
))
3512 .IsBefore(EditorRawDOMPoint(
3513 const_cast<Element
*>(&aRightBlockElement
))));
3515 const Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3517 EditorDOMRange range
;
3518 // Include trailing invisible white-spaces in aLeftBlockElement.
3519 TextFragmentData
textFragmentDataAtEndOfLeftBlockElement(
3520 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
3521 ? aPointContainingTheOtherBlock
3522 : EditorDOMPoint::AtEndOf(const_cast<Element
&>(aLeftBlockElement
)),
3524 if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement
.IsInitialized())) {
3525 return EditorDOMRange(); // TODO: Make here return error with Err.
3527 if (textFragmentDataAtEndOfLeftBlockElement
.StartsFromInvisibleBRElement()) {
3528 // If the left block element ends with an invisible `<br>` element,
3529 // it'll be deleted (and it means there is no invisible trailing
3530 // white-spaces). Therefore, the range should start from the invisible
3532 range
.SetStart(EditorDOMPoint(
3533 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr()));
3535 const EditorDOMRange
& trailingWhiteSpaceRange
=
3536 textFragmentDataAtEndOfLeftBlockElement
3537 .InvisibleTrailingWhiteSpaceRangeRef();
3538 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
3539 range
.SetStart(trailingWhiteSpaceRange
.StartRef());
3541 range
.SetStart(textFragmentDataAtEndOfLeftBlockElement
.ScanStartRef());
3544 // Include leading invisible white-spaces in aRightBlockElement.
3545 TextFragmentData
textFragmentDataAtStartOfRightBlockElement(
3546 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
&&
3547 !aPointContainingTheOtherBlock
.IsEndOfContainer()
3548 ? aPointContainingTheOtherBlock
.NextPoint()
3549 : EditorDOMPoint(const_cast<Element
*>(&aRightBlockElement
), 0),
3551 if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement
.IsInitialized())) {
3552 return EditorDOMRange(); // TODO: Make here return error with Err.
3554 const EditorDOMRange
& leadingWhiteSpaceRange
=
3555 textFragmentDataAtStartOfRightBlockElement
3556 .InvisibleLeadingWhiteSpaceRangeRef();
3557 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
3558 range
.SetEnd(leadingWhiteSpaceRange
.EndRef());
3560 range
.SetEnd(textFragmentDataAtStartOfRightBlockElement
.ScanStartRef());
3567 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3568 Element
* aEditingHost
, const EditorDOMRange
& aRange
) {
3569 MOZ_ASSERT(aRange
.IsPositionedAndValid());
3570 MOZ_ASSERT(aRange
.EndRef().IsSetAndValid());
3571 MOZ_ASSERT(aRange
.StartRef().IsSetAndValid());
3573 EditorDOMRange result
;
3574 TextFragmentData
textFragmentDataAtStart(aRange
.StartRef(), aEditingHost
);
3575 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
3576 return EditorDOMRange(); // TODO: Make here return error with Err.
3578 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart
=
3579 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3580 textFragmentDataAtStart
.InvisibleLeadingWhiteSpaceRangeRef());
3581 if (invisibleLeadingWhiteSpacesAtStart
.IsPositioned() &&
3582 !invisibleLeadingWhiteSpacesAtStart
.Collapsed()) {
3583 result
.SetStart(invisibleLeadingWhiteSpacesAtStart
.StartRef());
3585 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart
=
3586 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3587 textFragmentDataAtStart
.InvisibleTrailingWhiteSpaceRangeRef());
3588 if (invisibleTrailingWhiteSpacesAtStart
.IsPositioned() &&
3589 !invisibleTrailingWhiteSpacesAtStart
.Collapsed()) {
3591 invisibleTrailingWhiteSpacesAtStart
.StartRef().EqualsOrIsBefore(
3592 aRange
.StartRef()));
3593 result
.SetStart(invisibleTrailingWhiteSpacesAtStart
.StartRef());
3595 // If there is no invisible white-space and the line starts with a
3596 // text node, shrink the range to start of the text node.
3597 else if (!aRange
.StartRef().IsInTextNode() &&
3598 textFragmentDataAtStart
.StartsFromBlockBoundary() &&
3599 textFragmentDataAtStart
.EndRef().IsInTextNode()) {
3600 result
.SetStart(textFragmentDataAtStart
.EndRef());
3603 if (!result
.StartRef().IsSet()) {
3604 result
.SetStart(aRange
.StartRef());
3607 TextFragmentData
textFragmentDataAtEnd(aRange
.EndRef(), aEditingHost
);
3608 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3609 return EditorDOMRange(); // TODO: Make here return error with Err.
3611 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3612 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3613 textFragmentDataAtEnd
.InvisibleTrailingWhiteSpaceRangeRef());
3614 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3615 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3616 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3618 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3619 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3620 textFragmentDataAtEnd
.InvisibleLeadingWhiteSpaceRangeRef());
3621 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3622 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3623 MOZ_ASSERT(aRange
.EndRef().EqualsOrIsBefore(
3624 invisibleLeadingWhiteSpacesAtEnd
.EndRef()));
3625 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3627 // If there is no invisible white-space and the line ends with a text
3628 // node, shrink the range to end of the text node.
3629 else if (!aRange
.EndRef().IsInTextNode() &&
3630 textFragmentDataAtEnd
.EndsByBlockBoundary() &&
3631 textFragmentDataAtEnd
.StartRef().IsInTextNode()) {
3632 result
.SetEnd(EditorDOMPoint::AtEndOf(
3633 *textFragmentDataAtEnd
.StartRef().ContainerAsText()));
3636 if (!result
.EndRef().IsSet()) {
3637 result
.SetEnd(aRange
.EndRef());
3639 MOZ_ASSERT(result
.IsPositionedAndValid());
3643 /******************************************************************************
3644 * Utilities for other things.
3645 ******************************************************************************/
3648 Result
<bool, nsresult
>
3649 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
3650 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
3651 const Element
* aEditingHost
) {
3652 MOZ_ASSERT(aRange
.IsPositioned());
3653 MOZ_ASSERT(!aRange
.IsInSelection(),
3654 "Changing range in selection may cause running script");
3656 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
3657 NS_WARN_IF(!aRange
.GetEndContainer())) {
3658 return Err(NS_ERROR_FAILURE
);
3661 if (!aRange
.GetStartContainer()->IsContent() ||
3662 !aRange
.GetEndContainer()->IsContent()) {
3666 // If the range crosses a block boundary, we should do nothing for now
3667 // because it hits a bug of inserting a padding `<br>` element after
3668 // joining the blocks.
3669 if (HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3670 *aRange
.GetStartContainer()->AsContent(), aEditingHost
) !=
3671 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3672 *aRange
.GetEndContainer()->AsContent(), aEditingHost
)) {
3676 nsIContent
* startContent
= nullptr;
3677 if (aRange
.GetStartContainer() && aRange
.GetStartContainer()->IsText() &&
3678 aRange
.GetStartContainer()->AsText()->Length() == aRange
.StartOffset()) {
3679 // If next content is a visible `<br>` element, special inline content
3680 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3681 // element like `<hr>`, the range should start with it.
3682 TextFragmentData
textFragmentDataAtStart(
3683 EditorRawDOMPoint(aRange
.StartRef()), aEditingHost
);
3684 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
3685 return Err(NS_ERROR_FAILURE
);
3687 if (textFragmentDataAtStart
.EndsByVisibleBRElement()) {
3688 startContent
= textFragmentDataAtStart
.EndReasonBRElementPtr();
3689 } else if (textFragmentDataAtStart
.EndsBySpecialContent() ||
3690 (textFragmentDataAtStart
.EndsByOtherBlockElement() &&
3691 !HTMLEditUtils::IsContainerNode(
3692 *textFragmentDataAtStart
3693 .EndReasonOtherBlockElementPtr()))) {
3694 startContent
= textFragmentDataAtStart
.GetEndReasonContent();
3698 nsIContent
* endContent
= nullptr;
3699 if (aRange
.GetEndContainer() && aRange
.GetEndContainer()->IsText() &&
3700 !aRange
.EndOffset()) {
3701 // If previous content is a visible `<br>` element, special inline content
3702 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3703 // element like `<hr>`, the range should end after it.
3704 TextFragmentData
textFragmentDataAtEnd(EditorRawDOMPoint(aRange
.EndRef()),
3706 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3707 return Err(NS_ERROR_FAILURE
);
3709 if (textFragmentDataAtEnd
.StartsFromVisibleBRElement()) {
3710 endContent
= textFragmentDataAtEnd
.StartReasonBRElementPtr();
3711 } else if (textFragmentDataAtEnd
.StartsFromSpecialContent() ||
3712 (textFragmentDataAtEnd
.StartsFromOtherBlockElement() &&
3713 !HTMLEditUtils::IsContainerNode(
3714 *textFragmentDataAtEnd
3715 .StartReasonOtherBlockElementPtr()))) {
3716 endContent
= textFragmentDataAtEnd
.GetStartReasonContent();
3720 if (!startContent
&& !endContent
) {
3724 nsresult rv
= aRange
.SetStartAndEnd(
3725 startContent
? RangeBoundary(
3726 startContent
->GetParentNode(),
3727 startContent
->GetPreviousSibling()) // at startContent
3728 : aRange
.StartRef(),
3729 endContent
? RangeBoundary(endContent
->GetParentNode(),
3730 endContent
) // after endContent
3732 if (NS_FAILED(rv
)) {
3733 NS_WARNING("nsRange::SetStartAndEnd() failed");
3739 } // namespace mozilla