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
;
42 const char16_t kNBSP
= 160;
44 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
45 const EditorDOMPoint
& aPoint
) const;
46 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
47 const EditorRawDOMPoint
& aPoint
) const;
48 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
49 const EditorDOMPoint
& aPoint
) const;
50 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
51 const EditorRawDOMPoint
& aPoint
) const;
52 template EditorDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
53 Text
& aTextNode
, const Element
* aAncestorLimiter
);
54 template EditorRawDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
55 Text
& aTextNode
, const Element
* aAncestorLimiter
);
56 template EditorDOMPoint
WSRunScanner::GetFirstVisiblePoint(
57 Text
& aTextNode
, const Element
* aAncestorLimiter
);
58 template EditorRawDOMPoint
WSRunScanner::GetFirstVisiblePoint(
59 Text
& aTextNode
, const Element
* aAncestorLimiter
);
61 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
62 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aScanStartPoint
);
63 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
64 HTMLEditor
& aHTMLEditor
, const EditorRawDOMPoint
& aScanStartPoint
);
65 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
66 HTMLEditor
& aHTMLEditor
, const EditorDOMPointInText
& aScanStartPoint
);
68 template WSRunScanner::TextFragmentData::TextFragmentData(
69 const EditorDOMPoint
& aPoint
, const Element
* aEditingHost
);
70 template WSRunScanner::TextFragmentData::TextFragmentData(
71 const EditorRawDOMPoint
& aPoint
, const Element
* aEditingHost
);
72 template WSRunScanner::TextFragmentData::TextFragmentData(
73 const EditorDOMPointInText
& aPoint
, const Element
* aEditingHost
);
75 nsresult
WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
76 HTMLEditor
& aHTMLEditor
, nsCOMPtr
<nsINode
>* aSplitNode
,
77 int32_t* aSplitOffset
) {
78 if (NS_WARN_IF(!aSplitNode
) || NS_WARN_IF(!*aSplitNode
) ||
79 NS_WARN_IF(!aSplitOffset
)) {
80 return NS_ERROR_INVALID_ARG
;
83 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), aSplitNode
,
86 nsresult rv
= WhiteSpaceVisibilityKeeper::
87 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
88 aHTMLEditor
, EditorDOMPoint(*aSplitNode
, *aSplitOffset
));
89 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
90 "WhiteSpaceVisibilityKeeper::"
91 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() "
97 EditActionResult
WhiteSpaceVisibilityKeeper::
98 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
99 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
100 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
101 const Maybe
<nsAtom
*>& aListElementTagName
,
102 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
104 EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
105 MOZ_ASSERT(&aRightBlockElement
== aAtRightBlockChild
.GetContainer());
107 // NOTE: This method may extend deletion range:
108 // - to delete invisible white-spaces at end of aLeftBlockElement
109 // - to delete invisible white-spaces at start of
110 // afterRightBlockChild.GetChild()
111 // - to delete invisible white-spaces before afterRightBlockChild.GetChild()
112 // - to delete invisible `<br>` element at end of aLeftBlockElement
114 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
116 EditorDOMPoint afterRightBlockChild
= aAtRightBlockChild
.NextPoint();
117 MOZ_ASSERT(afterRightBlockChild
.IsSetAndValid());
118 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
119 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
122 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
123 "failed at left block");
124 return EditActionResult(rv
);
126 if (!afterRightBlockChild
.IsSetAndValid()) {
128 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
129 "running script and the point to be modified was changed");
130 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
133 OwningNonNull
<Element
> rightBlockElement
= aRightBlockElement
;
135 // We can't just track rightBlockElement because it's an Element.
136 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(),
137 &afterRightBlockChild
);
138 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
139 aHTMLEditor
, afterRightBlockChild
);
142 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
143 "failed at right block child");
144 return EditActionResult(rv
);
147 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
148 // Do we really need to do update rightBlockElement here??
149 // XXX And afterRightBlockChild.GetContainerAsElement() always returns
150 // an element pointer so that probably here should not use
151 // accessors of EditorDOMPoint, should use DOM API directly instead.
152 if (afterRightBlockChild
.GetContainerAsElement()) {
153 rightBlockElement
= *afterRightBlockChild
.GetContainerAsElement();
154 } else if (NS_WARN_IF(
155 !afterRightBlockChild
.GetContainerParentAsElement())) {
156 return EditActionResult(NS_ERROR_UNEXPECTED
);
158 rightBlockElement
= *afterRightBlockChild
.GetContainerParentAsElement();
163 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
164 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
165 aHTMLEditor
.GetActiveEditingHost(),
166 EditorDOMPoint::AtEndOf(aLeftBlockElement
));
168 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
169 "The preceding invisible BR element computation was different");
170 EditActionResult
ret(NS_OK
);
171 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
172 // AutoInclusiveAncestorBlockElementsJoiner.
173 if (NS_WARN_IF(aListElementTagName
.isSome())) {
174 // Since 2002, here was the following comment:
175 // > The idea here is to take all children in rightListElement that are
176 // > past offset, and pull them into leftlistElement.
177 // However, this has never been performed because we are here only when
178 // neither left list nor right list is a descendant of the other but
179 // in such case, getting a list item in the right list node almost
180 // always failed since a variable for offset of
181 // rightListElement->GetChildAt() was not initialized. So, it might be
182 // a bug, but we should keep this traditional behavior for now. If you
183 // find when we get here, please remove this comment if we don't need to
184 // do it. Otherwise, please move children of the right list node to the
185 // end of the left list node.
187 // XXX Although, we do nothing here, but for keeping traditional
188 // behavior, we should mark as handled.
191 // XXX Why do we ignore the result of MoveOneHardLineContents()?
192 NS_ASSERTION(rightBlockElement
== afterRightBlockChild
.GetContainer(),
193 "The relation is not guaranteed but assumed");
195 Result
<bool, nsresult
> firstLineHasContent
=
196 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(EditorRawDOMPoint(
197 rightBlockElement
, afterRightBlockChild
.Offset()));
198 #endif // #ifdef DEBUG
199 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
200 EditorDOMPoint(rightBlockElement
, afterRightBlockChild
.Offset()),
201 EditorDOMPoint(&aLeftBlockElement
, 0),
202 HTMLEditor::MoveToEndOfContainer::Yes
);
203 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
204 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
206 NS_WARNING_ASSERTION(moveNodeResult
.Succeeded(),
207 "HTMLEditor::MoveOneHardLineContents("
208 "MoveToEndOfContainer::Yes) failed, but ignored");
209 if (moveNodeResult
.Succeeded()) {
211 MOZ_ASSERT(!firstLineHasContent
.isErr());
212 if (firstLineHasContent
.inspect()) {
213 NS_ASSERTION(moveNodeResult
.Handled(),
214 "Failed to consider whether moving or not something");
216 NS_ASSERTION(moveNodeResult
.Ignored(),
217 "Failed to consider whether moving or not something");
219 #endif // #ifdef DEBUG
220 ret
|= moveNodeResult
;
222 // Now, all children of rightBlockElement were moved to leftBlockElement.
223 // So, afterRightBlockChild is now invalid.
224 afterRightBlockChild
.Clear();
227 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
231 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
232 *invisibleBRElementAtEndOfLeftBlockElement
);
233 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
234 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
237 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
238 return EditActionResult(rv
);
240 return EditActionHandled();
244 EditActionResult
WhiteSpaceVisibilityKeeper::
245 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
246 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
247 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
248 nsIContent
& aLeftContentInBlock
,
249 const Maybe
<nsAtom
*>& aListElementTagName
,
250 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
252 EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
254 &aLeftBlockElement
== &aLeftContentInBlock
||
255 EditorUtils::IsDescendantOf(aLeftContentInBlock
, aLeftBlockElement
));
256 MOZ_ASSERT(&aLeftBlockElement
== aAtLeftBlockChild
.GetContainer());
258 // NOTE: This method may extend deletion range:
259 // - to delete invisible white-spaces at start of aRightBlockElement
260 // - to delete invisible white-spaces before aRightBlockElement
261 // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild()
262 // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild()
263 // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
265 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
267 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
268 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0));
271 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() failed "
273 return EditActionResult(rv
);
275 if (!aAtLeftBlockChild
.IsSetAndValid()) {
277 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
278 "running script and the point to be modified was changed");
279 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
282 OwningNonNull
<Element
> originalLeftBlockElement
= aLeftBlockElement
;
283 OwningNonNull
<Element
> leftBlockElement
= aLeftBlockElement
;
284 EditorDOMPoint
atLeftBlockChild(aAtLeftBlockChild
);
286 // We can't just track leftBlockElement because it's an Element, so track
288 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &atLeftBlockChild
);
289 nsresult rv
= WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
290 aHTMLEditor
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
291 atLeftBlockChild
.Offset()));
294 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
295 "failed at left block child");
296 return EditActionResult(rv
);
298 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
299 // Do we really need to do update aRightBlockElement here??
300 // XXX And atLeftBlockChild.GetContainerAsElement() always returns
301 // an element pointer so that probably here should not use
302 // accessors of EditorDOMPoint, should use DOM API directly instead.
303 if (atLeftBlockChild
.GetContainerAsElement()) {
304 leftBlockElement
= *atLeftBlockChild
.GetContainerAsElement();
305 } else if (NS_WARN_IF(!atLeftBlockChild
.GetContainerParentAsElement())) {
306 return EditActionResult(NS_ERROR_UNEXPECTED
);
308 leftBlockElement
= *atLeftBlockChild
.GetContainerParentAsElement();
313 RefPtr
<HTMLBRElement
> invisibleBRElementBeforeLeftBlockElement
=
314 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
315 aHTMLEditor
.GetActiveEditingHost(), atLeftBlockChild
);
317 aPrecedingInvisibleBRElement
== invisibleBRElementBeforeLeftBlockElement
,
318 "The preceding invisible BR element computation was different");
319 EditActionResult
ret(NS_OK
);
320 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
321 // AutoInclusiveAncestorBlockElementsJoiner.
322 if (aListElementTagName
.isSome()) {
323 // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
324 MOZ_ASSERT(originalLeftBlockElement
== atLeftBlockChild
.GetContainer(),
325 "This is not guaranteed, but assumed");
327 Result
<bool, nsresult
> rightBlockHasContent
=
328 aHTMLEditor
.CanMoveChildren(aRightBlockElement
, aLeftBlockElement
);
329 #endif // #ifdef DEBUG
330 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveChildrenWithTransaction(
331 aRightBlockElement
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
332 atLeftBlockChild
.Offset()));
333 if (NS_WARN_IF(moveNodeResult
.EditorDestroyed())) {
334 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
336 NS_WARNING_ASSERTION(
337 moveNodeResult
.Succeeded(),
338 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
339 if (moveNodeResult
.Succeeded()) {
340 ret
|= moveNodeResult
;
342 MOZ_ASSERT(!rightBlockHasContent
.isErr());
343 if (rightBlockHasContent
.inspect()) {
344 NS_ASSERTION(moveNodeResult
.Handled(),
345 "Failed to consider whether moving or not children");
347 NS_ASSERTION(moveNodeResult
.Ignored(),
348 "Failed to consider whether moving or not children");
350 #endif // #ifdef DEBUG
352 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
353 atLeftBlockChild
.Clear();
355 // Left block is a parent of right block, and the parent of the previous
356 // visible content. Right block is a child and contains the contents we
359 EditorDOMPoint atPreviousContent
;
360 if (&aLeftContentInBlock
== leftBlockElement
) {
361 // We are working with valid HTML, aLeftContentInBlock is a block node,
362 // and is therefore allowed to contain aRightBlockElement. This is the
363 // simple case, we will simply move the content in aRightBlockElement
365 atPreviousContent
= atLeftBlockChild
;
367 // We try to work as well as possible with HTML that's already invalid.
368 // Although "right block" is a block, and a block must not be contained
369 // in inline elements, reality is that broken documents do exist. The
370 // DIRECT parent of "left NODE" might be an inline element. Previous
371 // versions of this code skipped inline parents until the first block
372 // parent was found (and used "left block" as the destination).
373 // However, in some situations this strategy moves the content to an
374 // unexpected position. (see bug 200416) The new idea is to make the
375 // moving content a sibling, next to the previous visible content.
376 atPreviousContent
.Set(&aLeftContentInBlock
);
378 // We want to move our content just after the previous visible node.
379 atPreviousContent
.AdvanceOffset();
382 MOZ_ASSERT(atPreviousContent
.IsSet());
384 // Because we don't want the moving content to receive the style of the
385 // previous content, we split the previous content's style.
388 Result
<bool, nsresult
> firstLineHasContent
=
389 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
390 EditorRawDOMPoint(&aRightBlockElement
, 0));
391 #endif // #ifdef DEBUG
393 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
394 // XXX It's odd to continue handling this edit action if there is no
396 if (!editingHost
|| &aLeftContentInBlock
!= editingHost
) {
397 SplitNodeResult splitResult
=
398 aHTMLEditor
.SplitAncestorStyledInlineElementsAt(atPreviousContent
,
400 if (splitResult
.Failed()) {
401 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
402 return EditActionResult(splitResult
.Rv());
405 if (splitResult
.Handled()) {
406 if (splitResult
.GetNextNode()) {
407 atPreviousContent
.Set(splitResult
.GetNextNode());
408 if (!atPreviousContent
.IsSet()) {
409 NS_WARNING("Next node of split point was orphaned");
410 return EditActionResult(NS_ERROR_NULL_POINTER
);
413 atPreviousContent
= splitResult
.SplitPoint();
414 if (!atPreviousContent
.IsSet()) {
415 NS_WARNING("Split node was orphaned");
416 return EditActionResult(NS_ERROR_NULL_POINTER
);
422 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
423 EditorDOMPoint(&aRightBlockElement
, 0), atPreviousContent
);
424 if (moveNodeResult
.Failed()) {
425 NS_WARNING("HTMLEditor::MoveOneHardLineContents() failed");
426 return EditActionResult(moveNodeResult
.Rv());
430 MOZ_ASSERT(!firstLineHasContent
.isErr());
431 if (firstLineHasContent
.inspect()) {
432 NS_ASSERTION(moveNodeResult
.Handled(),
433 "Failed to consider whether moving or not something");
435 NS_ASSERTION(moveNodeResult
.Ignored(),
436 "Failed to consider whether moving or not something");
438 #endif // #ifdef DEBUG
440 ret
|= moveNodeResult
;
443 if (!invisibleBRElementBeforeLeftBlockElement
) {
447 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
448 *invisibleBRElementBeforeLeftBlockElement
);
449 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
450 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
453 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
454 return EditActionResult(rv
);
456 return EditActionHandled();
460 EditActionResult
WhiteSpaceVisibilityKeeper::
461 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
462 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
463 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
464 const HTMLBRElement
* aPrecedingInvisibleBRElement
) {
466 !EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
468 !EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
470 // NOTE: This method may extend deletion range:
471 // - to delete invisible white-spaces at end of aLeftBlockElement
472 // - to delete invisible white-spaces at start of aRightBlockElement
473 // - to delete invisible `<br>` element at end of aLeftBlockElement
475 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
477 // Adjust white-space at block boundaries
478 nsresult rv
= WhiteSpaceVisibilityKeeper::
479 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
481 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement
),
482 EditorDOMPoint(&aRightBlockElement
, 0)));
485 "WhiteSpaceVisibilityKeeper::"
486 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
487 return EditActionResult(rv
);
490 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
491 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
492 aHTMLEditor
.GetActiveEditingHost(),
493 EditorDOMPoint::AtEndOf(aLeftBlockElement
));
495 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
496 "The preceding invisible BR element computation was different");
497 EditActionResult
ret(NS_OK
);
498 if (aListElementTagName
.isSome() ||
499 aLeftBlockElement
.NodeInfo()->NameAtom() ==
500 aRightBlockElement
.NodeInfo()->NameAtom()) {
501 // Nodes are same type. merge them.
502 EditorDOMPoint atFirstChildOfRightNode
;
503 nsresult rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
504 aLeftBlockElement
, aRightBlockElement
, &atFirstChildOfRightNode
);
505 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
506 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
508 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
509 "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
510 " failed, but ignored");
511 if (aListElementTagName
.isSome() && atFirstChildOfRightNode
.IsSet()) {
512 CreateElementResult convertListTypeResult
=
513 aHTMLEditor
.ChangeListElementType(
514 aRightBlockElement
, MOZ_KnownLive(*aListElementTagName
.ref()),
516 if (NS_WARN_IF(convertListTypeResult
.EditorDestroyed())) {
517 return EditActionResult(NS_ERROR_EDITOR_DESTROYED
);
519 NS_WARNING_ASSERTION(
520 convertListTypeResult
.Succeeded(),
521 "HTMLEditor::ChangeListElementType() failed, but ignored");
526 Result
<bool, nsresult
> firstLineHasContent
=
527 aHTMLEditor
.CanMoveOrDeleteSomethingInHardLine(
528 EditorRawDOMPoint(&aRightBlockElement
, 0));
529 #endif // #ifdef DEBUG
531 // Nodes are dissimilar types.
532 MoveNodeResult moveNodeResult
= aHTMLEditor
.MoveOneHardLineContents(
533 EditorDOMPoint(&aRightBlockElement
, 0),
534 EditorDOMPoint(&aLeftBlockElement
, 0),
535 HTMLEditor::MoveToEndOfContainer::Yes
);
536 if (moveNodeResult
.Failed()) {
538 "HTMLEditor::MoveOneHardLineContents(MoveToEndOfContainer::Yes) "
540 return EditActionResult(moveNodeResult
.Rv());
544 MOZ_ASSERT(!firstLineHasContent
.isErr());
545 if (firstLineHasContent
.inspect()) {
546 NS_ASSERTION(moveNodeResult
.Handled(),
547 "Failed to consider whether moving or not something");
549 NS_ASSERTION(moveNodeResult
.Ignored(),
550 "Failed to consider whether moving or not something");
552 #endif // #ifdef DEBUG
553 ret
|= moveNodeResult
;
556 if (!invisibleBRElementAtEndOfLeftBlockElement
) {
557 return ret
.MarkAsHandled();
560 rv
= aHTMLEditor
.DeleteNodeWithTransaction(
561 *invisibleBRElementAtEndOfLeftBlockElement
);
562 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
563 return ret
.SetResult(NS_ERROR_EDITOR_DESTROYED
);
565 // XXX In other top level if blocks, the result of
566 // DeleteNodeWithTransaction() is ignored. Why does only this result
569 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
570 return EditActionResult(rv
);
572 return EditActionHandled();
576 Result
<RefPtr
<Element
>, nsresult
> WhiteSpaceVisibilityKeeper::InsertBRElement(
577 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
) {
578 if (NS_WARN_IF(!aPointToInsert
.IsSet())) {
579 return Err(NS_ERROR_INVALID_ARG
);
582 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
583 // meanwhile, the pre case is handled in HandleInsertText() in
584 // HTMLEditSubActionHandler.cpp
586 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
587 TextFragmentData
textFragmentDataAtInsertionPoint(aPointToInsert
,
589 if (NS_WARN_IF(!textFragmentDataAtInsertionPoint
.IsInitialized())) {
590 return Err(NS_ERROR_FAILURE
);
592 const EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine
=
593 textFragmentDataAtInsertionPoint
594 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
595 const EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine
=
596 textFragmentDataAtInsertionPoint
597 .GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
598 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpaces
=
599 !invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned() ||
600 !invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()
601 ? Some(textFragmentDataAtInsertionPoint
.VisibleWhiteSpacesDataRef())
603 const PointPosition pointPositionWithVisibleWhiteSpaces
=
604 visibleWhiteSpaces
.isSome() && visibleWhiteSpaces
.ref().IsInitialized()
605 ? visibleWhiteSpaces
.ref().ComparePoint(aPointToInsert
)
606 : PointPosition::NotInSameDOMTree
;
608 EditorDOMPoint
pointToInsert(aPointToInsert
);
610 // Some scoping for AutoTrackDOMPoint. This will track our insertion
611 // point while we tweak any surrounding white-space
612 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToInsert
);
614 if (invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()) {
615 if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Collapsed()) {
616 // XXX Why don't we remove all of the invisible white-spaces?
617 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef() ==
619 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
620 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef(),
621 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.EndRef(),
622 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
625 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
630 // If new line will start with visible white-spaces, it needs to be start
632 else if (pointPositionWithVisibleWhiteSpaces
==
633 PointPosition::StartOfFragment
||
634 pointPositionWithVisibleWhiteSpaces
==
635 PointPosition::MiddleOfFragment
) {
636 EditorRawDOMPointInText atNextCharOfInsertionPoint
=
637 textFragmentDataAtInsertionPoint
.GetInclusiveNextEditableCharPoint(
639 if (atNextCharOfInsertionPoint
.IsSet() &&
640 !atNextCharOfInsertionPoint
.IsEndOfContainer() &&
641 atNextCharOfInsertionPoint
.IsCharASCIISpace() &&
642 !EditorUtils::IsContentPreformatted(
643 *atNextCharOfInsertionPoint
.ContainerAsText())) {
644 EditorRawDOMPointInText atPreviousCharOfNextCharOfInsertionPoint
=
645 textFragmentDataAtInsertionPoint
.GetPreviousEditableCharPoint(
646 atNextCharOfInsertionPoint
);
647 if (!atPreviousCharOfNextCharOfInsertionPoint
.IsSet() ||
648 atPreviousCharOfNextCharOfInsertionPoint
.IsEndOfContainer() ||
649 !atPreviousCharOfNextCharOfInsertionPoint
.IsCharASCIISpace()) {
650 // We are at start of non-nbsps. Convert to a single nbsp.
651 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
652 textFragmentDataAtInsertionPoint
653 .GetEndOfCollapsibleASCIIWhiteSpaces(
654 atNextCharOfInsertionPoint
);
656 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
658 EditorDOMRangeInTexts(atNextCharOfInsertionPoint
,
659 endOfCollapsibleASCIIWhiteSpaces
),
660 nsDependentSubstring(&kNBSP
, 1));
663 "WhiteSpaceVisibilityKeeper::"
664 "ReplaceTextAndRemoveEmptyTextNodes() failed");
671 if (invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned()) {
672 if (!invisibleLeadingWhiteSpaceRangeOfNewLine
.Collapsed()) {
673 // XXX Why don't we remove all of the invisible white-spaces?
674 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef() ==
676 // XXX If the DOM tree has been changed above,
677 // invisibleLeadingWhiteSpaceRangeOfNewLine may be invalid now.
678 // So, we may do something wrong here.
679 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
680 invisibleLeadingWhiteSpaceRangeOfNewLine
.StartRef(),
681 invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef(),
682 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
685 "WhiteSpaceVisibilityKeeper::"
686 "DeleteTextAndTextNodesWithTransaction() failed");
691 // If the `<br>` element is put immediately after an NBSP, it should be
692 // replaced with an ASCII white-space.
693 else if (pointPositionWithVisibleWhiteSpaces
==
694 PointPosition::MiddleOfFragment
||
695 pointPositionWithVisibleWhiteSpaces
==
696 PointPosition::EndOfFragment
) {
697 // XXX If the DOM tree has been changed above, pointToInsert` and/or
698 // `visibleWhiteSpaces` may be invalid. So, we may do
699 // something wrong here.
700 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
701 textFragmentDataAtInsertionPoint
702 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
704 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
705 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
706 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
707 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace
.ContainerAsText()),
708 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
710 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
717 RefPtr
<Element
> newBRElement
= aHTMLEditor
.InsertBRElementWithTransaction(
718 pointToInsert
, nsIEditor::eNone
);
719 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
720 return Err(NS_ERROR_EDITOR_DESTROYED
);
723 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
724 return Err(NS_ERROR_FAILURE
);
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 aHTMLEditor
.GetPriorHTMLSibling(&aContentToDelete
);
1206 // Delete the node, and join like nodes if appropriate
1207 rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContentToDelete
);
1208 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1209 return NS_ERROR_EDITOR_DESTROYED
;
1211 if (NS_FAILED(rv
)) {
1212 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1215 // Are they both text nodes? If so, join them!
1216 // XXX This may cause odd behavior if there is non-editable nodes
1217 // around the atomic content.
1218 if (!aCaretPoint
.IsInTextNode() || !previousEditableSibling
||
1219 !previousEditableSibling
->IsText()) {
1223 nsIContent
* nextEditableSibling
=
1224 aHTMLEditor
.GetNextHTMLSibling(previousEditableSibling
);
1225 if (aCaretPoint
.GetContainer() != nextEditableSibling
) {
1228 EditorDOMPoint atFirstChildOfRightNode
;
1229 rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
1230 *previousEditableSibling
,
1231 MOZ_KnownLive(*aCaretPoint
.GetContainerAsText()),
1232 &atFirstChildOfRightNode
);
1233 if (NS_FAILED(rv
)) {
1234 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1237 if (!atFirstChildOfRightNode
.IsSet()) {
1239 "HTMLEditor::JoinNearestEditableNodesWithTransaction() didn't return "
1240 "right node position");
1241 return NS_ERROR_FAILURE
;
1244 rv
= aHTMLEditor
.CollapseSelectionTo(atFirstChildOfRightNode
);
1245 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1246 "HTMLEditor::CollapseSelectionTo() failed");
1250 template <typename PT
, typename CT
>
1251 WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1252 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1253 MOZ_ASSERT(aPoint
.IsSet());
1255 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1256 return WSScanResult(nullptr, WSType::UnexpectedError
);
1259 // If the range has visible text and start of the visible text is before
1260 // aPoint, return previous character in the text.
1261 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1262 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1263 if (visibleWhiteSpaces
.IsInitialized() &&
1264 visibleWhiteSpaces
.StartRef().IsBefore(aPoint
)) {
1265 // If the visible things are not editable, we shouldn't scan "editable"
1266 // things now. Whether keep scanning editable things or not should be
1267 // considered by the caller.
1268 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1269 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
);
1271 EditorDOMPointInText atPreviousChar
= GetPreviousEditableCharPoint(aPoint
);
1272 // When it's a non-empty text node, return it.
1273 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsContainerEmpty()) {
1274 MOZ_ASSERT(!atPreviousChar
.IsEndOfContainer());
1275 return WSScanResult(atPreviousChar
.NextPoint(),
1276 atPreviousChar
.IsCharASCIISpaceOrNBSP()
1277 ? WSType::NormalWhiteSpaces
1278 : WSType::NormalText
);
1282 // Otherwise, return the start of the range.
1283 if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
1284 TextFragmentDataAtStartRef().StartRef().GetContainer()) {
1285 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
1287 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1288 TextFragmentDataAtStartRef().StartRawReason());
1290 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1291 TextFragmentDataAtStartRef().StartRawReason());
1294 template <typename PT
, typename CT
>
1295 WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
1296 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1297 MOZ_ASSERT(aPoint
.IsSet());
1299 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1300 return WSScanResult(nullptr, WSType::UnexpectedError
);
1303 // If the range has visible text and aPoint equals or is before the end of the
1304 // visible text, return inclusive next character in the text.
1305 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1306 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1307 if (visibleWhiteSpaces
.IsInitialized() &&
1308 aPoint
.EqualsOrIsBefore(visibleWhiteSpaces
.EndRef())) {
1309 // If the visible things are not editable, we shouldn't scan "editable"
1310 // things now. Whether keep scanning editable things or not should be
1311 // considered by the caller.
1312 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1313 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
);
1315 EditorDOMPointInText atNextChar
= GetInclusiveNextEditableCharPoint(aPoint
);
1316 // When it's a non-empty text node, return it.
1317 if (atNextChar
.IsSet() && !atNextChar
.IsContainerEmpty()) {
1318 return WSScanResult(
1320 !atNextChar
.IsEndOfContainer() && atNextChar
.IsCharASCIISpaceOrNBSP()
1321 ? WSType::NormalWhiteSpaces
1322 : WSType::NormalText
);
1326 // Otherwise, return the end of the range.
1327 if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
1328 TextFragmentDataAtStartRef().EndRef().GetContainer()) {
1329 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
1331 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1332 TextFragmentDataAtStartRef().EndRawReason());
1334 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1335 TextFragmentDataAtStartRef().EndRawReason());
1338 template <typename EditorDOMPointType
>
1339 WSRunScanner::TextFragmentData::TextFragmentData(
1340 const EditorDOMPointType
& aPoint
, const Element
* aEditingHost
)
1341 : mEditingHost(aEditingHost
), mIsPreformatted(false) {
1342 if (!aPoint
.IsSetAndValid()) {
1343 NS_WARNING("aPoint was invalid");
1346 if (!aPoint
.IsInContentNode()) {
1347 NS_WARNING("aPoint was in Document or DocumentFragment");
1348 // I.e., we're try to modify outside of root element. We don't need to
1349 // support such odd case because web apps cannot append text nodes as
1350 // direct child of Document node.
1354 mScanStartPoint
= aPoint
;
1355 NS_ASSERTION(EditorUtils::IsEditableContent(
1356 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
1357 "Given content is not editable");
1359 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
1360 "Given content is not an element and an orphan node");
1361 if (NS_WARN_IF(!EditorUtils::IsEditableContent(
1362 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
))) {
1365 Element
* editableBlockParentOrTopmostEditableInlineElement
= HTMLEditUtils::
1366 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
1367 *mScanStartPoint
.ContainerAsContent());
1368 if (!editableBlockParentOrTopmostEditableInlineElement
) {
1371 "GetInclusiveAncestorEditableBlockElementOrInlineEditingHost() "
1372 "couldn't find editing host");
1376 mStart
= BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1377 mScanStartPoint
, *editableBlockParentOrTopmostEditableInlineElement
,
1378 mEditingHost
, &mNBSPData
);
1379 mEnd
= BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1380 mScanStartPoint
, *editableBlockParentOrTopmostEditableInlineElement
,
1381 mEditingHost
, &mNBSPData
);
1382 // If scan start point is start/end of preformatted text node, only
1383 // mEnd/mStart crosses a preformatted character so that when one of
1384 // them crosses a preformatted character, this fragment's range is
1386 // Additionally, if the scan start point is preformatted, and there is
1387 // no text node around it, the range is also preformatted.
1388 mIsPreformatted
= mStart
.AcrossPreformattedCharacter() ||
1389 mEnd
.AcrossPreformattedCharacter() ||
1390 (EditorUtils::IsContentPreformatted(
1391 *mScanStartPoint
.ContainerAsContent()) &&
1392 !mStart
.IsNormalText() && !mEnd
.IsNormalText());
1396 template <typename EditorDOMPointType
>
1397 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1398 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1399 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1400 MOZ_ASSERT(aPoint
.IsSetAndValid());
1401 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1402 MOZ_DIAGNOSTIC_ASSERT(
1403 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1405 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1406 for (uint32_t i
= std::min(aPoint
.Offset(), textFragment
.GetLength()); i
;
1408 char16_t ch
= textFragment
.CharAt(i
- 1);
1409 if (nsCRT::IsAsciiSpace(ch
)) {
1413 if (ch
== HTMLEditUtils::kNBSP
) {
1415 aNBSPData
->NotifyNBSP(
1416 EditorDOMPointInText(aPoint
.ContainerAsText(), i
- 1),
1417 NoBreakingSpaceData::Scanning::Backward
);
1422 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1423 *aPoint
.ContainerAsText(), WSType::NormalText
,
1431 template <typename EditorDOMPointType
>
1432 WSRunScanner::TextFragmentData::BoundaryData
WSRunScanner::TextFragmentData::
1433 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1434 const EditorDOMPointType
& aPoint
,
1435 const Element
& aEditableBlockParentOrTopmostEditableInlineContent
,
1436 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1437 MOZ_ASSERT(aPoint
.IsSetAndValid());
1439 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer()) {
1440 // If the point is in a text node which is preformatted, we should return
1441 // the point as a visible character point.
1442 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1443 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1446 // If the text node is not preformatted, we should look for its preceding
1448 Maybe
<BoundaryData
> startInTextNode
=
1449 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint
,
1451 if (startInTextNode
.isSome()) {
1452 return startInTextNode
.ref();
1454 // The text node does not have visible character, let's keep scanning
1456 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1457 EditorDOMPoint(aPoint
.ContainerAsText(), 0),
1458 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1462 // Then, we need to check previous leaf node.
1463 nsIContent
* previousLeafContentOrBlock
=
1464 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
1465 aPoint
, aEditableBlockParentOrTopmostEditableInlineContent
,
1466 {LeafNodeType::LeafNodeOrNonEditableNode
}, aEditingHost
);
1467 if (!previousLeafContentOrBlock
) {
1468 // no prior node means we exhausted
1469 // aEditableBlockParentOrTopmostEditableInlineContent
1470 // mReasonContent can be either a block element or any non-editable
1471 // content in this case.
1472 return BoundaryData(aPoint
,
1473 const_cast<Element
&>(
1474 aEditableBlockParentOrTopmostEditableInlineContent
),
1475 WSType::CurrentBlockBoundary
, Preformatted::No
);
1478 if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock
)) {
1479 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1480 WSType::OtherBlockBoundary
, Preformatted::No
);
1483 if (!previousLeafContentOrBlock
->IsText() ||
1484 !previousLeafContentOrBlock
->IsEditable()) {
1485 // it's a break or a special node, like <img>, that is not a block and
1486 // not a break but still serves as a terminator to ws runs.
1487 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1488 previousLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1490 : WSType::SpecialContent
,
1494 if (!previousLeafContentOrBlock
->AsText()->TextLength()) {
1495 // If it's an empty text node, keep looking for its previous leaf content.
1496 // Note that even if the empty text node is preformatted, we should keep
1497 // looking for the previous one.
1498 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1499 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1500 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1504 if (EditorUtils::IsContentPreformatted(*previousLeafContentOrBlock
)) {
1505 // If the previous text node is preformatted and not empty, we should return
1506 // its end as found a visible character. Note that we stop scanning
1507 // collapsible white-spaces due to reaching preformatted non-empty text
1508 // node. I.e., the following text node might be not preformatted.
1509 return BoundaryData(EditorDOMPoint::AtEndOf(*previousLeafContentOrBlock
),
1510 *previousLeafContentOrBlock
, WSType::NormalText
,
1514 Maybe
<BoundaryData
> startInTextNode
=
1515 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1516 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock
->AsText()),
1518 if (startInTextNode
.isSome()) {
1519 return startInTextNode
.ref();
1522 // The text node does not have visible character, let's keep scanning
1524 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1525 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
1526 aEditableBlockParentOrTopmostEditableInlineContent
, aEditingHost
,
1531 template <typename EditorDOMPointType
>
1532 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1533 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1534 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
) {
1535 MOZ_ASSERT(aPoint
.IsSetAndValid());
1536 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1537 MOZ_DIAGNOSTIC_ASSERT(
1538 !EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText()));
1540 const nsTextFragment
& textFragment
= aPoint
.ContainerAsText()->TextFragment();
1541 for (uint32_t i
= aPoint
.Offset(); i
< textFragment
.GetLength(); i
++) {
1542 char16_t ch
= textFragment
.CharAt(i
);
1543 if (nsCRT::IsAsciiSpace(ch
)) {
1547 if (ch
== HTMLEditUtils::kNBSP
) {
1549 aNBSPData
->NotifyNBSP(EditorDOMPointInText(aPoint
.ContainerAsText(), i
),
1550 NoBreakingSpaceData::Scanning::Forward
);
1555 return Some(BoundaryData(EditorDOMPoint(aPoint
.ContainerAsText(), i
),
1556 *aPoint
.ContainerAsText(), WSType::NormalText
,
1564 template <typename EditorDOMPointType
>
1565 WSRunScanner::TextFragmentData::BoundaryData
1566 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1567 const EditorDOMPointType
& aPoint
,
1568 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
1569 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
) {
1570 MOZ_ASSERT(aPoint
.IsSetAndValid());
1572 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer()) {
1573 // If the point is in a text node which is preformatted, we should return
1574 // the point as a visible character point.
1575 if (EditorUtils::IsContentPreformatted(*aPoint
.ContainerAsText())) {
1576 return BoundaryData(aPoint
, *aPoint
.ContainerAsText(), WSType::NormalText
,
1579 // If the text node is not preformatted, we should look for inclusive
1581 Maybe
<BoundaryData
> endInTextNode
=
1582 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint
, aNBSPData
);
1583 if (endInTextNode
.isSome()) {
1584 return endInTextNode
.ref();
1586 // The text node does not have visible character, let's keep scanning
1588 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1589 EditorDOMPointInText::AtEndOf(*aPoint
.ContainerAsText()),
1590 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1594 // Then, we need to check next leaf node.
1595 nsIContent
* nextLeafContentOrBlock
=
1596 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1597 aPoint
, aEditableBlockParentOrTopmostEditableInlineElement
,
1598 {LeafNodeType::LeafNodeOrNonEditableNode
}, aEditingHost
);
1599 if (!nextLeafContentOrBlock
) {
1600 // no next node means we exhausted
1601 // aEditableBlockParentOrTopmostEditableInlineElement
1602 // mReasonContent can be either a block element or any non-editable
1603 // content in this case.
1604 return BoundaryData(aPoint
,
1605 const_cast<Element
&>(
1606 aEditableBlockParentOrTopmostEditableInlineElement
),
1607 WSType::CurrentBlockBoundary
, Preformatted::No
);
1610 if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock
)) {
1611 // we encountered a new block. therefore no more ws.
1612 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1613 WSType::OtherBlockBoundary
, Preformatted::No
);
1616 if (!nextLeafContentOrBlock
->IsText() ||
1617 !nextLeafContentOrBlock
->IsEditable()) {
1618 // we encountered a break or a special node, like <img>,
1619 // that is not a block and not a break but still
1620 // serves as a terminator to ws runs.
1621 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
1622 nextLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1624 : WSType::SpecialContent
,
1628 if (!nextLeafContentOrBlock
->AsText()->TextFragment().GetLength()) {
1629 // If it's an empty text node, keep looking for its next leaf content.
1630 // Note that even if the empty text node is preformatted, we should keep
1631 // looking for the next one.
1632 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1633 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0),
1634 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1638 if (EditorUtils::IsContentPreformatted(*nextLeafContentOrBlock
)) {
1639 // If the next text node is preformatted and not empty, we should return
1640 // its start as found a visible character. Note that we stop scanning
1641 // collapsible white-spaces due to reaching preformatted non-empty text
1642 // node. I.e., the following text node might be not preformatted.
1643 return BoundaryData(EditorDOMPoint(nextLeafContentOrBlock
, 0),
1644 *nextLeafContentOrBlock
, WSType::NormalText
,
1648 Maybe
<BoundaryData
> endInTextNode
=
1649 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1650 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0), aNBSPData
);
1651 if (endInTextNode
.isSome()) {
1652 return endInTextNode
.ref();
1655 // The text node does not have visible character, let's keep scanning
1657 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1658 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock
->AsText()),
1659 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1663 const EditorDOMRange
&
1664 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
1665 if (mLeadingWhiteSpaceRange
.isSome()) {
1666 return mLeadingWhiteSpaceRange
.ref();
1669 // If it's preformatted or not start of line, the range is not invisible
1670 // leading white-spaces.
1671 if (!StartsFromHardLineBreak()) {
1672 mLeadingWhiteSpaceRange
.emplace();
1673 return mLeadingWhiteSpaceRange
.ref();
1676 // If there is no NBSP, all of the given range is leading white-spaces.
1677 // Note that this result may be collapsed if there is no leading white-spaces.
1678 if (!mNBSPData
.FoundNBSP()) {
1679 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1680 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1681 return mLeadingWhiteSpaceRange
.ref();
1684 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1686 // Even if the first NBSP is the start, i.e., there is no invisible leading
1687 // white-space, return collapsed range.
1688 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mNBSPData
.FirstPointRef());
1689 return mLeadingWhiteSpaceRange
.ref();
1692 const EditorDOMRange
&
1693 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
1694 if (mTrailingWhiteSpaceRange
.isSome()) {
1695 return mTrailingWhiteSpaceRange
.ref();
1698 // If it's preformatted or not immediately before block boundary, the range is
1699 // not invisible trailing white-spaces. Note that collapsible white-spaces
1700 // before a `<br>` element is visible.
1701 if (!EndsByBlockBoundary()) {
1702 mTrailingWhiteSpaceRange
.emplace();
1703 return mTrailingWhiteSpaceRange
.ref();
1706 // If there is no NBSP, all of the given range is trailing white-spaces.
1707 // Note that this result may be collapsed if there is no trailing white-
1709 if (!mNBSPData
.FoundNBSP()) {
1710 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
1711 mTrailingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
1712 return mTrailingWhiteSpaceRange
.ref();
1715 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
1717 // If last NBSP is immediately before the end, there is no trailing white-
1719 if (mEnd
.PointRef().IsSet() &&
1720 mNBSPData
.LastPointRef().GetContainer() ==
1721 mEnd
.PointRef().GetContainer() &&
1722 mNBSPData
.LastPointRef().Offset() == mEnd
.PointRef().Offset() - 1) {
1723 mTrailingWhiteSpaceRange
.emplace();
1724 return mTrailingWhiteSpaceRange
.ref();
1727 // Otherwise, the may be some trailing white-spaces.
1728 MOZ_ASSERT(!mNBSPData
.LastPointRef().IsEndOfContainer());
1729 mTrailingWhiteSpaceRange
.emplace(mNBSPData
.LastPointRef().NextPoint(),
1731 return mTrailingWhiteSpaceRange
.ref();
1734 EditorDOMRangeInTexts
1735 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
1736 const EditorDOMRange
& aRange
) const {
1737 if (!aRange
.IsPositioned()) {
1738 return EditorDOMRangeInTexts();
1740 if (aRange
.Collapsed()) {
1741 // If collapsed, we can do nothing.
1742 return EditorDOMRangeInTexts();
1744 if (aRange
.IsInTextNodes()) {
1745 // Note that this may return a range which don't include any invisible
1746 // white-spaces due to empty text nodes.
1747 return aRange
.GetAsInTexts();
1750 EditorDOMPointInText firstPoint
=
1751 aRange
.StartRef().IsInTextNode()
1752 ? aRange
.StartRef().AsInText()
1753 : GetInclusiveNextEditableCharPoint(aRange
.StartRef());
1754 if (!firstPoint
.IsSet()) {
1755 return EditorDOMRangeInTexts();
1757 EditorDOMPointInText endPoint
;
1758 if (aRange
.EndRef().IsInTextNode()) {
1759 endPoint
= aRange
.EndRef().AsInText();
1761 // FYI: GetPreviousEditableCharPoint() returns last character's point
1762 // of preceding text node if it's not empty, but we need end of
1763 // the text node here.
1764 endPoint
= GetPreviousEditableCharPoint(aRange
.EndRef());
1765 if (endPoint
.IsSet() && endPoint
.IsAtLastContent()) {
1766 MOZ_ALWAYS_TRUE(endPoint
.AdvanceOffset());
1769 if (!endPoint
.IsSet() || firstPoint
== endPoint
) {
1770 return EditorDOMRangeInTexts();
1772 return EditorDOMRangeInTexts(firstPoint
, endPoint
);
1775 const WSRunScanner::VisibleWhiteSpacesData
&
1776 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
1777 if (mVisibleWhiteSpacesData
.isSome()) {
1778 return mVisibleWhiteSpacesData
.ref();
1781 if (IsPreformattedOrSurrondedByVisibleContent()) {
1782 VisibleWhiteSpacesData visibleWhiteSpaces
;
1783 if (mStart
.PointRef().IsSet()) {
1784 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1786 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1787 if (mEnd
.PointRef().IsSet()) {
1788 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1790 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1791 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1792 return mVisibleWhiteSpacesData
.ref();
1795 // If all of the range is invisible leading or trailing white-spaces,
1796 // there is no visible content.
1797 const EditorDOMRange
& leadingWhiteSpaceRange
=
1798 InvisibleLeadingWhiteSpaceRangeRef();
1799 const bool maybeHaveLeadingWhiteSpaces
=
1800 leadingWhiteSpaceRange
.StartRef().IsSet() ||
1801 leadingWhiteSpaceRange
.EndRef().IsSet();
1802 if (maybeHaveLeadingWhiteSpaces
&&
1803 leadingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1804 leadingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1805 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1806 return mVisibleWhiteSpacesData
.ref();
1808 const EditorDOMRange
& trailingWhiteSpaceRange
=
1809 InvisibleTrailingWhiteSpaceRangeRef();
1810 const bool maybeHaveTrailingWhiteSpaces
=
1811 trailingWhiteSpaceRange
.StartRef().IsSet() ||
1812 trailingWhiteSpaceRange
.EndRef().IsSet();
1813 if (maybeHaveTrailingWhiteSpaces
&&
1814 trailingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
1815 trailingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
1816 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
1817 return mVisibleWhiteSpacesData
.ref();
1820 if (!StartsFromHardLineBreak()) {
1821 VisibleWhiteSpacesData visibleWhiteSpaces
;
1822 if (mStart
.PointRef().IsSet()) {
1823 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
1825 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
1826 if (!maybeHaveTrailingWhiteSpaces
) {
1827 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1828 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1829 mVisibleWhiteSpacesData
= Some(visibleWhiteSpaces
);
1830 return mVisibleWhiteSpacesData
.ref();
1832 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1833 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1835 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1836 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1837 return mVisibleWhiteSpacesData
.ref();
1840 MOZ_ASSERT(StartsFromHardLineBreak());
1841 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces
);
1843 VisibleWhiteSpacesData visibleWhiteSpaces
;
1844 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
1845 visibleWhiteSpaces
.SetStartPoint(leadingWhiteSpaceRange
.EndRef());
1847 visibleWhiteSpaces
.SetStartFromLeadingWhiteSpaces();
1848 if (!EndsByBlockBoundary()) {
1849 // then no trailing ws. this normal run ends the overall ws run.
1850 if (mEnd
.PointRef().IsSet()) {
1851 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1853 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1854 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1855 return mVisibleWhiteSpacesData
.ref();
1858 MOZ_ASSERT(EndsByBlockBoundary());
1860 if (!maybeHaveTrailingWhiteSpaces
) {
1861 // normal ws runs right up to adjacent block (nbsp next to block)
1862 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
1863 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
1864 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1865 return mVisibleWhiteSpacesData
.ref();
1868 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
1869 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
1871 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
1872 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
1873 return mVisibleWhiteSpacesData
.ref();
1877 nsresult
WhiteSpaceVisibilityKeeper::
1878 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1879 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
) {
1880 if (NS_WARN_IF(!aRangeToDelete
.IsPositionedAndValid()) ||
1881 NS_WARN_IF(!aRangeToDelete
.IsInContentNodes())) {
1882 return NS_ERROR_INVALID_ARG
;
1885 EditorDOMRange
rangeToDelete(aRangeToDelete
);
1886 bool mayBecomeUnexpectedDOMTree
= aHTMLEditor
.MayHaveMutationEventListeners(
1887 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
1888 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
1889 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
1890 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
);
1892 RefPtr
<Element
> editingHost
= aHTMLEditor
.GetActiveEditingHost();
1893 TextFragmentData
textFragmentDataAtStart(rangeToDelete
.StartRef(),
1895 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
1896 return NS_ERROR_FAILURE
;
1898 TextFragmentData
textFragmentDataAtEnd(rangeToDelete
.EndRef(), editingHost
);
1899 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
1900 return NS_ERROR_FAILURE
;
1902 ReplaceRangeData replaceRangeDataAtEnd
=
1903 textFragmentDataAtEnd
.GetReplaceRangeDataAtEndOfDeletionRange(
1904 textFragmentDataAtStart
);
1905 if (replaceRangeDataAtEnd
.IsSet() && !replaceRangeDataAtEnd
.Collapsed()) {
1906 MOZ_ASSERT(rangeToDelete
.EndRef().EqualsOrIsBefore(
1907 replaceRangeDataAtEnd
.EndRef()));
1908 // If there is some text after deleting range, replacing range start must
1909 // equal or be before end of the deleting range.
1910 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
1911 !rangeToDelete
.EndRef().IsEndOfContainer(),
1912 replaceRangeDataAtEnd
.StartRef().EqualsOrIsBefore(
1913 rangeToDelete
.EndRef()));
1914 // If the deleting range end is end of a text node, the replacing range
1915 // starts with another node if the following text node starts with white-
1917 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
1918 rangeToDelete
.EndRef().IsEndOfContainer(),
1919 rangeToDelete
.EndRef() == replaceRangeDataAtEnd
.StartRef() ||
1920 replaceRangeDataAtEnd
.StartRef().IsStartOfContainer());
1921 MOZ_ASSERT(rangeToDelete
.StartRef().EqualsOrIsBefore(
1922 replaceRangeDataAtEnd
.StartRef()));
1923 if (!replaceRangeDataAtEnd
.HasReplaceString()) {
1924 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1925 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1927 AutoEditorDOMPointChildInvalidator
lockOffsetOfStart(startToDelete
);
1928 AutoEditorDOMPointChildInvalidator
lockOffsetOfEnd(endToDelete
);
1929 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1931 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1933 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1934 replaceRangeDataAtEnd
.StartRef(), replaceRangeDataAtEnd
.EndRef(),
1935 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1936 if (NS_FAILED(rv
)) {
1938 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1942 if (mayBecomeUnexpectedDOMTree
&&
1943 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1944 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1945 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1946 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1948 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1949 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1951 MOZ_ASSERT(replaceRangeDataAtEnd
.RangeRef().IsInTextNodes());
1952 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
1953 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
1955 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
1957 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
1960 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
1961 aHTMLEditor
, replaceRangeDataAtEnd
.RangeRef().AsInTexts(),
1962 replaceRangeDataAtEnd
.ReplaceStringRef());
1963 if (NS_FAILED(rv
)) {
1965 "WhiteSpaceVisibilityKeeper::"
1966 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
1971 if (mayBecomeUnexpectedDOMTree
&&
1972 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
1973 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
1974 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
1975 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1977 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
1978 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
1981 if (mayBecomeUnexpectedDOMTree
) {
1982 // If focus is changed by mutation event listeners, we should stop
1983 // handling this edit action.
1984 if (editingHost
!= aHTMLEditor
.GetActiveEditingHost()) {
1985 NS_WARNING("Active editing host was changed");
1986 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1988 if (!rangeToDelete
.IsInContentNodes()) {
1989 NS_WARNING("The modified range was not in content");
1990 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
1992 // If the DOM tree might be changed by mutation event listeners, we
1993 // should retrieve the latest data for avoiding to delete/replace
1994 // unexpected range.
1995 textFragmentDataAtStart
=
1996 TextFragmentData(rangeToDelete
.StartRef(), editingHost
);
1997 textFragmentDataAtEnd
=
1998 TextFragmentData(rangeToDelete
.EndRef(), editingHost
);
2001 ReplaceRangeData replaceRangeDataAtStart
=
2002 textFragmentDataAtStart
.GetReplaceRangeDataAtStartOfDeletionRange(
2003 textFragmentDataAtEnd
);
2004 if (!replaceRangeDataAtStart
.IsSet() || replaceRangeDataAtStart
.Collapsed()) {
2007 if (!replaceRangeDataAtStart
.HasReplaceString()) {
2008 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2009 replaceRangeDataAtStart
.StartRef(), replaceRangeDataAtStart
.EndRef(),
2010 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2011 // XXX Should we validate the range for making this return
2012 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2013 NS_WARNING_ASSERTION(
2015 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2018 MOZ_ASSERT(replaceRangeDataAtStart
.RangeRef().IsInTextNodes());
2019 nsresult rv
= WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2020 aHTMLEditor
, replaceRangeDataAtStart
.RangeRef().AsInTexts(),
2021 replaceRangeDataAtStart
.ReplaceStringRef());
2022 // XXX Should we validate the range for making this return
2023 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2024 NS_WARNING_ASSERTION(
2026 "WhiteSpaceVisibilityKeeper::"
2027 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() failed");
2032 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
2033 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const {
2034 const EditorDOMPoint
& startToDelete
=
2035 aTextFragmentDataAtStartToDelete
.ScanStartRef();
2036 const EditorDOMPoint
& endToDelete
= mScanStartPoint
;
2038 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2039 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2040 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2042 if (EndRef().EqualsOrIsBefore(endToDelete
)) {
2043 return ReplaceRangeData();
2046 // If deleting range is followed by invisible trailing white-spaces, we need
2047 // to remove it for making them not visible.
2048 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
2049 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete
);
2050 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
2051 if (invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
2052 return ReplaceRangeData();
2054 // XXX Why don't we remove all invisible white-spaces?
2055 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() == endToDelete
);
2056 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd
, u
""_ns
);
2059 if (IsPreformatted()) {
2060 return ReplaceRangeData();
2063 // If end of the deleting range is followed by visible white-spaces which
2064 // is not preformatted, we might need to replace the following ASCII
2065 // white-spaces with an NBSP.
2066 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtEnd
=
2067 VisibleWhiteSpacesDataRef();
2068 if (!nonPreformattedVisibleWhiteSpacesAtEnd
.IsInitialized()) {
2069 return ReplaceRangeData();
2071 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
=
2072 nonPreformattedVisibleWhiteSpacesAtEnd
.ComparePoint(endToDelete
);
2073 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2074 PointPosition::StartOfFragment
&&
2075 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2076 PointPosition::MiddleOfFragment
) {
2077 return ReplaceRangeData();
2079 // If start of deleting range follows white-spaces or end of delete
2080 // will be start of a line, the following text cannot start with an
2081 // ASCII white-space for keeping it visible.
2082 if (!aTextFragmentDataAtStartToDelete
2083 .FollowingContentMayBecomeFirstVisibleContent(startToDelete
)) {
2084 return ReplaceRangeData();
2086 EditorRawDOMPointInText nextCharOfStartOfEnd
=
2087 GetInclusiveNextEditableCharPoint(endToDelete
);
2088 if (!nextCharOfStartOfEnd
.IsSet() ||
2089 nextCharOfStartOfEnd
.IsEndOfContainer() ||
2090 !nextCharOfStartOfEnd
.IsCharASCIISpace() ||
2091 EditorUtils::IsContentPreformatted(
2092 *nextCharOfStartOfEnd
.ContainerAsText())) {
2093 return ReplaceRangeData();
2095 if (nextCharOfStartOfEnd
.IsStartOfContainer() ||
2096 nextCharOfStartOfEnd
.IsPreviousCharASCIISpace()) {
2097 nextCharOfStartOfEnd
=
2098 aTextFragmentDataAtStartToDelete
2099 .GetFirstASCIIWhiteSpacePointCollapsedTo(nextCharOfStartOfEnd
);
2101 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2102 aTextFragmentDataAtStartToDelete
.GetEndOfCollapsibleASCIIWhiteSpaces(
2103 nextCharOfStartOfEnd
);
2104 return ReplaceRangeData(nextCharOfStartOfEnd
,
2105 endOfCollapsibleASCIIWhiteSpaces
,
2106 nsDependentSubstring(&kNBSP
, 1));
2110 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
2111 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const {
2112 const EditorDOMPoint
& startToDelete
= mScanStartPoint
;
2113 const EditorDOMPoint
& endToDelete
=
2114 aTextFragmentDataAtEndToDelete
.ScanStartRef();
2116 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2117 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2118 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2120 if (startToDelete
.EqualsOrIsBefore(StartRef())) {
2121 return ReplaceRangeData();
2124 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
2125 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete
);
2127 // If deleting range follows invisible leading white-spaces, we need to
2128 // remove them for making them not visible.
2129 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
2130 if (invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
2131 return ReplaceRangeData();
2134 // XXX Why don't we remove all leading white-spaces?
2135 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart
, u
""_ns
);
2138 if (IsPreformatted()) {
2139 return ReplaceRangeData();
2142 // If start of the deleting range follows visible white-spaces which is not
2143 // preformatted, we might need to replace previous ASCII white-spaces with
2145 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtStart
=
2146 VisibleWhiteSpacesDataRef();
2147 if (!nonPreformattedVisibleWhiteSpacesAtStart
.IsInitialized()) {
2148 return ReplaceRangeData();
2151 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
=
2152 nonPreformattedVisibleWhiteSpacesAtStart
.ComparePoint(startToDelete
);
2153 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2154 PointPosition::MiddleOfFragment
&&
2155 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2156 PointPosition::EndOfFragment
) {
2157 return ReplaceRangeData();
2159 // If end of the deleting range is (was) followed by white-spaces or
2160 // previous character of start of deleting range will be immediately
2161 // before a block boundary, the text cannot ends with an ASCII white-space
2162 // for keeping it visible.
2163 if (!aTextFragmentDataAtEndToDelete
.PrecedingContentMayBecomeInvisible(
2165 return ReplaceRangeData();
2167 EditorRawDOMPointInText atPreviousCharOfStart
=
2168 GetPreviousEditableCharPoint(startToDelete
);
2169 if (!atPreviousCharOfStart
.IsSet() ||
2170 atPreviousCharOfStart
.IsEndOfContainer() ||
2171 !atPreviousCharOfStart
.IsCharASCIISpace() ||
2172 EditorUtils::IsContentPreformatted(
2173 *atPreviousCharOfStart
.ContainerAsText())) {
2174 return ReplaceRangeData();
2176 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2177 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2178 atPreviousCharOfStart
=
2179 GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2181 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2182 GetEndOfCollapsibleASCIIWhiteSpaces(atPreviousCharOfStart
);
2183 return ReplaceRangeData(atPreviousCharOfStart
,
2184 endOfCollapsibleASCIIWhiteSpaces
,
2185 nsDependentSubstring(&kNBSP
, 1));
2190 WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
2191 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
) {
2192 TextFragmentData
textFragmentDataAtSplitPoint(
2193 aPointToSplit
, aHTMLEditor
.GetActiveEditingHost());
2194 if (NS_WARN_IF(!textFragmentDataAtSplitPoint
.IsInitialized())) {
2195 return NS_ERROR_FAILURE
;
2198 // used to prepare white-space sequence to be split across two blocks.
2199 // The main issue here is make sure white-spaces around the split point
2200 // doesn't end up becoming non-significant leading or trailing ws after
2202 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2203 textFragmentDataAtSplitPoint
.VisibleWhiteSpacesDataRef();
2204 if (!visibleWhiteSpaces
.IsInitialized()) {
2205 return NS_OK
; // No visible white-space sequence.
2208 PointPosition pointPositionWithVisibleWhiteSpaces
=
2209 visibleWhiteSpaces
.ComparePoint(aPointToSplit
);
2211 // XXX If we split white-space sequence, the following code modify the DOM
2212 // tree twice. This is not reasonable and the latter change may touch
2213 // wrong position. We should do this once.
2215 // If we insert block boundary to start or middle of the white-space sequence,
2216 // the character at the insertion point needs to be an NBSP.
2217 EditorDOMPoint
pointToSplit(aPointToSplit
);
2218 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::StartOfFragment
||
2219 pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
) {
2220 EditorRawDOMPointInText atNextCharOfStart
=
2221 textFragmentDataAtSplitPoint
.GetInclusiveNextEditableCharPoint(
2223 if (atNextCharOfStart
.IsSet() && !atNextCharOfStart
.IsEndOfContainer() &&
2224 atNextCharOfStart
.IsCharASCIISpace() &&
2225 !EditorUtils::IsContentPreformatted(
2226 *atNextCharOfStart
.ContainerAsText())) {
2227 // pointToSplit will be referred bellow so that we need to keep
2228 // it a valid point.
2229 AutoEditorDOMPointChildInvalidator
forgetChild(pointToSplit
);
2230 if (atNextCharOfStart
.IsStartOfContainer() ||
2231 atNextCharOfStart
.IsPreviousCharASCIISpace()) {
2233 textFragmentDataAtSplitPoint
2234 .GetFirstASCIIWhiteSpacePointCollapsedTo(atNextCharOfStart
);
2236 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2237 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2240 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2242 EditorDOMRangeInTexts(atNextCharOfStart
,
2243 endOfCollapsibleASCIIWhiteSpaces
),
2244 nsDependentSubstring(&kNBSP
, 1));
2245 if (NS_FAILED(rv
)) {
2247 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2254 // If we insert block boundary to middle of or end of the white-space
2255 // sequence, the previous character at the insertion point needs to be an
2257 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
||
2258 pointPositionWithVisibleWhiteSpaces
== PointPosition::EndOfFragment
) {
2259 EditorRawDOMPointInText atPreviousCharOfStart
=
2260 textFragmentDataAtSplitPoint
.GetPreviousEditableCharPoint(pointToSplit
);
2261 if (atPreviousCharOfStart
.IsSet() &&
2262 !atPreviousCharOfStart
.IsEndOfContainer() &&
2263 atPreviousCharOfStart
.IsCharASCIISpace() &&
2264 !EditorUtils::IsContentPreformatted(
2265 *atPreviousCharOfStart
.ContainerAsText())) {
2266 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2267 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2268 atPreviousCharOfStart
=
2269 textFragmentDataAtSplitPoint
2270 .GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
);
2272 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2273 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2274 atPreviousCharOfStart
);
2276 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2278 EditorDOMRangeInTexts(atPreviousCharOfStart
,
2279 endOfCollapsibleASCIIWhiteSpaces
),
2280 nsDependentSubstring(&kNBSP
, 1));
2281 if (NS_FAILED(rv
)) {
2283 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2292 template <typename PT
, typename CT
>
2293 EditorDOMPointInText
2294 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint(
2295 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2296 MOZ_ASSERT(aPoint
.IsSetAndValid());
2298 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2299 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2300 return EditorDOMPointInText();
2303 EditorRawDOMPoint point
;
2304 if (nsIContent
* child
=
2305 aPoint
.CanContainerHaveChildren() ? aPoint
.GetChild() : nullptr) {
2306 nsIContent
* leafContent
= child
->HasChildren()
2307 ? HTMLEditUtils::GetFirstLeafChild(
2308 *child
, {LeafNodeType::OnlyLeafNode
})
2310 if (NS_WARN_IF(!leafContent
)) {
2311 return EditorDOMPointInText();
2313 point
.Set(leafContent
, 0);
2318 // If it points a character in a text node, return it.
2319 // XXX For the performance, this does not check whether the container
2320 // is outside of our range.
2321 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2322 !point
.IsEndOfContainer()) {
2323 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset());
2326 if (point
.GetContainer() == GetEndReasonContent()) {
2327 return EditorDOMPointInText();
2330 NS_ASSERTION(EditorUtils::IsEditableContent(
2331 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2332 "Given content is not editable");
2334 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2335 "Given content is not an element and an orphan node");
2336 nsIContent
* editableBlockParentOrTopmostEditableInlineContent
=
2337 mScanStartPoint
.ContainerAsContent() &&
2338 EditorUtils::IsEditableContent(
2339 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2341 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2342 *mScanStartPoint
.ContainerAsContent())
2344 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent
)) {
2345 // Meaning that the container of `mScanStartPoint` is not editable.
2346 editableBlockParentOrTopmostEditableInlineContent
=
2347 mScanStartPoint
.ContainerAsContent();
2350 for (nsIContent
* nextContent
=
2351 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2352 *point
.ContainerAsContent(),
2353 *editableBlockParentOrTopmostEditableInlineContent
,
2354 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
);
2356 nextContent
= HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2357 *nextContent
, *editableBlockParentOrTopmostEditableInlineContent
,
2358 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
)) {
2359 if (!nextContent
->IsText() || !nextContent
->IsEditable()) {
2360 if (nextContent
== GetEndReasonContent()) {
2361 break; // Reached end of current runs.
2365 return EditorDOMPointInText(nextContent
->AsText(), 0);
2367 return EditorDOMPointInText();
2370 template <typename PT
, typename CT
>
2371 EditorDOMPointInText
2372 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint(
2373 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2374 MOZ_ASSERT(aPoint
.IsSetAndValid());
2376 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2377 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2378 return EditorDOMPointInText();
2381 EditorRawDOMPoint point
;
2382 if (nsIContent
* previousChild
= aPoint
.CanContainerHaveChildren()
2383 ? aPoint
.GetPreviousSiblingOfChild()
2385 nsIContent
* leafContent
=
2386 previousChild
->HasChildren()
2387 ? HTMLEditUtils::GetLastLeafChild(*previousChild
,
2388 {LeafNodeType::OnlyLeafNode
})
2390 if (NS_WARN_IF(!leafContent
)) {
2391 return EditorDOMPointInText();
2393 point
.SetToEndOf(leafContent
);
2398 // If it points a character in a text node and it's not first character
2399 // in it, return its previous point.
2400 // XXX For the performance, this does not check whether the container
2401 // is outside of our range.
2402 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2403 !point
.IsStartOfContainer()) {
2404 return EditorDOMPointInText(point
.ContainerAsText(), point
.Offset() - 1);
2407 if (point
.GetContainer() == GetStartReasonContent()) {
2408 return EditorDOMPointInText();
2411 NS_ASSERTION(EditorUtils::IsEditableContent(
2412 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
),
2413 "Given content is not editable");
2415 mScanStartPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2416 "Given content is not an element and an orphan node");
2417 nsIContent
* editableBlockParentOrTopmostEditableInlineContent
=
2418 mScanStartPoint
.ContainerAsContent() &&
2419 EditorUtils::IsEditableContent(
2420 *mScanStartPoint
.ContainerAsContent(), EditorType::HTML
)
2422 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2423 *mScanStartPoint
.ContainerAsContent())
2425 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent
)) {
2426 // Meaning that the container of `mScanStartPoint` is not editable.
2427 editableBlockParentOrTopmostEditableInlineContent
=
2428 mScanStartPoint
.ContainerAsContent();
2431 for (nsIContent
* previousContent
=
2432 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2433 *point
.ContainerAsContent(),
2434 *editableBlockParentOrTopmostEditableInlineContent
,
2435 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
);
2438 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2440 *editableBlockParentOrTopmostEditableInlineContent
,
2441 {LeafNodeType::LeafNodeOrNonEditableNode
}, mEditingHost
)) {
2442 if (!previousContent
->IsText() || !previousContent
->IsEditable()) {
2443 if (previousContent
== GetStartReasonContent()) {
2444 break; // Reached start of current runs.
2448 return EditorDOMPointInText(
2449 previousContent
->AsText(),
2450 previousContent
->AsText()->TextLength()
2451 ? previousContent
->AsText()->TextLength() - 1
2454 return EditorDOMPointInText();
2458 template <typename EditorDOMPointType
>
2459 EditorDOMPointType
WSRunScanner::GetAfterLastVisiblePoint(
2460 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2461 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2462 return EditorDOMPointType::AtEndOf(aTextNode
);
2464 TextFragmentData
textFragmentData(
2465 EditorDOMPoint(&aTextNode
,
2466 aTextNode
.Length() ? aTextNode
.Length() - 1 : 0),
2468 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2469 return EditorDOMPointType(); // TODO: Make here return error with Err.
2471 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2472 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
2473 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2474 invisibleWhiteSpaceRange
.Collapsed()) {
2475 return EditorDOMPointType::AtEndOf(aTextNode
);
2477 return EditorDOMPointType(invisibleWhiteSpaceRange
.StartRef());
2481 template <typename EditorDOMPointType
>
2482 EditorDOMPointType
WSRunScanner::GetFirstVisiblePoint(
2483 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
2484 if (EditorUtils::IsContentPreformatted(aTextNode
)) {
2485 return EditorDOMPointType(&aTextNode
, 0);
2487 TextFragmentData
textFragmentData(EditorDOMPoint(&aTextNode
, 0),
2489 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2490 return EditorDOMPointType(); // TODO: Make here return error with Err.
2492 const EditorDOMRange
& invisibleWhiteSpaceRange
=
2493 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
2494 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
2495 invisibleWhiteSpaceRange
.Collapsed()) {
2496 return EditorDOMPointType(&aTextNode
, 0);
2498 return EditorDOMPointType(invisibleWhiteSpaceRange
.EndRef());
2501 EditorDOMPointInText
2502 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
2503 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2504 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2505 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2506 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2507 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2508 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2509 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2511 // If it's not the last character in the text node, let's scan following
2512 // characters in it.
2513 if (!aPointAtASCIIWhiteSpace
.IsAtLastContent()) {
2514 Maybe
<uint32_t> nextVisibleCharOffset
=
2515 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2516 aPointAtASCIIWhiteSpace
);
2517 if (nextVisibleCharOffset
.isSome()) {
2518 // There is non-white-space character in it.
2519 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2520 nextVisibleCharOffset
.value());
2524 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
2525 // the following text nodes.
2526 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2528 EditorDOMPointInText afterLastWhiteSpace
=
2529 EditorDOMPointInText::AtEndOf(*aPointAtASCIIWhiteSpace
.ContainerAsText());
2530 for (EditorDOMPointInText atEndOfPreviousTextNode
= afterLastWhiteSpace
;;) {
2531 EditorDOMPointInText atStartOfNextTextNode
=
2532 GetInclusiveNextEditableCharPoint(atEndOfPreviousTextNode
);
2533 if (!atStartOfNextTextNode
.IsSet()) {
2534 // There is no more text nodes. Return end of the previous text node.
2535 return afterLastWhiteSpace
;
2538 // We can ignore empty text nodes (even if it's preformatted).
2539 if (atStartOfNextTextNode
.IsContainerEmpty()) {
2540 atEndOfPreviousTextNode
= atStartOfNextTextNode
;
2544 // If next node starts with non-white-space character or next node is
2545 // preformatted, return end of previous text node.
2546 if (!atStartOfNextTextNode
.IsCharASCIISpace() ||
2547 EditorUtils::IsContentPreformatted(
2548 *atStartOfNextTextNode
.ContainerAsText())) {
2549 return afterLastWhiteSpace
;
2552 // Otherwise, scan the text node.
2553 Maybe
<uint32_t> nextVisibleCharOffset
=
2554 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2555 atStartOfNextTextNode
);
2556 if (nextVisibleCharOffset
.isSome()) {
2557 return EditorDOMPointInText(atStartOfNextTextNode
.ContainerAsText(),
2558 nextVisibleCharOffset
.value());
2561 // The next text nodes ends with white-space too. Try next one.
2562 afterLastWhiteSpace
= atEndOfPreviousTextNode
=
2563 EditorDOMPointInText::AtEndOf(*atStartOfNextTextNode
.ContainerAsText());
2567 EditorDOMPointInText
2568 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
2569 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
) const {
2570 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
2571 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
2572 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
2573 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2574 *aPointAtASCIIWhiteSpace
.ContainerAsText()),
2575 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2577 // If there is some characters before it, scan it in the text node first.
2578 if (!aPointAtASCIIWhiteSpace
.IsStartOfContainer()) {
2579 uint32_t firstASCIIWhiteSpaceOffset
=
2580 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2581 aPointAtASCIIWhiteSpace
);
2582 if (firstASCIIWhiteSpaceOffset
) {
2583 // There is a non-white-space character in it.
2584 return EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(),
2585 firstASCIIWhiteSpaceOffset
);
2589 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
2590 // the preceding text nodes.
2591 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2593 EditorDOMPointInText atLastWhiteSpace
=
2594 EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAsText(), 0);
2595 for (EditorDOMPointInText atStartOfPreviousTextNode
= atLastWhiteSpace
;;) {
2596 EditorDOMPointInText atLastCharOfNextTextNode
=
2597 GetPreviousEditableCharPoint(atStartOfPreviousTextNode
);
2598 if (!atLastCharOfNextTextNode
.IsSet()) {
2599 // There is no more text nodes. Return end of last text node.
2600 return atLastWhiteSpace
;
2603 // We can ignore empty text nodes (even if it's preformatted).
2604 if (atLastCharOfNextTextNode
.IsContainerEmpty()) {
2605 atStartOfPreviousTextNode
= atLastCharOfNextTextNode
;
2609 // If next node ends with non-white-space character or next node is
2610 // preformatted, return start of previous text node.
2611 if (!atLastCharOfNextTextNode
.IsCharASCIISpace() ||
2612 EditorUtils::IsContentPreformatted(
2613 *atLastCharOfNextTextNode
.ContainerAsText())) {
2614 return atLastWhiteSpace
;
2617 // Otherwise, scan the text node.
2618 uint32_t firstASCIIWhiteSpaceOffset
=
2619 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2620 atLastCharOfNextTextNode
);
2621 if (firstASCIIWhiteSpaceOffset
) {
2622 return EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(),
2623 firstASCIIWhiteSpaceOffset
);
2626 // The next text nodes starts with white-space too. Try next one.
2627 atLastWhiteSpace
= atStartOfPreviousTextNode
=
2628 EditorDOMPointInText(atLastCharOfNextTextNode
.ContainerAsText(), 0);
2633 nsresult
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2634 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
2635 const nsAString
& aReplaceString
) {
2636 MOZ_ASSERT(aRangeToReplace
.IsPositioned());
2637 MOZ_ASSERT(aRangeToReplace
.StartRef().IsSetAndValid());
2638 MOZ_ASSERT(aRangeToReplace
.EndRef().IsSetAndValid());
2639 MOZ_ASSERT(aRangeToReplace
.StartRef().IsBefore(aRangeToReplace
.EndRef()));
2641 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2642 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2643 MOZ_KnownLive(*aRangeToReplace
.StartRef().ContainerAsText()),
2644 aRangeToReplace
.StartRef().Offset(),
2645 aRangeToReplace
.InSameContainer()
2646 ? aRangeToReplace
.EndRef().Offset() -
2647 aRangeToReplace
.StartRef().Offset()
2648 : aRangeToReplace
.StartRef().ContainerAsText()->TextLength() -
2649 aRangeToReplace
.StartRef().Offset(),
2651 if (NS_FAILED(rv
)) {
2652 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2656 if (aRangeToReplace
.InSameContainer()) {
2660 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2661 EditorDOMPointInText::AtEndOf(
2662 *aRangeToReplace
.StartRef().ContainerAsText()),
2663 aRangeToReplace
.EndRef(),
2664 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2665 NS_WARNING_ASSERTION(
2667 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2671 char16_t
WSRunScanner::GetCharAt(Text
* aTextNode
, int32_t aOffset
) const {
2672 // return 0 if we can't get a char, for whatever reason
2673 if (NS_WARN_IF(!aTextNode
) || NS_WARN_IF(aOffset
< 0) ||
2674 NS_WARN_IF(aOffset
>=
2675 static_cast<int32_t>(aTextNode
->TextDataLength()))) {
2678 return aTextNode
->TextFragment().CharAt(aOffset
);
2682 template <typename EditorDOMPointType
>
2683 nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
2684 HTMLEditor
& aHTMLEditor
, const EditorDOMPointType
& aPoint
) {
2685 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
2686 TextFragmentData
textFragmentData(aPoint
, editingHost
);
2687 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
2688 return NS_ERROR_FAILURE
;
2691 // this routine examines a run of ws and tries to get rid of some unneeded
2692 // nbsp's, replacing them with regular ascii space if possible. Keeping
2693 // things simple for now and just trying to fix up the trailing ws in the run.
2694 if (!textFragmentData
.FoundNoBreakingWhiteSpaces()) {
2698 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2699 textFragmentData
.VisibleWhiteSpacesDataRef();
2700 if (!visibleWhiteSpaces
.IsInitialized()) {
2704 // Remove this block if we ship Blink-compat white-space normalization.
2705 if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
2706 // now check that what is to the left of it is compatible with replacing
2708 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
=
2709 visibleWhiteSpaces
.EndRef();
2710 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2711 textFragmentData
.GetPreviousEditableCharPoint(
2712 atEndOfVisibleWhiteSpaces
);
2713 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2714 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2715 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2719 // now check that what is to the left of it is compatible with replacing
2721 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2722 textFragmentData
.GetPreviousEditableCharPoint(
2723 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2724 bool isPreviousCharASCIIWhiteSpace
=
2725 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2726 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2727 .IsEndOfContainer() &&
2728 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2729 .IsCharASCIISpace();
2730 bool maybeNBSPFollowingVisibleContent
=
2731 (atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2732 !isPreviousCharASCIIWhiteSpace
) ||
2733 (!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2734 (visibleWhiteSpaces
.StartsFromNormalText() ||
2735 visibleWhiteSpaces
.StartsFromSpecialContent()));
2736 bool followedByVisibleContentOrBRElement
= false;
2738 // If the NBSP follows a visible content or an ASCII white-space, i.e.,
2739 // unless NBSP is first character and start of a block, we may need to
2740 // insert <br> element and restore the NBSP to an ASCII white-space.
2741 if (maybeNBSPFollowingVisibleContent
|| isPreviousCharASCIIWhiteSpace
) {
2742 followedByVisibleContentOrBRElement
=
2743 visibleWhiteSpaces
.EndsByNormalText() ||
2744 visibleWhiteSpaces
.EndsBySpecialContent() ||
2745 visibleWhiteSpaces
.EndsByBRElement();
2746 // First, try to insert <br> element if NBSP is at end of a block.
2747 // XXX We should stop this if there is a visible content.
2748 if (visibleWhiteSpaces
.EndsByBlockBoundary() &&
2749 aPoint
.IsInContentNode()) {
2750 bool insertBRElement
=
2751 HTMLEditUtils::IsBlockElement(*aPoint
.ContainerAsContent());
2752 if (!insertBRElement
) {
2753 NS_ASSERTION(EditorUtils::IsEditableContent(
2754 *aPoint
.ContainerAsContent(), EditorType::HTML
),
2755 "Given content is not editable");
2757 aPoint
.ContainerAsContent()->GetAsElementOrParentElement(),
2758 "Given content is not an element and an orphan node");
2759 nsIContent
* blockParentOrTopmostEditableInlineContent
=
2760 EditorUtils::IsEditableContent(*aPoint
.ContainerAsContent(),
2763 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2764 *aPoint
.ContainerAsContent())
2766 insertBRElement
= blockParentOrTopmostEditableInlineContent
&&
2767 HTMLEditUtils::IsBlockElement(
2768 *blockParentOrTopmostEditableInlineContent
);
2770 if (insertBRElement
) {
2771 // We are at a block boundary. Insert a <br>. Why? Well, first note
2772 // that the br will have no visible effect since it is up against a
2773 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
2774 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
2775 // this <br> addition gets us is the ability to convert a trailing
2776 // nbsp to a space. Consider: |<body>foo. '</body>|, where '
2777 // represents selection. User types space attempting to put 2 spaces
2778 // after the end of their sentence. We used to do this as:
2779 // |<body>foo.  </body>| This caused problems with soft wrapping:
2780 // the nbsp would wrap to the next line, which looked attrocious. If
2781 // you try to do: |<body>foo.  </body>| instead, the trailing
2782 // space is invisible because it is against a block boundary. If you
2784 // |<body>foo.  </body>| then you get an even uglier soft
2785 // wrapping problem, where foo is on one line until you type the final
2786 // space, and then "foo " jumps down to the next line. Ugh. The
2787 // best way I can find out of this is to throw in a harmless <br>
2788 // here, which allows us to do: |<body>foo.  <br></body>|, which
2789 // doesn't cause foo to jump lines, doesn't cause spaces to show up at
2790 // the beginning of soft wrapped lines, and lets the user see 2 spaces
2791 // when they type 2 spaces.
2793 RefPtr
<Element
> brElement
=
2794 aHTMLEditor
.InsertBRElementWithTransaction(
2795 atEndOfVisibleWhiteSpaces
);
2796 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
2797 return NS_ERROR_EDITOR_DESTROYED
;
2800 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
2801 return NS_ERROR_FAILURE
;
2804 atPreviousCharOfEndOfVisibleWhiteSpaces
=
2805 textFragmentData
.GetPreviousEditableCharPoint(
2806 atEndOfVisibleWhiteSpaces
);
2807 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2808 textFragmentData
.GetPreviousEditableCharPoint(
2809 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2810 isPreviousCharASCIIWhiteSpace
=
2811 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2812 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2813 .IsEndOfContainer() &&
2814 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2815 .IsCharASCIISpace();
2816 followedByVisibleContentOrBRElement
= true;
2820 // Next, replace the NBSP with an ASCII white-space if it's surrounded
2821 // by visible contents (or immediately before a <br> element).
2822 if (maybeNBSPFollowingVisibleContent
&&
2823 followedByVisibleContentOrBRElement
) {
2824 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2825 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2827 *atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()),
2828 atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset(), 1, u
" "_ns
);
2829 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2830 "HTMLEditor::ReplaceTextWithTransaction() failed");
2834 // If the text node is not preformatted, and the NBSP is followed by a <br>
2835 // element and following (maybe multiple) ASCII spaces, remove the NBSP,
2836 // but inserts a NBSP before the spaces. This makes a line break
2837 // opportunity to wrap the line.
2838 // XXX This is different behavior from Blink. Blink generates pairs of
2839 // an NBSP and an ASCII white-space, but put NBSP at the end of the
2840 // sequence. We should follow the behavior for web-compat.
2841 if (maybeNBSPFollowingVisibleContent
|| !isPreviousCharASCIIWhiteSpace
||
2842 !followedByVisibleContentOrBRElement
||
2843 EditorUtils::IsContentPreformatted(
2844 *atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2845 .GetContainerAsText())) {
2849 // Currently, we're at an NBSP following an ASCII space, and we need to
2850 // replace them with `" "` for avoiding collapsing white-spaces.
2851 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2852 .IsEndOfContainer());
2853 EditorDOMPointInText atFirstASCIIWhiteSpace
=
2854 textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2855 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2856 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2857 uint32_t numberOfASCIIWhiteSpacesInStartNode
=
2858 atFirstASCIIWhiteSpace
.ContainerAsText() ==
2859 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2860 ? atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset() -
2861 atFirstASCIIWhiteSpace
.Offset()
2862 : atFirstASCIIWhiteSpace
.ContainerAsText()->Length() -
2863 atFirstASCIIWhiteSpace
.Offset();
2864 // Replace all preceding ASCII white-spaces **and** the NBSP.
2865 uint32_t replaceLengthInStartNode
=
2866 numberOfASCIIWhiteSpacesInStartNode
+
2867 (atFirstASCIIWhiteSpace
.ContainerAsText() ==
2868 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAsText()
2871 nsresult rv
= aHTMLEditor
.ReplaceTextWithTransaction(
2872 MOZ_KnownLive(*atFirstASCIIWhiteSpace
.ContainerAsText()),
2873 atFirstASCIIWhiteSpace
.Offset(), replaceLengthInStartNode
,
2875 if (NS_FAILED(rv
)) {
2876 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2880 if (atFirstASCIIWhiteSpace
.GetContainer() ==
2881 atPreviousCharOfEndOfVisibleWhiteSpaces
.GetContainer()) {
2885 // We need to remove the following unnecessary ASCII white-spaces and
2886 // NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
2887 // into the start node.
2888 rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2889 EditorDOMPointInText::AtEndOf(
2890 *atFirstASCIIWhiteSpace
.ContainerAsText()),
2891 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint(),
2892 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2893 NS_WARNING_ASSERTION(
2895 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2899 // XXX This is called when top-level edit sub-action handling ends for
2900 // 3 points at most. However, this is not compatible with Blink.
2901 // Blink touches white-space sequence which includes new character
2902 // or following white-space sequence of new <br> element or, if and
2903 // only if deleting range is followed by white-space sequence (i.e.,
2904 // not touched previous white-space sequence of deleting range).
2905 // This should be done when we change to make each edit action
2906 // handler directly normalize white-space sequence rather than
2907 // OnEndHandlingTopLevelEditSucAction().
2909 // First, check if the last character is an NBSP. Otherwise, we don't need
2910 // to do nothing here.
2911 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
= visibleWhiteSpaces
.EndRef();
2912 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
2913 textFragmentData
.GetPreviousEditableCharPoint(atEndOfVisibleWhiteSpaces
);
2914 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
2915 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
2916 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP()) {
2920 // Next, consider the range to collapse ASCII white-spaces before there.
2921 EditorDOMPointInText startToDelete
, endToDelete
;
2923 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
2924 textFragmentData
.GetPreviousEditableCharPoint(
2925 atPreviousCharOfEndOfVisibleWhiteSpaces
);
2926 // If there are some preceding ASCII white-spaces, we need to treat them
2927 // as one white-space. I.e., we need to collapse them.
2928 if (atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP() &&
2929 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
2930 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2931 .IsCharASCIISpace()) {
2932 startToDelete
= textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
2933 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
);
2934 endToDelete
= atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
;
2936 // Otherwise, we don't need to remove any white-spaces, but we may need
2937 // to normalize the white-space sequence containing the previous NBSP.
2939 startToDelete
= endToDelete
=
2940 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint();
2943 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
2944 Result
<EditorDOMPoint
, nsresult
> result
=
2945 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2946 startToDelete
, endToDelete
,
2947 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
,
2948 HTMLEditor::DeleteDirection::Forward
);
2949 NS_WARNING_ASSERTION(
2951 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2952 return result
.isErr() ? result
.unwrapErr() : NS_OK
;
2955 EditorDOMPointInText
WSRunScanner::TextFragmentData::
2956 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
2957 const EditorDOMPoint
& aPointToInsert
) const {
2958 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
2959 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
2960 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2961 PointPosition::MiddleOfFragment
||
2962 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
2963 PointPosition::EndOfFragment
,
2964 "Previous char of aPoint should be in the visible white-spaces");
2966 // Try to change an NBSP to a space, if possible, just to prevent NBSP
2967 // proliferation. This routine is called when we are about to make this
2968 // point in the ws abut an inserted break or text, so we don't have to worry
2969 // about what is after it. What is after it now will end up after the
2971 EditorDOMPointInText atPreviousChar
=
2972 GetPreviousEditableCharPoint(aPointToInsert
);
2973 if (!atPreviousChar
.IsSet() || atPreviousChar
.IsEndOfContainer() ||
2974 !atPreviousChar
.IsCharNBSP() ||
2975 EditorUtils::IsContentPreformatted(*atPreviousChar
.ContainerAsText())) {
2976 return EditorDOMPointInText();
2979 EditorDOMPointInText atPreviousCharOfPreviousChar
=
2980 GetPreviousEditableCharPoint(atPreviousChar
);
2981 if (atPreviousCharOfPreviousChar
.IsSet()) {
2982 // If the previous char is in different text node and it's preformatted,
2983 // we shouldn't touch it.
2984 if (atPreviousChar
.ContainerAsText() !=
2985 atPreviousCharOfPreviousChar
.ContainerAsText() &&
2986 EditorUtils::IsContentPreformatted(
2987 *atPreviousCharOfPreviousChar
.ContainerAsText())) {
2988 return EditorDOMPointInText();
2990 // If the previous char of the NBSP at previous position of aPointToInsert
2991 // is an ASCII white-space, we don't need to replace it with same character.
2992 if (!atPreviousCharOfPreviousChar
.IsEndOfContainer() &&
2993 atPreviousCharOfPreviousChar
.IsCharASCIISpace()) {
2994 return EditorDOMPointInText();
2996 return atPreviousChar
;
2999 // If previous content of the NBSP is block boundary, we cannot replace the
3000 // NBSP with an ASCII white-space to keep it rendered.
3001 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3002 VisibleWhiteSpacesDataRef();
3003 if (!visibleWhiteSpaces
.StartsFromNormalText() &&
3004 !visibleWhiteSpaces
.StartsFromSpecialContent()) {
3005 return EditorDOMPointInText();
3007 return atPreviousChar
;
3010 EditorDOMPointInText
WSRunScanner::TextFragmentData::
3011 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
3012 const EditorDOMPoint
& aPointToInsert
) const {
3013 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
3014 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
3015 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3016 PointPosition::StartOfFragment
||
3017 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3018 PointPosition::MiddleOfFragment
,
3019 "Inclusive next char of aPointToInsert should be in the visible "
3022 // Try to change an nbsp to a space, if possible, just to prevent nbsp
3023 // proliferation This routine is called when we are about to make this point
3024 // in the ws abut an inserted text, so we don't have to worry about what is
3025 // before it. What is before it now will end up before the inserted text.
3026 EditorDOMPointInText atNextChar
=
3027 GetInclusiveNextEditableCharPoint(aPointToInsert
);
3028 if (!atNextChar
.IsSet() || NS_WARN_IF(atNextChar
.IsEndOfContainer()) ||
3029 !atNextChar
.IsCharNBSP() ||
3030 EditorUtils::IsContentPreformatted(*atNextChar
.ContainerAsText())) {
3031 return EditorDOMPointInText();
3034 EditorDOMPointInText atNextCharOfNextCharOfNBSP
=
3035 GetInclusiveNextEditableCharPoint(atNextChar
.NextPoint());
3036 if (atNextCharOfNextCharOfNBSP
.IsSet()) {
3037 // If the next char is in different text node and it's preformatted,
3038 // we shouldn't touch it.
3039 if (atNextChar
.ContainerAsText() !=
3040 atNextCharOfNextCharOfNBSP
.ContainerAsText() &&
3041 EditorUtils::IsContentPreformatted(
3042 *atNextCharOfNextCharOfNBSP
.ContainerAsText())) {
3043 return EditorDOMPointInText();
3045 // If following character of an NBSP is an ASCII white-space, we don't
3046 // need to replace it with same character.
3047 if (!atNextCharOfNextCharOfNBSP
.IsEndOfContainer() &&
3048 atNextCharOfNextCharOfNBSP
.IsCharASCIISpace()) {
3049 return EditorDOMPointInText();
3054 // If the NBSP is last character in the hard line, we don't need to
3055 // replace it because it's required to render multiple white-spaces.
3056 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3057 VisibleWhiteSpacesDataRef();
3058 if (!visibleWhiteSpaces
.EndsByNormalText() &&
3059 !visibleWhiteSpaces
.EndsBySpecialContent() &&
3060 !visibleWhiteSpaces
.EndsByBRElement()) {
3061 return EditorDOMPointInText();
3068 nsresult
WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
3069 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
3070 MOZ_ASSERT(aPoint
.IsSet());
3071 Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3072 TextFragmentData
textFragmentData(aPoint
, editingHost
);
3073 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3074 return NS_ERROR_FAILURE
;
3076 const EditorDOMRange
& leadingWhiteSpaceRange
=
3077 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
3078 // XXX Getting trailing white-space range now must be wrong because
3079 // mutation event listener may invalidate it.
3080 const EditorDOMRange
& trailingWhiteSpaceRange
=
3081 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
3082 DebugOnly
<bool> leadingWhiteSpacesDeleted
= false;
3083 if (leadingWhiteSpaceRange
.IsPositioned() &&
3084 !leadingWhiteSpaceRange
.Collapsed()) {
3085 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3086 leadingWhiteSpaceRange
.StartRef(), leadingWhiteSpaceRange
.EndRef(),
3087 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3088 if (NS_FAILED(rv
)) {
3090 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3091 "delete leading white-spaces");
3094 leadingWhiteSpacesDeleted
= true;
3096 if (trailingWhiteSpaceRange
.IsPositioned() &&
3097 !trailingWhiteSpaceRange
.Collapsed() &&
3098 leadingWhiteSpaceRange
!= trailingWhiteSpaceRange
) {
3099 NS_ASSERTION(!leadingWhiteSpacesDeleted
,
3100 "We're trying to remove trailing white-spaces with maybe "
3102 nsresult rv
= aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3103 trailingWhiteSpaceRange
.StartRef(), trailingWhiteSpaceRange
.EndRef(),
3104 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3105 if (NS_FAILED(rv
)) {
3107 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3108 "delete trailing white-spaces");
3115 /*****************************************************************************
3116 * Implementation for new white-space normalizer
3117 *****************************************************************************/
3120 EditorDOMRangeInTexts
3121 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3122 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
) {
3123 // Corresponding to handling invisible white-spaces part of
3124 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
3125 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
3127 MOZ_ASSERT(aStart
.ScanStartRef().IsSetAndValid());
3128 MOZ_ASSERT(aEnd
.ScanStartRef().IsSetAndValid());
3129 MOZ_ASSERT(aStart
.ScanStartRef().EqualsOrIsBefore(aEnd
.ScanStartRef()));
3130 MOZ_ASSERT(aStart
.ScanStartRef().IsInTextNode());
3131 MOZ_ASSERT(aEnd
.ScanStartRef().IsInTextNode());
3133 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
3134 // `GetReplaceRangeDataAtStartOfDeletionRange()` use
3135 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
3136 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
3137 // However, they are really odd as mentioned with "XXX" comments
3138 // in them. For the new white-space normalizer, we need to treat
3139 // invisible white-spaces stricter because the legacy path handles
3140 // white-spaces multiple times (e.g., calling `HTMLEditor::
3141 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
3142 // the bug, but in the new path, we should stop doing same things
3143 // multiple times for both performance and footprint. Therefore,
3144 // even though the result might be different in some edge cases,
3145 // we should use clean path for now. Perhaps, we should fix the odd
3146 // cases before shipping `beforeinput` event in release channel.
3148 const EditorDOMRange
& invisibleLeadingWhiteSpaceRange
=
3149 aStart
.InvisibleLeadingWhiteSpaceRangeRef();
3150 const EditorDOMRange
& invisibleTrailingWhiteSpaceRange
=
3151 aEnd
.InvisibleTrailingWhiteSpaceRangeRef();
3152 const bool hasInvisibleLeadingWhiteSpaces
=
3153 invisibleLeadingWhiteSpaceRange
.IsPositioned() &&
3154 !invisibleLeadingWhiteSpaceRange
.Collapsed();
3155 const bool hasInvisibleTrailingWhiteSpaces
=
3156 invisibleLeadingWhiteSpaceRange
!= invisibleTrailingWhiteSpaceRange
&&
3157 invisibleTrailingWhiteSpaceRange
.IsPositioned() &&
3158 !invisibleTrailingWhiteSpaceRange
.Collapsed();
3160 EditorDOMRangeInTexts
result(aStart
.ScanStartRef().AsInText(),
3161 aEnd
.ScanStartRef().AsInText());
3162 MOZ_ASSERT(result
.IsPositionedAndValid());
3163 if (!hasInvisibleLeadingWhiteSpaces
&& !hasInvisibleTrailingWhiteSpaces
) {
3168 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3169 invisibleLeadingWhiteSpaceRange
.StartRef().IsBefore(
3170 invisibleTrailingWhiteSpaceRange
.StartRef()));
3171 const EditorDOMPoint
& aroundFirstInvisibleWhiteSpace
=
3172 hasInvisibleLeadingWhiteSpaces
3173 ? invisibleLeadingWhiteSpaceRange
.StartRef()
3174 : invisibleTrailingWhiteSpaceRange
.StartRef();
3175 if (aroundFirstInvisibleWhiteSpace
.IsBefore(result
.StartRef())) {
3176 if (aroundFirstInvisibleWhiteSpace
.IsInTextNode()) {
3177 result
.SetStart(aroundFirstInvisibleWhiteSpace
.AsInText());
3178 MOZ_ASSERT(result
.IsPositionedAndValid());
3180 const EditorDOMPointInText atFirstInvisibleWhiteSpace
=
3181 hasInvisibleLeadingWhiteSpaces
3182 ? aStart
.GetInclusiveNextEditableCharPoint(
3183 aroundFirstInvisibleWhiteSpace
)
3184 : aEnd
.GetInclusiveNextEditableCharPoint(
3185 aroundFirstInvisibleWhiteSpace
);
3186 MOZ_ASSERT(atFirstInvisibleWhiteSpace
.IsSet());
3188 atFirstInvisibleWhiteSpace
.EqualsOrIsBefore(result
.StartRef()));
3189 result
.SetStart(atFirstInvisibleWhiteSpace
);
3190 MOZ_ASSERT(result
.IsPositionedAndValid());
3194 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3195 invisibleLeadingWhiteSpaceRange
.EndRef().IsBefore(
3196 invisibleTrailingWhiteSpaceRange
.EndRef()));
3197 const EditorDOMPoint
& afterLastInvisibleWhiteSpace
=
3198 hasInvisibleTrailingWhiteSpaces
3199 ? invisibleTrailingWhiteSpaceRange
.EndRef()
3200 : invisibleLeadingWhiteSpaceRange
.EndRef();
3201 if (afterLastInvisibleWhiteSpace
.EqualsOrIsBefore(result
.EndRef())) {
3202 MOZ_ASSERT(result
.IsPositionedAndValid());
3205 if (afterLastInvisibleWhiteSpace
.IsInTextNode()) {
3206 result
.SetEnd(afterLastInvisibleWhiteSpace
.AsInText());
3207 MOZ_ASSERT(result
.IsPositionedAndValid());
3210 const EditorDOMPointInText atLastInvisibleWhiteSpace
=
3211 hasInvisibleTrailingWhiteSpaces
3212 ? aEnd
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
)
3213 : aStart
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
);
3214 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsSet());
3215 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsContainerEmpty() ||
3216 atLastInvisibleWhiteSpace
.IsAtLastContent());
3217 MOZ_ASSERT(result
.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace
));
3218 result
.SetEnd(atLastInvisibleWhiteSpace
.IsEndOfContainer()
3219 ? atLastInvisibleWhiteSpace
3220 : atLastInvisibleWhiteSpace
.NextPoint());
3221 MOZ_ASSERT(result
.IsPositionedAndValid());
3226 Result
<EditorDOMRangeInTexts
, nsresult
>
3227 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(Element
* aEditingHost
,
3228 const EditorDOMPoint
& aPoint
) {
3229 // Corresponding to computing delete range part of
3230 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
3231 MOZ_ASSERT(aPoint
.IsSetAndValid());
3233 TextFragmentData
textFragmentDataAtCaret(aPoint
, aEditingHost
);
3234 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
3235 return Err(NS_ERROR_FAILURE
);
3237 EditorDOMPointInText atPreviousChar
=
3238 textFragmentDataAtCaret
.GetPreviousEditableCharPoint(aPoint
);
3239 if (!atPreviousChar
.IsSet()) {
3240 return EditorDOMRangeInTexts(); // There is no content in the block.
3243 // XXX When previous char point is in an empty text node, we do nothing,
3244 // but this must look odd from point of user view. We should delete
3245 // something before aPoint.
3246 if (atPreviousChar
.IsEndOfContainer()) {
3247 return EditorDOMRangeInTexts();
3250 // Extend delete range if previous char is a low surrogate following
3251 // a high surrogate.
3252 EditorDOMPointInText atNextChar
= atPreviousChar
.NextPoint();
3253 if (!atPreviousChar
.IsStartOfContainer()) {
3254 if (atPreviousChar
.IsCharLowSurrogateFollowingHighSurrogate()) {
3255 atPreviousChar
= atPreviousChar
.PreviousPoint();
3257 // If caret is in middle of a surrogate pair, delete the surrogate pair
3259 else if (atPreviousChar
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3260 atNextChar
= atNextChar
.NextPoint();
3264 // If the text node is preformatted, just remove the previous character.
3265 if (textFragmentDataAtCaret
.IsPreformatted()) {
3266 return EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3269 // If previous char is an ASCII white-spaces, delete all adjcent ASCII
3271 EditorDOMRangeInTexts rangeToDelete
;
3272 if (atPreviousChar
.IsCharASCIISpace()) {
3273 EditorDOMPointInText startToDelete
=
3274 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3276 if (!startToDelete
.IsSet()) {
3278 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3279 return Err(NS_ERROR_FAILURE
);
3281 EditorDOMPointInText endToDelete
=
3282 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(
3284 if (!endToDelete
.IsSet()) {
3285 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3286 return Err(NS_ERROR_FAILURE
);
3288 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3290 // if previous char is not an ASCII white-space, remove it.
3292 rangeToDelete
= EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
3295 // If there is no removable and visible content, we should do nothing.
3296 if (rangeToDelete
.Collapsed()) {
3297 return EditorDOMRangeInTexts();
3300 // And also delete invisible white-spaces if they become visible.
3301 TextFragmentData textFragmentDataAtStart
=
3302 rangeToDelete
.StartRef() != aPoint
3303 ? TextFragmentData(rangeToDelete
.StartRef(), aEditingHost
)
3304 : textFragmentDataAtCaret
;
3305 TextFragmentData textFragmentDataAtEnd
=
3306 rangeToDelete
.EndRef() != aPoint
3307 ? TextFragmentData(rangeToDelete
.EndRef(), aEditingHost
)
3308 : textFragmentDataAtCaret
;
3309 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
3310 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3311 return Err(NS_ERROR_FAILURE
);
3313 EditorDOMRangeInTexts extendedRangeToDelete
=
3314 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3315 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3316 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3317 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3322 Result
<EditorDOMRangeInTexts
, nsresult
>
3323 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
3324 Element
* aEditingHost
, const EditorDOMPoint
& aPoint
) {
3325 // Corresponding to computing delete range part of
3326 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
3327 MOZ_ASSERT(aPoint
.IsSetAndValid());
3329 TextFragmentData
textFragmentDataAtCaret(aPoint
, aEditingHost
);
3330 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
3331 return Err(NS_ERROR_FAILURE
);
3333 EditorDOMPointInText atCaret
=
3334 textFragmentDataAtCaret
.GetInclusiveNextEditableCharPoint(aPoint
);
3335 if (!atCaret
.IsSet()) {
3336 return EditorDOMRangeInTexts(); // There is no content in the block.
3338 // If caret is in middle of a surrogate pair, we should remove next
3339 // character (blink-compat).
3340 if (!atCaret
.IsEndOfContainer() &&
3341 atCaret
.IsCharLowSurrogateFollowingHighSurrogate()) {
3342 atCaret
= atCaret
.NextPoint();
3345 // XXX When next char point is in an empty text node, we do nothing,
3346 // but this must look odd from point of user view. We should delete
3347 // something after aPoint.
3348 if (atCaret
.IsEndOfContainer()) {
3349 return EditorDOMRangeInTexts();
3352 // Extend delete range if previous char is a low surrogate following
3353 // a high surrogate.
3354 EditorDOMPointInText atNextChar
= atCaret
.NextPoint();
3355 if (atCaret
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3356 atNextChar
= atNextChar
.NextPoint();
3359 // If the text node is preformatted, just remove the previous character.
3360 if (textFragmentDataAtCaret
.IsPreformatted()) {
3361 return EditorDOMRangeInTexts(atCaret
, atNextChar
);
3364 // If next char is an ASCII whitespaces, delete all adjcent ASCII
3366 EditorDOMRangeInTexts rangeToDelete
;
3367 if (atCaret
.IsCharASCIISpace()) {
3368 EditorDOMPointInText startToDelete
=
3369 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3371 if (!startToDelete
.IsSet()) {
3373 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3374 return Err(NS_ERROR_FAILURE
);
3376 EditorDOMPointInText endToDelete
=
3377 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(atCaret
);
3378 if (!endToDelete
.IsSet()) {
3379 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3380 return Err(NS_ERROR_FAILURE
);
3382 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
3384 // if next char is not an ASCII white-space, remove it.
3386 rangeToDelete
= EditorDOMRangeInTexts(atCaret
, atNextChar
);
3389 // If there is no removable and visible content, we should do nothing.
3390 if (rangeToDelete
.Collapsed()) {
3391 return EditorDOMRangeInTexts();
3394 // And also delete invisible white-spaces if they become visible.
3395 TextFragmentData textFragmentDataAtStart
=
3396 rangeToDelete
.StartRef() != aPoint
3397 ? TextFragmentData(rangeToDelete
.StartRef(), aEditingHost
)
3398 : textFragmentDataAtCaret
;
3399 TextFragmentData textFragmentDataAtEnd
=
3400 rangeToDelete
.EndRef() != aPoint
3401 ? TextFragmentData(rangeToDelete
.EndRef(), aEditingHost
)
3402 : textFragmentDataAtCaret
;
3403 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
3404 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3405 return Err(NS_ERROR_FAILURE
);
3407 EditorDOMRangeInTexts extendedRangeToDelete
=
3408 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3409 textFragmentDataAtStart
, textFragmentDataAtEnd
);
3410 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
3411 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
3416 EditorDOMRange
WSRunScanner::GetRangesForDeletingAtomicContent(
3417 Element
* aEditingHost
, const nsIContent
& aAtomicContent
) {
3418 if (aAtomicContent
.IsHTMLElement(nsGkAtoms::br
)) {
3419 // Preceding white-spaces should be preserved, but the following
3420 // white-spaces should be invisible around `<br>` element.
3421 TextFragmentData
textFragmentDataAfterBRElement(
3422 EditorDOMPoint::After(aAtomicContent
), aEditingHost
);
3423 if (NS_WARN_IF(!textFragmentDataAfterBRElement
.IsInitialized())) {
3424 return EditorDOMRange(); // TODO: Make here return error with Err.
3426 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3427 textFragmentDataAfterBRElement
.GetNonCollapsedRangeInTexts(
3428 textFragmentDataAfterBRElement
3429 .InvisibleLeadingWhiteSpaceRangeRef());
3430 return followingInvisibleWhiteSpaces
.IsPositioned() &&
3431 !followingInvisibleWhiteSpaces
.Collapsed()
3433 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3434 followingInvisibleWhiteSpaces
.EndRef())
3436 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3437 EditorDOMPoint::After(aAtomicContent
));
3440 if (!HTMLEditUtils::IsBlockElement(aAtomicContent
)) {
3441 // Both preceding and following white-spaces around it should be preserved
3442 // around inline elements like `<img>`.
3443 return EditorDOMRange(
3444 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3445 EditorDOMPoint::After(aAtomicContent
));
3448 // Both preceding and following white-spaces can be invisible around a
3450 TextFragmentData
textFragmentDataBeforeAtomicContent(
3451 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)), aEditingHost
);
3452 if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent
.IsInitialized())) {
3453 return EditorDOMRange(); // TODO: Make here return error with Err.
3455 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces
=
3456 textFragmentDataBeforeAtomicContent
.GetNonCollapsedRangeInTexts(
3457 textFragmentDataBeforeAtomicContent
3458 .InvisibleTrailingWhiteSpaceRangeRef());
3459 TextFragmentData
textFragmentDataAfterAtomicContent(
3460 EditorDOMPoint::After(aAtomicContent
), aEditingHost
);
3461 if (NS_WARN_IF(!textFragmentDataAfterAtomicContent
.IsInitialized())) {
3462 return EditorDOMRange(); // TODO: Make here return error with Err.
3464 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
3465 textFragmentDataAfterAtomicContent
.GetNonCollapsedRangeInTexts(
3466 textFragmentDataAfterAtomicContent
3467 .InvisibleLeadingWhiteSpaceRangeRef());
3468 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet() &&
3469 followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3470 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3471 followingInvisibleWhiteSpaces
.EndRef());
3473 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet()) {
3474 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
3475 EditorDOMPoint::After(aAtomicContent
));
3477 if (followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
3478 return EditorDOMRange(
3479 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3480 followingInvisibleWhiteSpaces
.EndRef());
3482 return EditorDOMRange(
3483 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
3484 EditorDOMPoint::After(aAtomicContent
));
3488 EditorDOMRange
WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
3489 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
3490 const Element
& aRightBlockElement
,
3491 const EditorDOMPoint
& aPointContainingTheOtherBlock
) {
3492 MOZ_ASSERT(&aLeftBlockElement
!= &aRightBlockElement
);
3494 aPointContainingTheOtherBlock
.IsSet(),
3495 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
||
3496 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
);
3498 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
,
3499 aRightBlockElement
.IsInclusiveDescendantOf(
3500 aPointContainingTheOtherBlock
.GetChild()));
3502 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
,
3503 aLeftBlockElement
.IsInclusiveDescendantOf(
3504 aPointContainingTheOtherBlock
.GetChild()));
3506 !aPointContainingTheOtherBlock
.IsSet(),
3507 !aRightBlockElement
.IsInclusiveDescendantOf(&aLeftBlockElement
));
3509 !aPointContainingTheOtherBlock
.IsSet(),
3510 !aLeftBlockElement
.IsInclusiveDescendantOf(&aRightBlockElement
));
3511 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock
.IsSet(),
3512 EditorRawDOMPoint(const_cast<Element
*>(&aLeftBlockElement
))
3513 .IsBefore(EditorRawDOMPoint(
3514 const_cast<Element
*>(&aRightBlockElement
))));
3516 const Element
* editingHost
= aHTMLEditor
.GetActiveEditingHost();
3518 EditorDOMRange range
;
3519 // Include trailing invisible white-spaces in aLeftBlockElement.
3520 TextFragmentData
textFragmentDataAtEndOfLeftBlockElement(
3521 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
3522 ? aPointContainingTheOtherBlock
3523 : EditorDOMPoint::AtEndOf(const_cast<Element
&>(aLeftBlockElement
)),
3525 if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement
.IsInitialized())) {
3526 return EditorDOMRange(); // TODO: Make here return error with Err.
3528 if (textFragmentDataAtEndOfLeftBlockElement
.StartsFromBRElement() &&
3529 !aHTMLEditor
.IsVisibleBRElement(
3530 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr())) {
3531 // If the left block element ends with an invisible `<br>` element,
3532 // it'll be deleted (and it means there is no invisible trailing
3533 // white-spaces). Therefore, the range should start from the invisible
3535 range
.SetStart(EditorDOMPoint(
3536 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr()));
3538 const EditorDOMRange
& trailingWhiteSpaceRange
=
3539 textFragmentDataAtEndOfLeftBlockElement
3540 .InvisibleTrailingWhiteSpaceRangeRef();
3541 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
3542 range
.SetStart(trailingWhiteSpaceRange
.StartRef());
3544 range
.SetStart(textFragmentDataAtEndOfLeftBlockElement
.ScanStartRef());
3547 // Include leading invisible white-spaces in aRightBlockElement.
3548 TextFragmentData
textFragmentDataAtStartOfRightBlockElement(
3549 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
&&
3550 !aPointContainingTheOtherBlock
.IsEndOfContainer()
3551 ? aPointContainingTheOtherBlock
.NextPoint()
3552 : EditorDOMPoint(const_cast<Element
*>(&aRightBlockElement
), 0),
3554 if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement
.IsInitialized())) {
3555 return EditorDOMRange(); // TODO: Make here return error with Err.
3557 const EditorDOMRange
& leadingWhiteSpaceRange
=
3558 textFragmentDataAtStartOfRightBlockElement
3559 .InvisibleLeadingWhiteSpaceRangeRef();
3560 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
3561 range
.SetEnd(leadingWhiteSpaceRange
.EndRef());
3563 range
.SetEnd(textFragmentDataAtStartOfRightBlockElement
.ScanStartRef());
3570 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3571 Element
* aEditingHost
, const EditorDOMRange
& aRange
) {
3572 MOZ_ASSERT(aRange
.IsPositionedAndValid());
3573 MOZ_ASSERT(aRange
.EndRef().IsSetAndValid());
3574 MOZ_ASSERT(aRange
.StartRef().IsSetAndValid());
3576 EditorDOMRange result
;
3577 TextFragmentData
textFragmentDataAtStart(aRange
.StartRef(), aEditingHost
);
3578 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
3579 return EditorDOMRange(); // TODO: Make here return error with Err.
3581 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart
=
3582 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3583 textFragmentDataAtStart
.InvisibleLeadingWhiteSpaceRangeRef());
3584 if (invisibleLeadingWhiteSpacesAtStart
.IsPositioned() &&
3585 !invisibleLeadingWhiteSpacesAtStart
.Collapsed()) {
3586 result
.SetStart(invisibleLeadingWhiteSpacesAtStart
.StartRef());
3588 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart
=
3589 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
3590 textFragmentDataAtStart
.InvisibleTrailingWhiteSpaceRangeRef());
3591 if (invisibleTrailingWhiteSpacesAtStart
.IsPositioned() &&
3592 !invisibleTrailingWhiteSpacesAtStart
.Collapsed()) {
3594 invisibleTrailingWhiteSpacesAtStart
.StartRef().EqualsOrIsBefore(
3595 aRange
.StartRef()));
3596 result
.SetStart(invisibleTrailingWhiteSpacesAtStart
.StartRef());
3598 // If there is no invisible white-space and the line starts with a
3599 // text node, shrink the range to start of the text node.
3600 else if (!aRange
.StartRef().IsInTextNode() &&
3601 textFragmentDataAtStart
.StartsFromBlockBoundary() &&
3602 textFragmentDataAtStart
.EndRef().IsInTextNode()) {
3603 result
.SetStart(textFragmentDataAtStart
.EndRef());
3606 if (!result
.StartRef().IsSet()) {
3607 result
.SetStart(aRange
.StartRef());
3610 TextFragmentData
textFragmentDataAtEnd(aRange
.EndRef(), aEditingHost
);
3611 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3612 return EditorDOMRange(); // TODO: Make here return error with Err.
3614 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3615 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3616 textFragmentDataAtEnd
.InvisibleTrailingWhiteSpaceRangeRef());
3617 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3618 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3619 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3621 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
3622 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
3623 textFragmentDataAtEnd
.InvisibleLeadingWhiteSpaceRangeRef());
3624 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
3625 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
3626 MOZ_ASSERT(aRange
.EndRef().EqualsOrIsBefore(
3627 invisibleLeadingWhiteSpacesAtEnd
.EndRef()));
3628 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
3630 // If there is no invisible white-space and the line ends with a text
3631 // node, shrink the range to end of the text node.
3632 else if (!aRange
.EndRef().IsInTextNode() &&
3633 textFragmentDataAtEnd
.EndsByBlockBoundary() &&
3634 textFragmentDataAtEnd
.StartRef().IsInTextNode()) {
3635 result
.SetEnd(EditorDOMPoint::AtEndOf(
3636 *textFragmentDataAtEnd
.StartRef().ContainerAsText()));
3639 if (!result
.EndRef().IsSet()) {
3640 result
.SetEnd(aRange
.EndRef());
3642 MOZ_ASSERT(result
.IsPositionedAndValid());
3646 /******************************************************************************
3647 * Utilities for other things.
3648 ******************************************************************************/
3651 Result
<bool, nsresult
>
3652 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
3653 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
3654 const Element
* aEditingHost
) {
3655 MOZ_ASSERT(aRange
.IsPositioned());
3656 MOZ_ASSERT(!aRange
.IsInSelection(),
3657 "Changing range in selection may cause running script");
3659 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
3660 NS_WARN_IF(!aRange
.GetEndContainer())) {
3661 return Err(NS_ERROR_FAILURE
);
3664 if (!aRange
.GetStartContainer()->IsContent() ||
3665 !aRange
.GetEndContainer()->IsContent()) {
3669 // If the range crosses a block boundary, we should do nothing for now
3670 // because it hits a bug of inserting a padding `<br>` element after
3671 // joining the blocks.
3672 if (HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3673 *aRange
.GetStartContainer()->AsContent(), aEditingHost
) !=
3674 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3675 *aRange
.GetEndContainer()->AsContent(), aEditingHost
)) {
3679 nsIContent
* startContent
= nullptr;
3680 if (aRange
.GetStartContainer() && aRange
.GetStartContainer()->IsText() &&
3681 aRange
.GetStartContainer()->AsText()->Length() == aRange
.StartOffset()) {
3682 // If next content is a visible `<br>` element, special inline content
3683 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3684 // element like `<hr>`, the range should start with it.
3685 TextFragmentData
textFragmentDataAtStart(
3686 EditorRawDOMPoint(aRange
.StartRef()), aEditingHost
);
3687 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
3688 return Err(NS_ERROR_FAILURE
);
3690 if (textFragmentDataAtStart
.EndsByBRElement()) {
3691 if (aHTMLEditor
.IsVisibleBRElement(
3692 textFragmentDataAtStart
.EndReasonBRElementPtr())) {
3693 startContent
= textFragmentDataAtStart
.EndReasonBRElementPtr();
3695 } else if (textFragmentDataAtStart
.EndsBySpecialContent() ||
3696 (textFragmentDataAtStart
.EndsByOtherBlockElement() &&
3697 !HTMLEditUtils::IsContainerNode(
3698 *textFragmentDataAtStart
3699 .EndReasonOtherBlockElementPtr()))) {
3700 startContent
= textFragmentDataAtStart
.GetEndReasonContent();
3704 nsIContent
* endContent
= nullptr;
3705 if (aRange
.GetEndContainer() && aRange
.GetEndContainer()->IsText() &&
3706 !aRange
.EndOffset()) {
3707 // If previous content is a visible `<br>` element, special inline content
3708 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3709 // element like `<hr>`, the range should end after it.
3710 TextFragmentData
textFragmentDataAtEnd(EditorRawDOMPoint(aRange
.EndRef()),
3712 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
3713 return Err(NS_ERROR_FAILURE
);
3715 if (textFragmentDataAtEnd
.StartsFromBRElement()) {
3716 if (aHTMLEditor
.IsVisibleBRElement(
3717 textFragmentDataAtEnd
.StartReasonBRElementPtr())) {
3718 endContent
= textFragmentDataAtEnd
.StartReasonBRElementPtr();
3720 } else if (textFragmentDataAtEnd
.StartsFromSpecialContent() ||
3721 (textFragmentDataAtEnd
.StartsFromOtherBlockElement() &&
3722 !HTMLEditUtils::IsContainerNode(
3723 *textFragmentDataAtEnd
3724 .StartReasonOtherBlockElementPtr()))) {
3725 endContent
= textFragmentDataAtEnd
.GetStartReasonContent();
3729 if (!startContent
&& !endContent
) {
3733 nsresult rv
= aRange
.SetStartAndEnd(
3734 startContent
? RangeBoundary(
3735 startContent
->GetParentNode(),
3736 startContent
->GetPreviousSibling()) // at startContent
3737 : aRange
.StartRef(),
3738 endContent
? RangeBoundary(endContent
->GetParentNode(),
3739 endContent
) // after endContent
3741 if (NS_FAILED(rv
)) {
3742 NS_WARNING("nsRange::SetStartAndEnd() failed");
3748 } // namespace mozilla