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 "EditorDOMPoint.h"
9 #include "EditorUtils.h"
10 #include "ErrorList.h"
11 #include "HTMLEditHelpers.h" // for MoveNodeResult, SplitNodeResult
12 #include "HTMLEditor.h"
13 #include "HTMLEditorNestedClasses.h" // for AutoMoveOneLineHandler
14 #include "HTMLEditUtils.h"
15 #include "SelectionState.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Casting.h"
19 #include "mozilla/SelectionState.h"
20 #include "mozilla/mozalloc.h"
21 #include "mozilla/OwningNonNull.h"
22 #include "mozilla/RangeUtils.h"
23 #include "mozilla/StaticPrefs_dom.h" // for StaticPrefs::dom_*
24 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_*
25 #include "mozilla/InternalMutationEvent.h"
26 #include "mozilla/dom/AncestorIterator.h"
28 #include "nsAString.h"
30 #include "nsContentUtils.h"
33 #include "nsIContent.h"
34 #include "nsIContentInlines.h"
35 #include "nsISupportsImpl.h"
38 #include "nsTextFragment.h"
44 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
45 using LeafNodeTypes
= HTMLEditUtils::LeafNodeTypes
;
46 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
48 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
49 const EditorDOMPoint
& aPoint
) const;
50 template WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
51 const EditorRawDOMPoint
& aPoint
) const;
52 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
53 const EditorDOMPoint
& aPoint
) const;
54 template WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
55 const EditorRawDOMPoint
& aPoint
) const;
56 template EditorDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
57 Text
& aTextNode
, const Element
* aAncestorLimiter
);
58 template EditorRawDOMPoint
WSRunScanner::GetAfterLastVisiblePoint(
59 Text
& aTextNode
, const Element
* aAncestorLimiter
);
60 template EditorDOMPoint
WSRunScanner::GetFirstVisiblePoint(
61 Text
& aTextNode
, const Element
* aAncestorLimiter
);
62 template EditorRawDOMPoint
WSRunScanner::GetFirstVisiblePoint(
63 Text
& aTextNode
, const Element
* aAncestorLimiter
);
65 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
66 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aScanStartPoint
);
67 template nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
68 HTMLEditor
& aHTMLEditor
, const EditorDOMPointInText
& aScanStartPoint
);
70 template WSRunScanner::TextFragmentData::TextFragmentData(
71 const EditorDOMPoint
& aPoint
, const Element
* aEditingHost
,
72 BlockInlineCheck aBlockInlineCheck
);
73 template WSRunScanner::TextFragmentData::TextFragmentData(
74 const EditorRawDOMPoint
& aPoint
, const Element
* aEditingHost
,
75 BlockInlineCheck aBlockInlineCheck
);
76 template WSRunScanner::TextFragmentData::TextFragmentData(
77 const EditorDOMPointInText
& aPoint
, const Element
* aEditingHost
,
78 BlockInlineCheck aBlockInlineCheck
);
80 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
81 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint
,
82 const EditorDOMPoint
& aPoint
);
83 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
84 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint
,
85 const EditorRawDOMPoint
& aPoint
);
86 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
87 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint
,
88 const EditorDOMPointInText
& aPoint
);
89 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
90 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint
,
91 const EditorRawDOMPointInText
& aPoint
);
93 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
94 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint
,
95 const EditorDOMPoint
& aPoint
);
96 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
97 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint
,
98 const EditorRawDOMPoint
& aPoint
);
99 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
100 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint
,
101 const EditorDOMPointInText
& aPoint
);
102 NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(
103 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint
,
104 const EditorRawDOMPointInText
& aPoint
);
106 Result
<EditorDOMPoint
, nsresult
>
107 WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement(
108 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
,
109 const Element
& aSplittingBlockElement
) {
110 if (NS_WARN_IF(!aPointToSplit
.IsInContentNode()) ||
111 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement
)) ||
112 NS_WARN_IF(!EditorUtils::IsEditableContent(
113 *aPointToSplit
.ContainerAs
<nsIContent
>(), EditorType::HTML
))) {
114 return Err(NS_ERROR_FAILURE
);
117 // The container of aPointToSplit may be not splittable, e.g., selection
118 // may be collapsed **in** a `<br>` element or a comment node. So, look
119 // for splittable point with climbing the tree up.
120 EditorDOMPoint
pointToSplit(aPointToSplit
);
121 for (nsIContent
* content
: aPointToSplit
.ContainerAs
<nsIContent
>()
122 ->InclusiveAncestorsOfType
<nsIContent
>()) {
123 if (content
== &aSplittingBlockElement
) {
126 if (HTMLEditUtils::IsSplittableNode(*content
)) {
129 pointToSplit
.Set(content
);
133 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &pointToSplit
);
135 nsresult rv
= WhiteSpaceVisibilityKeeper::
136 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(aHTMLEditor
,
138 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
139 return Err(NS_ERROR_EDITOR_DESTROYED
);
143 "WhiteSpaceVisibilityKeeper::"
144 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() failed");
149 if (NS_WARN_IF(!pointToSplit
.IsInContentNode()) ||
151 !pointToSplit
.ContainerAs
<nsIContent
>()->IsInclusiveDescendantOf(
152 &aSplittingBlockElement
)) ||
153 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(aSplittingBlockElement
)) ||
154 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(
155 *pointToSplit
.ContainerAs
<nsIContent
>()))) {
156 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
163 Result
<EditActionResult
, nsresult
> WhiteSpaceVisibilityKeeper::
164 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
165 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
166 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtRightBlockChild
,
167 const Maybe
<nsAtom
*>& aListElementTagName
,
168 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
169 const Element
& aEditingHost
) {
171 EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
172 MOZ_ASSERT(&aRightBlockElement
== aAtRightBlockChild
.GetContainer());
174 // NOTE: This method may extend deletion range:
175 // - to delete invisible white-spaces at end of aLeftBlockElement
176 // - to delete invisible white-spaces at start of
177 // afterRightBlockChild.GetChild()
178 // - to delete invisible white-spaces before afterRightBlockChild.GetChild()
179 // - to delete invisible `<br>` element at end of aLeftBlockElement
182 Result
<CaretPoint
, nsresult
> caretPointOrError
=
183 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
184 aHTMLEditor
, EditorDOMPoint::AtEndOf(aLeftBlockElement
));
185 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
187 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
189 return caretPointOrError
.propagateErr();
191 // Ignore caret suggestion because there was
192 // AutoTransactionsConserveSelection.
193 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
196 // Check whether aLeftBlockElement is a descendant of aRightBlockElement.
197 if (aHTMLEditor
.MayHaveMutationEventListeners()) {
198 EditorDOMPoint leftBlockContainingPointInRightBlockElement
;
199 if (aHTMLEditor
.MayHaveMutationEventListeners() &&
200 MOZ_UNLIKELY(!EditorUtils::IsDescendantOf(
201 aLeftBlockElement
, aRightBlockElement
,
202 &leftBlockContainingPointInRightBlockElement
))) {
204 "Deleting invisible whitespace at end of left block element caused "
205 "moving the left block element outside the right block element");
206 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
209 if (MOZ_UNLIKELY(leftBlockContainingPointInRightBlockElement
!=
210 aAtRightBlockChild
)) {
212 "Deleting invisible whitespace at end of left block element caused "
213 "changing the left block element in the right block element");
214 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
217 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aRightBlockElement
,
218 EditorType::HTML
))) {
220 "Deleting invisible whitespace at end of left block element caused "
221 "making the right block element non-editable");
222 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
225 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aLeftBlockElement
,
226 EditorType::HTML
))) {
228 "Deleting invisible whitespace at end of left block element caused "
229 "making the left block element non-editable");
230 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
234 OwningNonNull
<Element
> rightBlockElement
= aRightBlockElement
;
235 EditorDOMPoint afterRightBlockChild
= aAtRightBlockChild
.NextPoint();
237 // We can't just track rightBlockElement because it's an Element.
238 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(),
239 &afterRightBlockChild
);
240 Result
<CaretPoint
, nsresult
> caretPointOrError
=
241 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
242 aHTMLEditor
, afterRightBlockChild
);
243 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
245 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
247 return caretPointOrError
.propagateErr();
249 // Ignore caret suggestion because there was
250 // AutoTransactionsConserveSelection.
251 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
253 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
254 // Do we really need to do update rightBlockElement here??
255 // XXX And afterRightBlockChild.GetContainerAs<Element>() always returns
256 // an element pointer so that probably here should not use
257 // accessors of EditorDOMPoint, should use DOM API directly instead.
258 if (afterRightBlockChild
.GetContainerAs
<Element
>()) {
259 rightBlockElement
= *afterRightBlockChild
.ContainerAs
<Element
>();
260 } else if (NS_WARN_IF(
261 !afterRightBlockChild
.GetContainerParentAs
<Element
>())) {
262 return Err(NS_ERROR_UNEXPECTED
);
264 rightBlockElement
= *afterRightBlockChild
.GetContainerParentAs
<Element
>();
269 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
270 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
271 aHTMLEditor
.ComputeEditingHost(),
272 EditorDOMPoint::AtEndOf(aLeftBlockElement
),
273 BlockInlineCheck::UseComputedDisplayStyle
);
275 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
276 "The preceding invisible BR element computation was different");
277 auto ret
= EditActionResult::IgnoredResult();
278 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
279 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
280 // AutoInclusiveAncestorBlockElementsJoiner.
281 if (NS_WARN_IF(aListElementTagName
.isSome())) {
282 // Since 2002, here was the following comment:
283 // > The idea here is to take all children in rightListElement that are
284 // > past offset, and pull them into leftlistElement.
285 // However, this has never been performed because we are here only when
286 // neither left list nor right list is a descendant of the other but
287 // in such case, getting a list item in the right list node almost
288 // always failed since a variable for offset of
289 // rightListElement->GetChildAt() was not initialized. So, it might be
290 // a bug, but we should keep this traditional behavior for now. If you
291 // find when we get here, please remove this comment if we don't need to
292 // do it. Otherwise, please move children of the right list node to the
293 // end of the left list node.
295 // XXX Although, we do nothing here, but for keeping traditional
296 // behavior, we should mark as handled.
299 // XXX Why do we ignore the result of AutoMoveOneLineHandler::Run()?
300 NS_ASSERTION(rightBlockElement
== afterRightBlockChild
.GetContainer(),
301 "The relation is not guaranteed but assumed");
303 Result
<bool, nsresult
> firstLineHasContent
=
304 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
305 EditorDOMPoint(rightBlockElement
, afterRightBlockChild
.Offset()),
307 #endif // #ifdef DEBUG
308 HTMLEditor::AutoMoveOneLineHandler
lineMoverToEndOfLeftBlock(
310 nsresult rv
= lineMoverToEndOfLeftBlock
.Prepare(
312 EditorDOMPoint(rightBlockElement
, afterRightBlockChild
.Offset()),
315 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
318 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
319 lineMoverToEndOfLeftBlock
.Run(aHTMLEditor
, aEditingHost
);
320 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
321 NS_WARNING("AutoMoveOneLineHandler::Run() failed");
322 return moveNodeResult
.propagateErr();
326 MOZ_ASSERT(!firstLineHasContent
.isErr());
327 if (firstLineHasContent
.inspect()) {
328 NS_ASSERTION(moveNodeResult
.inspect().Handled(),
329 "Failed to consider whether moving or not something");
331 NS_ASSERTION(moveNodeResult
.inspect().Ignored(),
332 "Failed to consider whether moving or not something");
334 #endif // #ifdef DEBUG
336 // We don't need to update selection here because of dontChangeMySelection
338 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
339 ret
|= moveNodeResult
.unwrap();
340 // Now, all children of rightBlockElement were moved to leftBlockElement.
341 // So, afterRightBlockChild is now invalid.
342 afterRightBlockChild
.Clear();
345 if (!invisibleBRElementAtEndOfLeftBlockElement
||
346 !invisibleBRElementAtEndOfLeftBlockElement
->IsInComposedDoc()) {
350 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
351 *invisibleBRElementAtEndOfLeftBlockElement
);
353 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
356 return EditActionResult::HandledResult();
360 Result
<EditActionResult
, nsresult
> WhiteSpaceVisibilityKeeper::
361 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
362 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
363 Element
& aRightBlockElement
, const EditorDOMPoint
& aAtLeftBlockChild
,
364 nsIContent
& aLeftContentInBlock
,
365 const Maybe
<nsAtom
*>& aListElementTagName
,
366 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
367 const Element
& aEditingHost
) {
369 EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
371 &aLeftBlockElement
== &aLeftContentInBlock
||
372 EditorUtils::IsDescendantOf(aLeftContentInBlock
, aLeftBlockElement
));
373 MOZ_ASSERT(&aLeftBlockElement
== aAtLeftBlockChild
.GetContainer());
375 // NOTE: This method may extend deletion range:
376 // - to delete invisible white-spaces at start of aRightBlockElement
377 // - to delete invisible white-spaces before aRightBlockElement
378 // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild()
379 // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild()
380 // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
383 Result
<CaretPoint
, nsresult
> caretPointOrError
=
384 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
385 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0));
386 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
388 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
390 return caretPointOrError
.propagateErr();
392 // Ignore caret suggestion because there was
393 // AutoTransactionsConserveSelection.
394 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
397 // Check whether aRightBlockElement is a descendant of aLeftBlockElement.
398 if (aHTMLEditor
.MayHaveMutationEventListeners()) {
399 EditorDOMPoint rightBlockContainingPointInLeftBlockElement
;
400 if (aHTMLEditor
.MayHaveMutationEventListeners() &&
401 MOZ_UNLIKELY(!EditorUtils::IsDescendantOf(
402 aRightBlockElement
, aLeftBlockElement
,
403 &rightBlockContainingPointInLeftBlockElement
))) {
405 "Deleting invisible whitespace at start of right block element "
406 "caused moving the right block element outside the left block "
408 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
411 if (MOZ_UNLIKELY(rightBlockContainingPointInLeftBlockElement
!=
412 aAtLeftBlockChild
)) {
414 "Deleting invisible whitespace at start of right block element "
415 "caused changing the right block element position in the left block "
417 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
420 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aLeftBlockElement
,
421 EditorType::HTML
))) {
423 "Deleting invisible whitespace at start of right block element "
424 "caused making the left block element non-editable");
425 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
428 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aRightBlockElement
,
429 EditorType::HTML
))) {
431 "Deleting invisible whitespace at start of right block element "
432 "caused making the right block element non-editable");
433 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
437 OwningNonNull
<Element
> originalLeftBlockElement
= aLeftBlockElement
;
438 OwningNonNull
<Element
> leftBlockElement
= aLeftBlockElement
;
439 EditorDOMPoint
atLeftBlockChild(aAtLeftBlockChild
);
441 // We can't just track leftBlockElement because it's an Element, so track
443 AutoTrackDOMPoint
tracker(aHTMLEditor
.RangeUpdaterRef(), &atLeftBlockChild
);
444 Result
<CaretPoint
, nsresult
> caretPointOrError
=
445 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
446 aHTMLEditor
, EditorDOMPoint(atLeftBlockChild
.GetContainer(),
447 atLeftBlockChild
.Offset()));
448 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
450 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
452 return caretPointOrError
.propagateErr();
454 // Ignore caret suggestion because there was
455 // AutoTransactionsConserveSelection.
456 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
458 if (MOZ_UNLIKELY(!atLeftBlockChild
.IsSetAndValid())) {
460 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
461 "unexpected DOM tree");
462 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
464 // XXX atLeftBlockChild.GetContainerAs<Element>() should always return
465 // an element pointer so that probably here should not use
466 // accessors of EditorDOMPoint, should use DOM API directly instead.
467 if (Element
* nearestAncestor
=
468 atLeftBlockChild
.GetContainerOrContainerParentElement()) {
469 leftBlockElement
= *nearestAncestor
;
471 return Err(NS_ERROR_UNEXPECTED
);
475 RefPtr
<HTMLBRElement
> invisibleBRElementBeforeLeftBlockElement
=
476 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
477 aHTMLEditor
.ComputeEditingHost(), atLeftBlockChild
,
478 BlockInlineCheck::UseComputedDisplayStyle
);
480 aPrecedingInvisibleBRElement
== invisibleBRElementBeforeLeftBlockElement
,
481 "The preceding invisible BR element computation was different");
482 auto ret
= EditActionResult::IgnoredResult();
483 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
484 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
485 // AutoInclusiveAncestorBlockElementsJoiner.
486 if (aListElementTagName
.isSome()) {
487 // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
488 MOZ_ASSERT(originalLeftBlockElement
== atLeftBlockChild
.GetContainer(),
489 "This is not guaranteed, but assumed");
491 Result
<bool, nsresult
> rightBlockHasContent
=
492 aHTMLEditor
.CanMoveChildren(aRightBlockElement
, aLeftBlockElement
);
493 #endif // #ifdef DEBUG
494 // TODO: Stop using HTMLEditor::PreserveWhiteSpaceStyle::No due to no tests.
495 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
496 aHTMLEditor
.MoveChildrenWithTransaction(
498 EditorDOMPoint(atLeftBlockChild
.GetContainer(),
499 atLeftBlockChild
.Offset()),
500 HTMLEditor::PreserveWhiteSpaceStyle::No
,
501 HTMLEditor::RemoveIfCommentNode::Yes
);
502 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
503 if (NS_WARN_IF(moveNodeResult
.inspectErr() ==
504 NS_ERROR_EDITOR_DESTROYED
)) {
505 return Err(moveNodeResult
.unwrapErr());
508 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
511 MOZ_ASSERT(!rightBlockHasContent
.isErr());
512 if (rightBlockHasContent
.inspect()) {
513 NS_ASSERTION(moveNodeResult
.inspect().Handled(),
514 "Failed to consider whether moving or not children");
516 NS_ASSERTION(moveNodeResult
.inspect().Ignored(),
517 "Failed to consider whether moving or not children");
519 #endif // #ifdef DEBUG
520 // We don't need to update selection here because of dontChangeMySelection
522 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
523 ret
|= moveNodeResult
.unwrap();
525 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
526 atLeftBlockChild
.Clear();
528 // Left block is a parent of right block, and the parent of the previous
529 // visible content. Right block is a child and contains the contents we
532 EditorDOMPoint pointToMoveFirstLineContent
;
533 if (&aLeftContentInBlock
== leftBlockElement
) {
534 // We are working with valid HTML, aLeftContentInBlock is a block element,
535 // and is therefore allowed to contain aRightBlockElement. This is the
536 // simple case, we will simply move the content in aRightBlockElement
538 pointToMoveFirstLineContent
= atLeftBlockChild
;
539 MOZ_ASSERT(pointToMoveFirstLineContent
.GetContainer() ==
542 if (NS_WARN_IF(!aLeftContentInBlock
.IsInComposedDoc())) {
543 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
545 // We try to work as well as possible with HTML that's already invalid.
546 // Although "right block" is a block, and a block must not be contained
547 // in inline elements, reality is that broken documents do exist. The
548 // DIRECT parent of "left NODE" might be an inline element. Previous
549 // versions of this code skipped inline parents until the first block
550 // parent was found (and used "left block" as the destination).
551 // However, in some situations this strategy moves the content to an
552 // unexpected position. (see bug 200416) The new idea is to make the
553 // moving content a sibling, next to the previous visible content.
554 pointToMoveFirstLineContent
.SetAfter(&aLeftContentInBlock
);
555 if (NS_WARN_IF(!pointToMoveFirstLineContent
.IsInContentNode())) {
556 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
560 MOZ_ASSERT(pointToMoveFirstLineContent
.IsSetAndValid());
562 // Because we don't want the moving content to receive the style of the
563 // previous content, we split the previous content's style.
566 Result
<bool, nsresult
> firstLineHasContent
=
567 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
568 EditorDOMPoint(&aRightBlockElement
, 0u), aEditingHost
);
569 #endif // #ifdef DEBUG
571 if (&aLeftContentInBlock
!= &aEditingHost
) {
572 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
573 aHTMLEditor
.SplitAncestorStyledInlineElementsAt(
574 pointToMoveFirstLineContent
, EditorInlineStyle::RemoveAllStyles(),
575 HTMLEditor::SplitAtEdges::eDoNotCreateEmptyContainer
);
576 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
577 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
578 return splitNodeResult
.propagateErr();
580 SplitNodeResult unwrappedSplitNodeResult
= splitNodeResult
.unwrap();
581 nsresult rv
= unwrappedSplitNodeResult
.SuggestCaretPointTo(
582 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
583 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
585 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
588 if (!unwrappedSplitNodeResult
.DidSplit()) {
589 // If nothing was split, we should move the first line content to after
590 // the parent inline elements.
591 for (EditorDOMPoint parentPoint
= pointToMoveFirstLineContent
;
592 pointToMoveFirstLineContent
.IsEndOfContainer() &&
593 pointToMoveFirstLineContent
.IsInContentNode();
594 pointToMoveFirstLineContent
= EditorDOMPoint::After(
595 *pointToMoveFirstLineContent
.ContainerAs
<nsIContent
>())) {
596 if (pointToMoveFirstLineContent
.GetContainer() ==
597 &aLeftBlockElement
||
598 NS_WARN_IF(pointToMoveFirstLineContent
.GetContainer() ==
603 if (NS_WARN_IF(!pointToMoveFirstLineContent
.IsInContentNode())) {
604 return Err(NS_ERROR_FAILURE
);
606 } else if (unwrappedSplitNodeResult
.Handled()) {
607 // If se split something, we should move the first line contents before
608 // the right elements.
609 if (nsIContent
* nextContentAtSplitPoint
=
610 unwrappedSplitNodeResult
.GetNextContent()) {
611 pointToMoveFirstLineContent
.Set(nextContentAtSplitPoint
);
612 if (NS_WARN_IF(!pointToMoveFirstLineContent
.IsInContentNode())) {
613 return Err(NS_ERROR_FAILURE
);
616 pointToMoveFirstLineContent
=
617 unwrappedSplitNodeResult
.AtSplitPoint
<EditorDOMPoint
>();
618 if (NS_WARN_IF(!pointToMoveFirstLineContent
.IsInContentNode())) {
619 return Err(NS_ERROR_FAILURE
);
623 MOZ_DIAGNOSTIC_ASSERT(pointToMoveFirstLineContent
.IsSetAndValid());
626 HTMLEditor::AutoMoveOneLineHandler
lineMoverToPoint(
627 pointToMoveFirstLineContent
);
628 nsresult rv
= lineMoverToPoint
.Prepare(
629 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0u), aEditingHost
);
631 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
634 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
635 lineMoverToPoint
.Run(aHTMLEditor
, aEditingHost
);
636 if (moveNodeResult
.isErr()) {
637 NS_WARNING("AutoMoveOneLineHandler::Run() failed");
638 return moveNodeResult
.propagateErr();
642 MOZ_ASSERT(!firstLineHasContent
.isErr());
643 if (firstLineHasContent
.inspect()) {
644 NS_ASSERTION(moveNodeResult
.inspect().Handled(),
645 "Failed to consider whether moving or not something");
647 NS_ASSERTION(moveNodeResult
.inspect().Ignored(),
648 "Failed to consider whether moving or not something");
650 #endif // #ifdef DEBUG
652 // We don't need to update selection here because of dontChangeMySelection
654 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
655 ret
|= moveNodeResult
.unwrap();
658 if (!invisibleBRElementBeforeLeftBlockElement
||
659 !invisibleBRElementBeforeLeftBlockElement
->IsInComposedDoc()) {
663 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
664 *invisibleBRElementBeforeLeftBlockElement
);
666 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
669 return EditActionResult::HandledResult();
673 Result
<EditActionResult
, nsresult
> WhiteSpaceVisibilityKeeper::
674 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
675 HTMLEditor
& aHTMLEditor
, Element
& aLeftBlockElement
,
676 Element
& aRightBlockElement
, const Maybe
<nsAtom
*>& aListElementTagName
,
677 const HTMLBRElement
* aPrecedingInvisibleBRElement
,
678 const Element
& aEditingHost
) {
680 !EditorUtils::IsDescendantOf(aLeftBlockElement
, aRightBlockElement
));
682 !EditorUtils::IsDescendantOf(aRightBlockElement
, aLeftBlockElement
));
684 // NOTE: This method may extend deletion range:
685 // - to delete invisible white-spaces at end of aLeftBlockElement
686 // - to delete invisible white-spaces at start of aRightBlockElement
687 // - to delete invisible `<br>` element at end of aLeftBlockElement
689 // Adjust white-space at block boundaries
691 Result
<CaretPoint
, nsresult
> caretPointOrError
=
692 WhiteSpaceVisibilityKeeper::
693 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
695 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement
),
696 EditorDOMPoint(&aRightBlockElement
, 0)),
698 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
700 "WhiteSpaceVisibilityKeeper::"
701 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() "
703 return caretPointOrError
.propagateErr();
705 // Ignore caret point suggestion because there was
706 // AutoTransactionsConserveSelection.
707 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
710 RefPtr
<HTMLBRElement
> invisibleBRElementAtEndOfLeftBlockElement
=
711 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
712 aHTMLEditor
.ComputeEditingHost(),
713 EditorDOMPoint::AtEndOf(aLeftBlockElement
),
714 BlockInlineCheck::UseComputedDisplayStyle
);
716 aPrecedingInvisibleBRElement
== invisibleBRElementAtEndOfLeftBlockElement
,
717 "The preceding invisible BR element computation was different");
718 auto ret
= EditActionResult::IgnoredResult();
719 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
720 if (aListElementTagName
.isSome() ||
721 // TODO: We should stop merging entire blocks even if they have same
722 // white-space style because Chrome behave so. However, it's risky to
723 // change our behavior in the major cases so that we should do it in
724 // a bug to manage only the change.
725 (aLeftBlockElement
.NodeInfo()->NameAtom() ==
726 aRightBlockElement
.NodeInfo()->NameAtom() &&
727 EditorUtils::GetComputedWhiteSpaceStyles(aLeftBlockElement
) ==
728 EditorUtils::GetComputedWhiteSpaceStyles(aRightBlockElement
))) {
729 // Nodes are same type. merge them.
730 EditorDOMPoint atFirstChildOfRightNode
;
731 nsresult rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
732 aLeftBlockElement
, aRightBlockElement
, &atFirstChildOfRightNode
);
733 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
734 return Err(NS_ERROR_EDITOR_DESTROYED
);
736 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
737 "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
738 " failed, but ignored");
739 if (aListElementTagName
.isSome() && atFirstChildOfRightNode
.IsSet()) {
740 Result
<CreateElementResult
, nsresult
> convertListTypeResult
=
741 aHTMLEditor
.ChangeListElementType(
742 aRightBlockElement
, MOZ_KnownLive(*aListElementTagName
.ref()),
744 if (MOZ_UNLIKELY(convertListTypeResult
.isErr())) {
745 if (NS_WARN_IF(convertListTypeResult
.inspectErr() ==
746 NS_ERROR_EDITOR_DESTROYED
)) {
747 return Err(NS_ERROR_EDITOR_DESTROYED
);
749 NS_WARNING("HTMLEditor::ChangeListElementType() failed, but ignored");
751 // There is AutoTransactionConserveSelection above, therefore, we don't
752 // need to update selection here.
753 convertListTypeResult
.inspect().IgnoreCaretPointSuggestion();
759 Result
<bool, nsresult
> firstLineHasContent
=
760 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
761 EditorDOMPoint(&aRightBlockElement
, 0u), aEditingHost
);
762 #endif // #ifdef DEBUG
764 // Nodes are dissimilar types.
765 HTMLEditor::AutoMoveOneLineHandler
lineMoverToEndOfLeftBlock(
767 nsresult rv
= lineMoverToEndOfLeftBlock
.Prepare(
768 aHTMLEditor
, EditorDOMPoint(&aRightBlockElement
, 0u), aEditingHost
);
770 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
773 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
774 lineMoverToEndOfLeftBlock
.Run(aHTMLEditor
, aEditingHost
);
775 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
776 NS_WARNING("AutoMoveOneLineHandler::Run() failed");
777 return moveNodeResult
.propagateErr();
781 MOZ_ASSERT(!firstLineHasContent
.isErr());
782 if (firstLineHasContent
.inspect()) {
783 NS_ASSERTION(moveNodeResult
.inspect().Handled(),
784 "Failed to consider whether moving or not something");
786 NS_ASSERTION(moveNodeResult
.inspect().Ignored(),
787 "Failed to consider whether moving or not something");
789 #endif // #ifdef DEBUG
791 // We don't need to update selection here because of dontChangeMySelection
793 moveNodeResult
.inspect().IgnoreCaretPointSuggestion();
794 ret
|= moveNodeResult
.unwrap();
797 if (!invisibleBRElementAtEndOfLeftBlockElement
||
798 !invisibleBRElementAtEndOfLeftBlockElement
->IsInComposedDoc()) {
803 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(
804 *invisibleBRElementAtEndOfLeftBlockElement
);
805 // XXX In other top level if blocks, the result of
806 // DeleteNodeWithTransaction() is ignored. Why does only this result
809 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
812 return EditActionResult::HandledResult();
816 Result
<CreateElementResult
, nsresult
>
817 WhiteSpaceVisibilityKeeper::InsertBRElement(
818 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToInsert
,
819 const Element
& aEditingHost
) {
820 if (MOZ_UNLIKELY(NS_WARN_IF(!aPointToInsert
.IsSet()))) {
821 return Err(NS_ERROR_INVALID_ARG
);
824 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
825 // meanwhile, the pre case is handled in HandleInsertText() in
826 // HTMLEditSubActionHandler.cpp
828 TextFragmentData
textFragmentDataAtInsertionPoint(
829 aPointToInsert
, &aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
831 NS_WARN_IF(!textFragmentDataAtInsertionPoint
.IsInitialized()))) {
832 return Err(NS_ERROR_FAILURE
);
834 EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine
=
835 textFragmentDataAtInsertionPoint
836 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
837 EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine
=
838 textFragmentDataAtInsertionPoint
839 .GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert
);
840 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpaces
=
841 !invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned() ||
842 !invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()
843 ? Some(textFragmentDataAtInsertionPoint
.VisibleWhiteSpacesDataRef())
845 const PointPosition pointPositionWithVisibleWhiteSpaces
=
846 visibleWhiteSpaces
.isSome() && visibleWhiteSpaces
.ref().IsInitialized()
847 ? visibleWhiteSpaces
.ref().ComparePoint(aPointToInsert
)
848 : PointPosition::NotInSameDOMTree
;
850 EditorDOMPoint
pointToInsert(aPointToInsert
);
851 EditorDOMPoint atNBSPReplaceableWithSP
;
852 if (!invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned() &&
853 (pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
||
854 pointPositionWithVisibleWhiteSpaces
== PointPosition::EndOfFragment
)) {
855 atNBSPReplaceableWithSP
=
856 textFragmentDataAtInsertionPoint
857 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
859 .To
<EditorDOMPoint
>();
863 if (invisibleTrailingWhiteSpaceRangeOfCurrentLine
.IsPositioned()) {
864 if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Collapsed()) {
865 // XXX Why don't we remove all of the invisible white-spaces?
866 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef() ==
868 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
870 AutoTrackDOMPoint
trackEndOfLineNBSP(aHTMLEditor
.RangeUpdaterRef(),
871 &atNBSPReplaceableWithSP
);
872 AutoTrackDOMRange
trackLeadingWhiteSpaceRange(
873 aHTMLEditor
.RangeUpdaterRef(),
874 &invisibleLeadingWhiteSpaceRangeOfNewLine
);
875 Result
<CaretPoint
, nsresult
> caretPointOrError
=
876 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
877 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.StartRef(),
878 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.EndRef(),
879 HTMLEditor::TreatEmptyTextNodes::
880 KeepIfContainerOfRangeBoundaries
);
881 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
883 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
884 return caretPointOrError
.propagateErr();
886 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
887 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
888 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
889 SuggestCaret::AndIgnoreTrivialError
});
891 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
894 NS_WARNING_ASSERTION(
895 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
896 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
897 // Don't refer the following variables anymore unless tracking the
899 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Clear();
902 // If new line will start with visible white-spaces, it needs to be start
904 else if (pointPositionWithVisibleWhiteSpaces
==
905 PointPosition::StartOfFragment
||
906 pointPositionWithVisibleWhiteSpaces
==
907 PointPosition::MiddleOfFragment
) {
908 auto atNextCharOfInsertionPoint
=
909 textFragmentDataAtInsertionPoint
910 .GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
912 if (atNextCharOfInsertionPoint
.IsSet() &&
913 !atNextCharOfInsertionPoint
.IsEndOfContainer() &&
914 atNextCharOfInsertionPoint
.IsCharCollapsibleASCIISpace()) {
915 const EditorDOMPointInText atPreviousCharOfNextCharOfInsertionPoint
=
916 textFragmentDataAtInsertionPoint
.GetPreviousEditableCharPoint(
917 atNextCharOfInsertionPoint
);
918 if (!atPreviousCharOfNextCharOfInsertionPoint
.IsSet() ||
919 atPreviousCharOfNextCharOfInsertionPoint
.IsEndOfContainer() ||
920 !atPreviousCharOfNextCharOfInsertionPoint
.IsCharASCIISpace()) {
921 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
923 AutoTrackDOMPoint
trackEndOfLineNBSP(aHTMLEditor
.RangeUpdaterRef(),
924 &atNBSPReplaceableWithSP
);
925 AutoTrackDOMRange
trackLeadingWhiteSpaceRange(
926 aHTMLEditor
.RangeUpdaterRef(),
927 &invisibleLeadingWhiteSpaceRangeOfNewLine
);
928 // We are at start of non-nbsps. Convert to a single nbsp.
929 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
930 textFragmentDataAtInsertionPoint
931 .GetEndOfCollapsibleASCIIWhiteSpaces(
932 atNextCharOfInsertionPoint
, nsIEditor::eNone
);
934 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
936 EditorDOMRangeInTexts(atNextCharOfInsertionPoint
,
937 endOfCollapsibleASCIIWhiteSpaces
),
938 nsDependentSubstring(&HTMLEditUtils::kNBSP
, 1));
939 if (MOZ_UNLIKELY(NS_FAILED(rv
))) {
941 "WhiteSpaceVisibilityKeeper::"
942 "ReplaceTextAndRemoveEmptyTextNodes() failed");
945 // Don't refer the following variables anymore unless tracking the
947 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Clear();
952 if (invisibleLeadingWhiteSpaceRangeOfNewLine
.IsPositioned()) {
953 if (!invisibleLeadingWhiteSpaceRangeOfNewLine
.Collapsed()) {
954 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
956 // XXX Why don't we remove all of the invisible white-spaces?
957 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef() ==
959 Result
<CaretPoint
, nsresult
> caretPointOrError
=
960 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
961 invisibleLeadingWhiteSpaceRangeOfNewLine
.StartRef(),
962 invisibleLeadingWhiteSpaceRangeOfNewLine
.EndRef(),
963 HTMLEditor::TreatEmptyTextNodes::
964 KeepIfContainerOfRangeBoundaries
);
965 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
967 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
968 return caretPointOrError
.propagateErr();
970 nsresult rv
= caretPointOrError
.unwrap().SuggestCaretPointTo(
971 aHTMLEditor
, {SuggestCaret::OnlyIfHasSuggestion
,
972 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
,
973 SuggestCaret::AndIgnoreTrivialError
});
975 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
978 NS_WARNING_ASSERTION(
979 rv
!= NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR
,
980 "CaretPoint::SuggestCaretPointTo() failed, but ignored");
981 // Don't refer the following variables anymore unless tracking the
983 atNBSPReplaceableWithSP
.Clear();
984 invisibleLeadingWhiteSpaceRangeOfNewLine
.Clear();
985 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Clear();
988 // If the `<br>` element is put immediately after an NBSP, it should be
989 // replaced with an ASCII white-space.
990 else if (atNBSPReplaceableWithSP
.IsInTextNode()) {
991 const EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
992 atNBSPReplaceableWithSP
.AsInText();
993 if (!atNBSPReplacedWithASCIIWhiteSpace
.IsEndOfContainer() &&
994 atNBSPReplacedWithASCIIWhiteSpace
.IsCharNBSP()) {
995 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
997 Result
<InsertTextResult
, nsresult
> replaceTextResult
=
998 aHTMLEditor
.ReplaceTextWithTransaction(
1000 *atNBSPReplacedWithASCIIWhiteSpace
.ContainerAs
<Text
>()),
1001 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
1002 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
1003 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
1004 return replaceTextResult
.propagateErr();
1006 // Ignore caret suggestion because there was
1007 // AutoTransactionsConserveSelection.
1008 replaceTextResult
.unwrap().IgnoreCaretPointSuggestion();
1009 // Don't refer the following variables anymore unless tracking the
1011 atNBSPReplaceableWithSP
.Clear();
1012 invisibleLeadingWhiteSpaceRangeOfNewLine
.Clear();
1013 invisibleTrailingWhiteSpaceRangeOfCurrentLine
.Clear();
1018 Result
<CreateElementResult
, nsresult
> insertBRElementResult
=
1019 aHTMLEditor
.InsertBRElement(WithTransaction::Yes
, pointToInsert
);
1020 NS_WARNING_ASSERTION(
1021 insertBRElementResult
.isOk(),
1022 "HTMLEditor::InsertBRElement(WithTransaction::Yes, eNone) failed");
1023 return insertBRElementResult
;
1027 Result
<InsertTextResult
, nsresult
> WhiteSpaceVisibilityKeeper::ReplaceText(
1028 HTMLEditor
& aHTMLEditor
, const nsAString
& aStringToInsert
,
1029 const EditorDOMRange
& aRangeToBeReplaced
, const Element
& aEditingHost
) {
1030 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
1031 // meanwhile, the pre case is handled in HandleInsertText() in
1032 // HTMLEditSubActionHandler.cpp
1034 // MOOSE: for now, just getting the ws logic straight. This implementation
1035 // is very slow. Will need to replace edit rules impl with a more efficient
1036 // text sink here that does the minimal amount of searching/replacing/copying
1038 if (aStringToInsert
.IsEmpty()) {
1039 MOZ_ASSERT(aRangeToBeReplaced
.Collapsed());
1040 return InsertTextResult();
1043 TextFragmentData
textFragmentDataAtStart(
1044 aRangeToBeReplaced
.StartRef(), &aEditingHost
,
1045 BlockInlineCheck::UseComputedDisplayStyle
);
1046 if (MOZ_UNLIKELY(NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()))) {
1047 return Err(NS_ERROR_FAILURE
);
1049 const bool isInsertionPointEqualsOrIsBeforeStartOfText
=
1050 aRangeToBeReplaced
.StartRef().EqualsOrIsBefore(
1051 textFragmentDataAtStart
.StartRef());
1052 TextFragmentData textFragmentDataAtEnd
=
1053 aRangeToBeReplaced
.Collapsed()
1054 ? textFragmentDataAtStart
1055 : TextFragmentData(aRangeToBeReplaced
.EndRef(), &aEditingHost
,
1056 BlockInlineCheck::UseComputedDisplayStyle
);
1057 if (MOZ_UNLIKELY(NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized()))) {
1058 return Err(NS_ERROR_FAILURE
);
1060 const bool isInsertionPointEqualsOrAfterEndOfText
=
1061 textFragmentDataAtEnd
.EndRef().EqualsOrIsBefore(
1062 aRangeToBeReplaced
.EndRef());
1064 EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
1065 textFragmentDataAtStart
1066 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
1067 aRangeToBeReplaced
.StartRef());
1068 const bool isInvisibleLeadingWhiteSpaceRangeAtStartPositioned
=
1069 invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned();
1070 EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
1071 textFragmentDataAtEnd
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
1072 aRangeToBeReplaced
.EndRef());
1073 const bool isInvisibleTrailingWhiteSpaceRangeAtEndPositioned
=
1074 invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned();
1075 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtStart
=
1076 !isInvisibleLeadingWhiteSpaceRangeAtStartPositioned
1077 ? Some(textFragmentDataAtStart
.VisibleWhiteSpacesDataRef())
1079 const PointPosition pointPositionWithVisibleWhiteSpacesAtStart
=
1080 visibleWhiteSpacesAtStart
.isSome() &&
1081 visibleWhiteSpacesAtStart
.ref().IsInitialized()
1082 ? visibleWhiteSpacesAtStart
.ref().ComparePoint(
1083 aRangeToBeReplaced
.StartRef())
1084 : PointPosition::NotInSameDOMTree
;
1085 const Maybe
<const VisibleWhiteSpacesData
> visibleWhiteSpacesAtEnd
=
1086 !isInvisibleTrailingWhiteSpaceRangeAtEndPositioned
1087 ? Some(textFragmentDataAtEnd
.VisibleWhiteSpacesDataRef())
1089 const PointPosition pointPositionWithVisibleWhiteSpacesAtEnd
=
1090 visibleWhiteSpacesAtEnd
.isSome() &&
1091 visibleWhiteSpacesAtEnd
.ref().IsInitialized()
1092 ? visibleWhiteSpacesAtEnd
.ref().ComparePoint(
1093 aRangeToBeReplaced
.EndRef())
1094 : PointPosition::NotInSameDOMTree
;
1096 EditorDOMPoint pointToPutCaret
;
1097 EditorDOMPoint
pointToInsert(aRangeToBeReplaced
.StartRef());
1098 EditorDOMPoint atNBSPReplaceableWithSP
;
1099 if (!invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned() &&
1100 (pointPositionWithVisibleWhiteSpacesAtStart
==
1101 PointPosition::MiddleOfFragment
||
1102 pointPositionWithVisibleWhiteSpacesAtStart
==
1103 PointPosition::EndOfFragment
)) {
1104 atNBSPReplaceableWithSP
=
1105 textFragmentDataAtStart
1106 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1108 .To
<EditorDOMPoint
>();
1110 nsAutoString
theString(aStringToInsert
);
1112 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
1113 if (!invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
1114 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
1116 AutoTrackDOMPoint
trackPrecedingNBSP(aHTMLEditor
.RangeUpdaterRef(),
1117 &atNBSPReplaceableWithSP
);
1118 AutoTrackDOMRange
trackInvisibleLeadingWhiteSpaceRange(
1119 aHTMLEditor
.RangeUpdaterRef(),
1120 &invisibleLeadingWhiteSpaceRangeAtStart
);
1121 AutoTrackDOMRange
trackInvisibleTrailingWhiteSpaceRange(
1122 aHTMLEditor
.RangeUpdaterRef(),
1123 &invisibleTrailingWhiteSpaceRangeAtEnd
);
1124 // XXX Why don't we remove all of the invisible white-spaces?
1125 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() ==
1127 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1128 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1129 invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef(),
1130 invisibleTrailingWhiteSpaceRangeAtEnd
.EndRef(),
1131 HTMLEditor::TreatEmptyTextNodes::
1132 KeepIfContainerOfRangeBoundaries
);
1133 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1135 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1136 return caretPointOrError
.propagateErr();
1138 caretPointOrError
.unwrap().MoveCaretPointTo(
1139 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1142 // Replace an NBSP at inclusive next character of replacing range to an
1143 // ASCII white-space if inserting into a visible white-space sequence.
1144 // XXX With modifying the inserting string later, this creates a line break
1145 // opportunity after the inserting string, but this causes
1146 // inconsistent result with inserting order. E.g., type white-space
1147 // n times with various order.
1148 else if (pointPositionWithVisibleWhiteSpacesAtEnd
==
1149 PointPosition::StartOfFragment
||
1150 pointPositionWithVisibleWhiteSpacesAtEnd
==
1151 PointPosition::MiddleOfFragment
) {
1152 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
1153 textFragmentDataAtEnd
1154 .GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
1155 aRangeToBeReplaced
.EndRef());
1156 if (atNBSPReplacedWithASCIIWhiteSpace
.IsSet()) {
1157 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1159 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
1161 AutoTrackDOMPoint
trackPrecedingNBSP(aHTMLEditor
.RangeUpdaterRef(),
1162 &atNBSPReplaceableWithSP
);
1163 AutoTrackDOMRange
trackInvisibleLeadingWhiteSpaceRange(
1164 aHTMLEditor
.RangeUpdaterRef(),
1165 &invisibleLeadingWhiteSpaceRangeAtStart
);
1166 AutoTrackDOMRange
trackInvisibleTrailingWhiteSpaceRange(
1167 aHTMLEditor
.RangeUpdaterRef(),
1168 &invisibleTrailingWhiteSpaceRangeAtEnd
);
1169 Result
<InsertTextResult
, nsresult
> replaceTextResult
=
1170 aHTMLEditor
.ReplaceTextWithTransaction(
1172 *atNBSPReplacedWithASCIIWhiteSpace
.ContainerAs
<Text
>()),
1173 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
1174 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
1175 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
1176 return replaceTextResult
.propagateErr();
1178 // Ignore caret suggestion because there was
1179 // AutoTransactionsConserveSelection.
1180 replaceTextResult
.unwrap().IgnoreCaretPointSuggestion();
1184 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
1185 if (!invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
1186 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1188 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
1190 AutoTrackDOMRange
trackInvisibleTrailingWhiteSpaceRange(
1191 aHTMLEditor
.RangeUpdaterRef(),
1192 &invisibleTrailingWhiteSpaceRangeAtEnd
);
1193 // XXX Why don't we remove all of the invisible white-spaces?
1194 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeAtStart
.EndRef() ==
1196 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1197 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1198 invisibleLeadingWhiteSpaceRangeAtStart
.StartRef(),
1199 invisibleLeadingWhiteSpaceRangeAtStart
.EndRef(),
1200 HTMLEditor::TreatEmptyTextNodes::
1201 KeepIfContainerOfRangeBoundaries
);
1202 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1204 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1205 return caretPointOrError
.propagateErr();
1207 trackPointToPutCaret
.FlushAndStopTracking();
1208 caretPointOrError
.unwrap().MoveCaretPointTo(
1209 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1210 // Don't refer the following variables anymore unless tracking the
1212 atNBSPReplaceableWithSP
.Clear();
1213 invisibleLeadingWhiteSpaceRangeAtStart
.Clear();
1216 // Replace an NBSP at previous character of insertion point to an ASCII
1217 // white-space if inserting into a visible white-space sequence.
1218 // XXX With modifying the inserting string later, this creates a line break
1219 // opportunity before the inserting string, but this causes
1220 // inconsistent result with inserting order. E.g., type white-space
1221 // n times with various order.
1222 else if (atNBSPReplaceableWithSP
.IsInTextNode()) {
1223 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace
=
1224 atNBSPReplaceableWithSP
.AsInText();
1225 if (!atNBSPReplacedWithASCIIWhiteSpace
.IsEndOfContainer() &&
1226 atNBSPReplacedWithASCIIWhiteSpace
.IsCharNBSP()) {
1227 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1229 AutoTrackDOMPoint
trackPointToInsert(aHTMLEditor
.RangeUpdaterRef(),
1231 AutoTrackDOMRange
trackInvisibleTrailingWhiteSpaceRange(
1232 aHTMLEditor
.RangeUpdaterRef(),
1233 &invisibleTrailingWhiteSpaceRangeAtEnd
);
1234 Result
<InsertTextResult
, nsresult
> replaceTextResult
=
1235 aHTMLEditor
.ReplaceTextWithTransaction(
1237 *atNBSPReplacedWithASCIIWhiteSpace
.ContainerAs
<Text
>()),
1238 atNBSPReplacedWithASCIIWhiteSpace
.Offset(), 1, u
" "_ns
);
1239 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
1240 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
1241 return replaceTextResult
.propagateErr();
1243 // Ignore caret suggestion because there was
1244 // AutoTransactionsConserveSelection.
1245 replaceTextResult
.unwrap().IgnoreCaretPointSuggestion();
1246 // Don't refer the following variables anymore unless tracking the
1248 atNBSPReplaceableWithSP
.Clear();
1249 invisibleLeadingWhiteSpaceRangeAtStart
.Clear();
1254 // If white-space and/or linefeed characters are collapsible, and inserting
1255 // string starts and/or ends with a collapsible characters, we need to
1256 // replace them with NBSP for making sure the collapsible characters visible.
1257 // FYI: There is no case only linefeeds are collapsible. So, we need to
1258 // do the things only when white-spaces are collapsible.
1259 MOZ_DIAGNOSTIC_ASSERT(!theString
.IsEmpty());
1260 if (NS_WARN_IF(!pointToInsert
.IsInContentNode()) ||
1261 !EditorUtils::IsWhiteSpacePreformatted(
1262 *pointToInsert
.ContainerAs
<nsIContent
>())) {
1263 const bool isNewLineCollapsible
=
1264 !pointToInsert
.IsInContentNode() ||
1265 !EditorUtils::IsNewLinePreformatted(
1266 *pointToInsert
.ContainerAs
<nsIContent
>());
1267 auto IsCollapsibleChar
= [&isNewLineCollapsible
](char16_t aChar
) -> bool {
1268 return nsCRT::IsAsciiSpace(aChar
) &&
1269 (isNewLineCollapsible
|| aChar
!= HTMLEditUtils::kNewLine
);
1271 if (IsCollapsibleChar(theString
[0])) {
1272 // If inserting string will follow some invisible leading white-spaces,
1273 // the string needs to start with an NBSP.
1274 if (isInvisibleLeadingWhiteSpaceRangeAtStartPositioned
) {
1275 theString
.SetCharAt(HTMLEditUtils::kNBSP
, 0);
1277 // If inserting around visible white-spaces, check whether the previous
1278 // character of insertion point is an NBSP or an ASCII white-space.
1279 else if (pointPositionWithVisibleWhiteSpacesAtStart
==
1280 PointPosition::MiddleOfFragment
||
1281 pointPositionWithVisibleWhiteSpacesAtStart
==
1282 PointPosition::EndOfFragment
) {
1283 const auto atPreviousChar
=
1284 textFragmentDataAtStart
1285 .GetPreviousEditableCharPoint
<EditorRawDOMPointInText
>(
1287 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsEndOfContainer() &&
1288 atPreviousChar
.IsCharASCIISpace()) {
1289 theString
.SetCharAt(HTMLEditUtils::kNBSP
, 0);
1292 // If the insertion point is (was) before the start of text and it's
1293 // immediately after a hard line break, the first ASCII white-space should
1294 // be replaced with an NBSP for making it visible.
1295 else if (textFragmentDataAtStart
.StartsFromHardLineBreak() &&
1296 isInsertionPointEqualsOrIsBeforeStartOfText
) {
1297 theString
.SetCharAt(HTMLEditUtils::kNBSP
, 0);
1301 // Then the tail. Note that it may be the first character.
1302 const uint32_t lastCharIndex
= theString
.Length() - 1;
1303 if (IsCollapsibleChar(theString
[lastCharIndex
])) {
1304 // If inserting string will be followed by some invisible trailing
1305 // white-spaces, the string needs to end with an NBSP.
1306 if (isInvisibleTrailingWhiteSpaceRangeAtEndPositioned
) {
1307 theString
.SetCharAt(HTMLEditUtils::kNBSP
, lastCharIndex
);
1309 // If inserting around visible white-spaces, check whether the inclusive
1310 // next character of end of replaced range is an NBSP or an ASCII
1312 if (pointPositionWithVisibleWhiteSpacesAtEnd
==
1313 PointPosition::StartOfFragment
||
1314 pointPositionWithVisibleWhiteSpacesAtEnd
==
1315 PointPosition::MiddleOfFragment
) {
1316 const auto atNextChar
=
1317 textFragmentDataAtEnd
1318 .GetInclusiveNextEditableCharPoint
<EditorRawDOMPointInText
>(
1320 if (atNextChar
.IsSet() && !atNextChar
.IsEndOfContainer() &&
1321 atNextChar
.IsCharASCIISpace()) {
1322 theString
.SetCharAt(HTMLEditUtils::kNBSP
, lastCharIndex
);
1325 // If the end of replacing range is (was) after the end of text and it's
1326 // immediately before block boundary, the last ASCII white-space should
1327 // be replaced with an NBSP for making it visible.
1328 else if (textFragmentDataAtEnd
.EndsByBlockBoundary() &&
1329 isInsertionPointEqualsOrAfterEndOfText
) {
1330 theString
.SetCharAt(HTMLEditUtils::kNBSP
, lastCharIndex
);
1334 // Next, scan string for adjacent ws and convert to nbsp/space combos
1335 // MOOSE: don't need to convert tabs here since that is done by
1336 // WillInsertText() before we are called. Eventually, all that logic will
1337 // be pushed down into here and made more efficient.
1338 enum class PreviousChar
{
1341 PreformattedNewLine
,
1343 PreviousChar previousChar
= PreviousChar::NonCollapsibleChar
;
1344 for (uint32_t i
= 0; i
<= lastCharIndex
; i
++) {
1345 if (IsCollapsibleChar(theString
[i
])) {
1346 // If current char is collapsible and 2nd or latter character of
1347 // collapsible characters, we need to make the previous character an
1348 // NBSP for avoiding current character to be collapsed to it.
1349 if (previousChar
== PreviousChar::CollapsibleChar
) {
1351 theString
.SetCharAt(HTMLEditUtils::kNBSP
, i
- 1);
1352 // Keep previousChar as PreviousChar::CollapsibleChar.
1356 // If current character is a collapsbile white-space and the previous
1357 // character is a preformatted linefeed, we need to replace the current
1358 // character with an NBSP for avoiding collapsed with the previous
1360 if (previousChar
== PreviousChar::PreformattedNewLine
) {
1362 theString
.SetCharAt(HTMLEditUtils::kNBSP
, i
);
1363 previousChar
= PreviousChar::NonCollapsibleChar
;
1367 previousChar
= PreviousChar::CollapsibleChar
;
1371 if (theString
[i
] != HTMLEditUtils::kNewLine
) {
1372 previousChar
= PreviousChar::NonCollapsibleChar
;
1376 // If current character is a preformatted linefeed and the previous
1377 // character is collapbile white-space, the previous character will be
1378 // collapsed into current linefeed. Therefore, we need to replace the
1379 // previous character with an NBSP.
1380 MOZ_ASSERT(!isNewLineCollapsible
);
1381 if (previousChar
== PreviousChar::CollapsibleChar
) {
1383 theString
.SetCharAt(HTMLEditUtils::kNBSP
, i
- 1);
1385 previousChar
= PreviousChar::PreformattedNewLine
;
1389 // XXX If the point is not editable, InsertTextWithTransaction() returns
1390 // error, but we keep handling it. But I think that it wastes the
1391 // runtime cost. So, perhaps, we should return error code which couldn't
1392 // modify it and make each caller of this method decide whether it should
1393 // keep or stop handling the edit action.
1394 if (MOZ_UNLIKELY(!aHTMLEditor
.GetDocument())) {
1396 "WhiteSpaceVisibilityKeeper::ReplaceText() lost proper document");
1397 return Err(NS_ERROR_UNEXPECTED
);
1399 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1401 OwningNonNull
<Document
> document
= *aHTMLEditor
.GetDocument();
1402 Result
<InsertTextResult
, nsresult
> insertTextResult
=
1403 aHTMLEditor
.InsertTextWithTransaction(document
, theString
, pointToInsert
);
1404 if (MOZ_UNLIKELY(insertTextResult
.isErr())) {
1405 NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
1406 return insertTextResult
.propagateErr();
1408 trackPointToPutCaret
.FlushAndStopTracking();
1409 if (insertTextResult
.inspect().HasCaretPointSuggestion()) {
1410 return insertTextResult
;
1412 return InsertTextResult(insertTextResult
.unwrap(),
1413 std::move(pointToPutCaret
));
1417 Result
<CaretPoint
, nsresult
>
1418 WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
1419 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
,
1420 const Element
& aEditingHost
) {
1421 TextFragmentData
textFragmentDataAtDeletion(
1422 aPoint
, &aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
1423 if (NS_WARN_IF(!textFragmentDataAtDeletion
.IsInitialized())) {
1424 return Err(NS_ERROR_FAILURE
);
1426 const EditorDOMPointInText atPreviousCharOfStart
=
1427 textFragmentDataAtDeletion
.GetPreviousEditableCharPoint(aPoint
);
1428 if (!atPreviousCharOfStart
.IsSet() ||
1429 atPreviousCharOfStart
.IsEndOfContainer()) {
1430 return CaretPoint(EditorDOMPoint());
1433 // If the char is a collapsible white-space or a non-collapsible new line
1434 // but it can collapse adjacent white-spaces, we need to extend the range
1435 // to delete all invisible white-spaces.
1436 if (atPreviousCharOfStart
.IsCharCollapsibleASCIISpace() ||
1437 atPreviousCharOfStart
1438 .IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
1439 auto startToDelete
=
1440 textFragmentDataAtDeletion
1441 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPoint
>(
1442 atPreviousCharOfStart
, nsIEditor::ePrevious
);
1443 auto endToDelete
= textFragmentDataAtDeletion
1444 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPoint
>(
1445 atPreviousCharOfStart
, nsIEditor::ePrevious
);
1446 EditorDOMPoint pointToPutCaret
;
1448 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1449 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1450 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
1451 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1453 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1455 return caretPointOrError
;
1457 caretPointOrError
.unwrap().MoveCaretPointTo(
1458 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1462 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1464 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1465 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1466 startToDelete
, endToDelete
,
1467 HTMLEditor::TreatEmptyTextNodes::
1468 KeepIfContainerOfRangeBoundaries
);
1469 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1471 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1472 return caretPointOrError
;
1474 trackPointToPutCaret
.FlushAndStopTracking();
1475 caretPointOrError
.unwrap().MoveCaretPointTo(
1476 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1478 return CaretPoint(std::move(pointToPutCaret
));
1481 if (atPreviousCharOfStart
.IsCharCollapsibleNBSP()) {
1482 auto startToDelete
= atPreviousCharOfStart
.To
<EditorDOMPoint
>();
1483 auto endToDelete
= startToDelete
.NextPoint
<EditorDOMPoint
>();
1484 EditorDOMPoint pointToPutCaret
;
1486 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1487 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1488 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
1489 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1491 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1493 return caretPointOrError
;
1495 caretPointOrError
.unwrap().MoveCaretPointTo(
1496 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1500 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1502 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1503 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1504 startToDelete
, endToDelete
,
1505 HTMLEditor::TreatEmptyTextNodes::
1506 KeepIfContainerOfRangeBoundaries
);
1507 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1509 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1510 return caretPointOrError
;
1512 trackPointToPutCaret
.FlushAndStopTracking();
1513 caretPointOrError
.unwrap().MoveCaretPointTo(
1514 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1516 return CaretPoint(std::move(pointToPutCaret
));
1519 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1520 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1521 atPreviousCharOfStart
, atPreviousCharOfStart
.NextPoint(),
1522 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1523 NS_WARNING_ASSERTION(
1524 caretPointOrError
.isOk(),
1525 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1526 return caretPointOrError
;
1530 Result
<CaretPoint
, nsresult
>
1531 WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
1532 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
,
1533 const Element
& aEditingHost
) {
1534 TextFragmentData
textFragmentDataAtDeletion(
1535 aPoint
, &aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
1536 if (NS_WARN_IF(!textFragmentDataAtDeletion
.IsInitialized())) {
1537 return Err(NS_ERROR_FAILURE
);
1539 auto atNextCharOfStart
=
1540 textFragmentDataAtDeletion
1541 .GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(aPoint
);
1542 if (!atNextCharOfStart
.IsSet() || atNextCharOfStart
.IsEndOfContainer()) {
1543 return CaretPoint(EditorDOMPoint());
1546 // If the char is a collapsible white-space or a non-collapsible new line
1547 // but it can collapse adjacent white-spaces, we need to extend the range
1548 // to delete all invisible white-spaces.
1549 if (atNextCharOfStart
.IsCharCollapsibleASCIISpace() ||
1550 atNextCharOfStart
.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
1551 auto startToDelete
=
1552 textFragmentDataAtDeletion
1553 .GetFirstASCIIWhiteSpacePointCollapsedTo
<EditorDOMPoint
>(
1554 atNextCharOfStart
, nsIEditor::eNext
);
1555 auto endToDelete
= textFragmentDataAtDeletion
1556 .GetEndOfCollapsibleASCIIWhiteSpaces
<EditorDOMPoint
>(
1557 atNextCharOfStart
, nsIEditor::eNext
);
1558 EditorDOMPoint pointToPutCaret
;
1560 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1561 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1562 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
1563 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1565 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1567 return caretPointOrError
;
1569 caretPointOrError
.unwrap().MoveCaretPointTo(
1570 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1574 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1576 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1577 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1578 startToDelete
, endToDelete
,
1579 HTMLEditor::TreatEmptyTextNodes::
1580 KeepIfContainerOfRangeBoundaries
);
1581 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1583 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1584 return caretPointOrError
;
1586 trackPointToPutCaret
.FlushAndStopTracking();
1587 caretPointOrError
.unwrap().MoveCaretPointTo(
1588 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1590 return CaretPoint(std::move(pointToPutCaret
));
1593 if (atNextCharOfStart
.IsCharCollapsibleNBSP()) {
1594 auto startToDelete
= atNextCharOfStart
.To
<EditorDOMPoint
>();
1595 auto endToDelete
= startToDelete
.NextPoint
<EditorDOMPoint
>();
1596 EditorDOMPoint pointToPutCaret
;
1598 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1599 WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints(
1600 aHTMLEditor
, &startToDelete
, &endToDelete
, aEditingHost
);
1601 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1603 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1605 return caretPointOrError
;
1607 caretPointOrError
.unwrap().MoveCaretPointTo(
1608 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1612 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1614 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1615 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1616 startToDelete
, endToDelete
,
1617 HTMLEditor::TreatEmptyTextNodes::
1618 KeepIfContainerOfRangeBoundaries
);
1619 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1621 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1622 return caretPointOrError
;
1624 trackPointToPutCaret
.FlushAndStopTracking();
1625 caretPointOrError
.unwrap().MoveCaretPointTo(
1626 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1628 return CaretPoint(std::move(pointToPutCaret
));
1631 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1632 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
1633 atNextCharOfStart
, atNextCharOfStart
.NextPoint(),
1634 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
1635 NS_WARNING_ASSERTION(
1636 caretPointOrError
.isOk(),
1637 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1638 return caretPointOrError
;
1642 Result
<CaretPoint
, nsresult
>
1643 WhiteSpaceVisibilityKeeper::DeleteContentNodeAndJoinTextNodesAroundIt(
1644 HTMLEditor
& aHTMLEditor
, nsIContent
& aContentToDelete
,
1645 const EditorDOMPoint
& aCaretPoint
, const Element
& aEditingHost
) {
1646 EditorDOMPoint
atContent(&aContentToDelete
);
1647 if (!atContent
.IsSet()) {
1648 NS_WARNING("Deleting content node was an orphan node");
1649 return Err(NS_ERROR_FAILURE
);
1651 if (!HTMLEditUtils::IsRemovableNode(aContentToDelete
)) {
1652 NS_WARNING("Deleting content node wasn't removable");
1653 return Err(NS_ERROR_FAILURE
);
1655 EditorDOMPoint
pointToPutCaret(aCaretPoint
);
1657 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1659 Result
<CaretPoint
, nsresult
> caretPointOrError
=
1660 WhiteSpaceVisibilityKeeper::
1661 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1662 aHTMLEditor
, EditorDOMRange(atContent
, atContent
.NextPoint()),
1664 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
1666 "WhiteSpaceVisibilityKeeper::"
1667 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() "
1669 return caretPointOrError
;
1671 trackPointToPutCaret
.FlushAndStopTracking();
1672 caretPointOrError
.unwrap().MoveCaretPointTo(
1673 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1676 nsCOMPtr
<nsIContent
> previousEditableSibling
=
1677 HTMLEditUtils::GetPreviousSibling(
1678 aContentToDelete
, {WalkTreeOption::IgnoreNonEditableNode
});
1679 // Delete the node, and join like nodes if appropriate
1681 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
1683 nsresult rv
= aHTMLEditor
.DeleteNodeWithTransaction(aContentToDelete
);
1684 if (NS_FAILED(rv
)) {
1685 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1690 // Are they both text nodes? If so, join them!
1691 // XXX This may cause odd behavior if there is non-editable nodes
1692 // around the atomic content.
1693 if (!aCaretPoint
.IsInTextNode() || !previousEditableSibling
||
1694 !previousEditableSibling
->IsText()) {
1695 return CaretPoint(std::move(pointToPutCaret
));
1698 nsIContent
* nextEditableSibling
= HTMLEditUtils::GetNextSibling(
1699 *previousEditableSibling
, {WalkTreeOption::IgnoreNonEditableNode
});
1700 if (aCaretPoint
.GetContainer() != nextEditableSibling
) {
1701 return CaretPoint(std::move(pointToPutCaret
));
1704 nsresult rv
= aHTMLEditor
.JoinNearestEditableNodesWithTransaction(
1705 *previousEditableSibling
, MOZ_KnownLive(*aCaretPoint
.ContainerAs
<Text
>()),
1707 if (NS_FAILED(rv
)) {
1708 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1711 if (!pointToPutCaret
.IsSet()) {
1713 "HTMLEditor::JoinNearestEditableNodesWithTransaction() didn't return "
1714 "right node position");
1715 return Err(NS_ERROR_FAILURE
);
1717 return CaretPoint(std::move(pointToPutCaret
));
1720 template <typename PT
, typename CT
>
1721 WSScanResult
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1722 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1723 MOZ_ASSERT(aPoint
.IsSet());
1725 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1726 return WSScanResult(nullptr, WSType::UnexpectedError
, mBlockInlineCheck
);
1729 // If the range has visible text and start of the visible text is before
1730 // aPoint, return previous character in the text.
1731 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1732 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1733 if (visibleWhiteSpaces
.IsInitialized() &&
1734 visibleWhiteSpaces
.StartRef().IsBefore(aPoint
)) {
1735 // If the visible things are not editable, we shouldn't scan "editable"
1736 // things now. Whether keep scanning editable things or not should be
1737 // considered by the caller.
1738 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1739 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
,
1742 const auto atPreviousChar
=
1743 GetPreviousEditableCharPoint
<EditorRawDOMPointInText
>(aPoint
);
1744 // When it's a non-empty text node, return it.
1745 if (atPreviousChar
.IsSet() && !atPreviousChar
.IsContainerEmpty()) {
1746 MOZ_ASSERT(!atPreviousChar
.IsEndOfContainer());
1747 return WSScanResult(atPreviousChar
.template NextPoint
<EditorDOMPoint
>(),
1748 atPreviousChar
.IsCharCollapsibleASCIISpaceOrNBSP()
1749 ? WSType::CollapsibleWhiteSpaces
1750 : WSType::NonCollapsibleCharacters
,
1755 // Otherwise, return the start of the range.
1756 if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
1757 TextFragmentDataAtStartRef().StartRef().GetContainer()) {
1758 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
1760 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1761 TextFragmentDataAtStartRef().StartRawReason(),
1764 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1765 TextFragmentDataAtStartRef().StartRawReason(),
1769 template <typename PT
, typename CT
>
1770 WSScanResult
WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
1771 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
1772 MOZ_ASSERT(aPoint
.IsSet());
1774 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1775 return WSScanResult(nullptr, WSType::UnexpectedError
, mBlockInlineCheck
);
1778 // If the range has visible text and aPoint equals or is before the end of the
1779 // visible text, return inclusive next character in the text.
1780 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
1781 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1782 if (visibleWhiteSpaces
.IsInitialized() &&
1783 aPoint
.EqualsOrIsBefore(visibleWhiteSpaces
.EndRef())) {
1784 // If the visible things are not editable, we shouldn't scan "editable"
1785 // things now. Whether keep scanning editable things or not should be
1786 // considered by the caller.
1787 if (aPoint
.GetChild() && !aPoint
.GetChild()->IsEditable()) {
1788 return WSScanResult(aPoint
.GetChild(), WSType::SpecialContent
,
1791 const auto atNextChar
=
1792 GetInclusiveNextEditableCharPoint
<EditorDOMPoint
>(aPoint
);
1793 // When it's a non-empty text node, return it.
1794 if (atNextChar
.IsSet() && !atNextChar
.IsContainerEmpty()) {
1795 return WSScanResult(atNextChar
,
1796 !atNextChar
.IsEndOfContainer() &&
1797 atNextChar
.IsCharCollapsibleASCIISpaceOrNBSP()
1798 ? WSType::CollapsibleWhiteSpaces
1799 : WSType::NonCollapsibleCharacters
,
1804 // Otherwise, return the end of the range.
1805 if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
1806 TextFragmentDataAtStartRef().EndRef().GetContainer()) {
1807 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
1809 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1810 TextFragmentDataAtStartRef().EndRawReason(),
1813 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1814 TextFragmentDataAtStartRef().EndRawReason(),
1818 template <typename EditorDOMPointType
>
1819 WSRunScanner::TextFragmentData::TextFragmentData(
1820 const EditorDOMPointType
& aPoint
, const Element
* aEditingHost
,
1821 BlockInlineCheck aBlockInlineCheck
)
1822 : mEditingHost(aEditingHost
), mBlockInlineCheck(aBlockInlineCheck
) {
1823 if (!aPoint
.IsSetAndValid()) {
1824 NS_WARNING("aPoint was invalid");
1827 if (!aPoint
.IsInContentNode()) {
1828 NS_WARNING("aPoint was in Document or DocumentFragment");
1829 // I.e., we're try to modify outside of root element. We don't need to
1830 // support such odd case because web apps cannot append text nodes as
1831 // direct child of Document node.
1835 mScanStartPoint
= aPoint
.template To
<EditorDOMPoint
>();
1837 EditorUtils::IsEditableContent(*mScanStartPoint
.ContainerAs
<nsIContent
>(),
1839 "Given content is not editable");
1841 mScanStartPoint
.ContainerAs
<nsIContent
>()->GetAsElementOrParentElement(),
1842 "Given content is not an element and an orphan node");
1843 if (NS_WARN_IF(!EditorUtils::IsEditableContent(
1844 *mScanStartPoint
.ContainerAs
<nsIContent
>(), EditorType::HTML
))) {
1847 const Element
* editableBlockElementOrInlineEditingHost
=
1848 HTMLEditUtils::GetInclusiveAncestorElement(
1849 *mScanStartPoint
.ContainerAs
<nsIContent
>(),
1850 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost
,
1852 if (!editableBlockElementOrInlineEditingHost
) {
1854 "HTMLEditUtils::GetInclusiveAncestorElement(HTMLEditUtils::"
1855 "ClosestEditableBlockElementOrInlineEditingHost) couldn't find "
1860 mStart
= BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1861 mScanStartPoint
, *editableBlockElementOrInlineEditingHost
, mEditingHost
,
1862 &mNBSPData
, aBlockInlineCheck
);
1863 MOZ_ASSERT_IF(mStart
.IsNonCollapsibleCharacters(),
1864 !mStart
.PointRef().IsPreviousCharPreformattedNewLine());
1865 MOZ_ASSERT_IF(mStart
.IsPreformattedLineBreak(),
1866 mStart
.PointRef().IsPreviousCharPreformattedNewLine());
1867 mEnd
= BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1868 mScanStartPoint
, *editableBlockElementOrInlineEditingHost
, mEditingHost
,
1869 &mNBSPData
, aBlockInlineCheck
);
1870 MOZ_ASSERT_IF(mEnd
.IsNonCollapsibleCharacters(),
1871 !mEnd
.PointRef().IsCharPreformattedNewLine());
1872 MOZ_ASSERT_IF(mEnd
.IsPreformattedLineBreak(),
1873 mEnd
.PointRef().IsCharPreformattedNewLine());
1877 template <typename EditorDOMPointType
>
1878 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
1879 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1880 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
1881 BlockInlineCheck aBlockInlineCheck
) {
1882 MOZ_ASSERT(aPoint
.IsSetAndValid());
1883 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
1885 const bool isWhiteSpaceCollapsible
= !EditorUtils::IsWhiteSpacePreformatted(
1886 *aPoint
.template ContainerAs
<Text
>());
1887 const bool isNewLineCollapsible
=
1888 !EditorUtils::IsNewLinePreformatted(*aPoint
.template ContainerAs
<Text
>());
1889 const nsTextFragment
& textFragment
=
1890 aPoint
.template ContainerAs
<Text
>()->TextFragment();
1891 for (uint32_t i
= std::min(aPoint
.Offset(), textFragment
.GetLength()); i
;
1893 WSType wsTypeOfNonCollapsibleChar
;
1894 switch (textFragment
.CharAt(i
- 1)) {
1895 case HTMLEditUtils::kSpace
:
1896 case HTMLEditUtils::kCarriageReturn
:
1897 case HTMLEditUtils::kTab
:
1898 if (isWhiteSpaceCollapsible
) {
1899 continue; // collapsible white-space or invisible white-space.
1901 // preformatted white-space.
1902 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
1904 case HTMLEditUtils::kNewLine
:
1905 if (isNewLineCollapsible
) {
1906 continue; // collapsible linefeed.
1908 // preformatted linefeed.
1909 wsTypeOfNonCollapsibleChar
= WSType::PreformattedLineBreak
;
1911 case HTMLEditUtils::kNBSP
:
1912 if (isWhiteSpaceCollapsible
) {
1914 aNBSPData
->NotifyNBSP(
1915 EditorDOMPointInText(aPoint
.template ContainerAs
<Text
>(),
1917 NoBreakingSpaceData::Scanning::Backward
);
1921 // NBSP is never converted from collapsible white-space/linefeed.
1922 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
1925 MOZ_ASSERT(!nsCRT::IsAsciiSpace(textFragment
.CharAt(i
- 1)));
1926 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
1930 return Some(BoundaryData(
1931 EditorDOMPoint(aPoint
.template ContainerAs
<Text
>(), i
),
1932 *aPoint
.template ContainerAs
<Text
>(), wsTypeOfNonCollapsibleChar
));
1939 template <typename EditorDOMPointType
>
1940 WSRunScanner::TextFragmentData::BoundaryData
WSRunScanner::TextFragmentData::
1941 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1942 const EditorDOMPointType
& aPoint
,
1943 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
1944 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
1945 BlockInlineCheck aBlockInlineCheck
) {
1946 MOZ_ASSERT(aPoint
.IsSetAndValid());
1948 if (aPoint
.IsInTextNode() && !aPoint
.IsStartOfContainer()) {
1949 Maybe
<BoundaryData
> startInTextNode
=
1950 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1951 aPoint
, aNBSPData
, aBlockInlineCheck
);
1952 if (startInTextNode
.isSome()) {
1953 return startInTextNode
.ref();
1955 // The text node does not have visible character, let's keep scanning
1957 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1958 EditorDOMPoint(aPoint
.template ContainerAs
<Text
>(), 0),
1959 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
1960 aNBSPData
, aBlockInlineCheck
);
1963 // Then, we need to check previous leaf node.
1964 nsIContent
* previousLeafContentOrBlock
=
1965 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
1966 aPoint
, aEditableBlockParentOrTopmostEditableInlineElement
,
1967 {LeafNodeType::LeafNodeOrNonEditableNode
}, aBlockInlineCheck
,
1969 if (!previousLeafContentOrBlock
) {
1970 // no prior node means we exhausted
1971 // aEditableBlockParentOrTopmostEditableInlineElement
1972 // mReasonContent can be either a block element or any non-editable
1973 // content in this case.
1974 return BoundaryData(aPoint
,
1975 const_cast<Element
&>(
1976 aEditableBlockParentOrTopmostEditableInlineElement
),
1977 WSType::CurrentBlockBoundary
);
1980 if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock
,
1981 aBlockInlineCheck
)) {
1982 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1983 WSType::OtherBlockBoundary
);
1986 if (!previousLeafContentOrBlock
->IsText() ||
1987 !previousLeafContentOrBlock
->IsEditable()) {
1988 // it's a break or a special node, like <img>, that is not a block and
1989 // not a break but still serves as a terminator to ws runs.
1990 return BoundaryData(aPoint
, *previousLeafContentOrBlock
,
1991 previousLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
1993 : WSType::SpecialContent
);
1996 if (!previousLeafContentOrBlock
->AsText()->TextLength()) {
1997 // If it's an empty text node, keep looking for its previous leaf content.
1998 // Note that even if the empty text node is preformatted, we should keep
1999 // looking for the previous one.
2000 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
2001 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
2002 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
2003 aNBSPData
, aBlockInlineCheck
);
2006 Maybe
<BoundaryData
> startInTextNode
=
2007 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
2008 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock
->AsText()),
2009 aNBSPData
, aBlockInlineCheck
);
2010 if (startInTextNode
.isSome()) {
2011 return startInTextNode
.ref();
2014 // The text node does not have visible character, let's keep scanning
2016 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
2017 EditorDOMPointInText(previousLeafContentOrBlock
->AsText(), 0),
2018 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
2019 aNBSPData
, aBlockInlineCheck
);
2023 template <typename EditorDOMPointType
>
2024 Maybe
<WSRunScanner::TextFragmentData::BoundaryData
> WSRunScanner::
2025 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
2026 const EditorDOMPointType
& aPoint
, NoBreakingSpaceData
* aNBSPData
,
2027 BlockInlineCheck aBlockInlineCheck
) {
2028 MOZ_ASSERT(aPoint
.IsSetAndValid());
2029 MOZ_DIAGNOSTIC_ASSERT(aPoint
.IsInTextNode());
2031 const bool isWhiteSpaceCollapsible
= !EditorUtils::IsWhiteSpacePreformatted(
2032 *aPoint
.template ContainerAs
<Text
>());
2033 const bool isNewLineCollapsible
=
2034 !EditorUtils::IsNewLinePreformatted(*aPoint
.template ContainerAs
<Text
>());
2035 const nsTextFragment
& textFragment
=
2036 aPoint
.template ContainerAs
<Text
>()->TextFragment();
2037 for (uint32_t i
= aPoint
.Offset(); i
< textFragment
.GetLength(); i
++) {
2038 WSType wsTypeOfNonCollapsibleChar
;
2039 switch (textFragment
.CharAt(i
)) {
2040 case HTMLEditUtils::kSpace
:
2041 case HTMLEditUtils::kCarriageReturn
:
2042 case HTMLEditUtils::kTab
:
2043 if (isWhiteSpaceCollapsible
) {
2044 continue; // collapsible white-space or invisible white-space.
2046 // preformatted white-space.
2047 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
2049 case HTMLEditUtils::kNewLine
:
2050 if (isNewLineCollapsible
) {
2051 continue; // collapsible linefeed.
2053 // preformatted linefeed.
2054 wsTypeOfNonCollapsibleChar
= WSType::PreformattedLineBreak
;
2056 case HTMLEditUtils::kNBSP
:
2057 if (isWhiteSpaceCollapsible
) {
2059 aNBSPData
->NotifyNBSP(
2060 EditorDOMPointInText(aPoint
.template ContainerAs
<Text
>(), i
),
2061 NoBreakingSpaceData::Scanning::Forward
);
2065 // NBSP is never converted from collapsible white-space/linefeed.
2066 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
2069 MOZ_ASSERT(!nsCRT::IsAsciiSpace(textFragment
.CharAt(i
)));
2070 wsTypeOfNonCollapsibleChar
= WSType::NonCollapsibleCharacters
;
2074 return Some(BoundaryData(
2075 EditorDOMPoint(aPoint
.template ContainerAs
<Text
>(), i
),
2076 *aPoint
.template ContainerAs
<Text
>(), wsTypeOfNonCollapsibleChar
));
2083 template <typename EditorDOMPointType
>
2084 WSRunScanner::TextFragmentData::BoundaryData
2085 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
2086 const EditorDOMPointType
& aPoint
,
2087 const Element
& aEditableBlockParentOrTopmostEditableInlineElement
,
2088 const Element
* aEditingHost
, NoBreakingSpaceData
* aNBSPData
,
2089 BlockInlineCheck aBlockInlineCheck
) {
2090 MOZ_ASSERT(aPoint
.IsSetAndValid());
2092 if (aPoint
.IsInTextNode() && !aPoint
.IsEndOfContainer()) {
2093 Maybe
<BoundaryData
> endInTextNode
=
2094 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint
, aNBSPData
,
2096 if (endInTextNode
.isSome()) {
2097 return endInTextNode
.ref();
2099 // The text node does not have visible character, let's keep scanning
2101 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
2102 EditorDOMPointInText::AtEndOf(*aPoint
.template ContainerAs
<Text
>()),
2103 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
2104 aNBSPData
, aBlockInlineCheck
);
2107 // Then, we need to check next leaf node.
2108 nsIContent
* nextLeafContentOrBlock
=
2109 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2110 aPoint
, aEditableBlockParentOrTopmostEditableInlineElement
,
2111 {LeafNodeType::LeafNodeOrNonEditableNode
}, aBlockInlineCheck
,
2113 if (!nextLeafContentOrBlock
) {
2114 // no next node means we exhausted
2115 // aEditableBlockParentOrTopmostEditableInlineElement
2116 // mReasonContent can be either a block element or any non-editable
2117 // content in this case.
2118 return BoundaryData(aPoint
.template To
<EditorDOMPoint
>(),
2119 const_cast<Element
&>(
2120 aEditableBlockParentOrTopmostEditableInlineElement
),
2121 WSType::CurrentBlockBoundary
);
2124 if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock
,
2125 aBlockInlineCheck
)) {
2126 // we encountered a new block. therefore no more ws.
2127 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
2128 WSType::OtherBlockBoundary
);
2131 if (!nextLeafContentOrBlock
->IsText() ||
2132 !nextLeafContentOrBlock
->IsEditable()) {
2133 // we encountered a break or a special node, like <img>,
2134 // that is not a block and not a break but still
2135 // serves as a terminator to ws runs.
2136 return BoundaryData(aPoint
, *nextLeafContentOrBlock
,
2137 nextLeafContentOrBlock
->IsHTMLElement(nsGkAtoms::br
)
2139 : WSType::SpecialContent
);
2142 if (!nextLeafContentOrBlock
->AsText()->TextFragment().GetLength()) {
2143 // If it's an empty text node, keep looking for its next leaf content.
2144 // Note that even if the empty text node is preformatted, we should keep
2145 // looking for the next one.
2146 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
2147 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0),
2148 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
2149 aNBSPData
, aBlockInlineCheck
);
2152 Maybe
<BoundaryData
> endInTextNode
=
2153 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
2154 EditorDOMPointInText(nextLeafContentOrBlock
->AsText(), 0), aNBSPData
,
2156 if (endInTextNode
.isSome()) {
2157 return endInTextNode
.ref();
2160 // The text node does not have visible character, let's keep scanning
2162 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
2163 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock
->AsText()),
2164 aEditableBlockParentOrTopmostEditableInlineElement
, aEditingHost
,
2165 aNBSPData
, aBlockInlineCheck
);
2168 const EditorDOMRange
&
2169 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
2170 if (mLeadingWhiteSpaceRange
.isSome()) {
2171 return mLeadingWhiteSpaceRange
.ref();
2174 // If it's start of line, there is no invisible leading white-spaces.
2175 if (!StartsFromHardLineBreak()) {
2176 mLeadingWhiteSpaceRange
.emplace();
2177 return mLeadingWhiteSpaceRange
.ref();
2180 // If there is no NBSP, all of the given range is leading white-spaces.
2181 // Note that this result may be collapsed if there is no leading white-spaces.
2182 if (!mNBSPData
.FoundNBSP()) {
2183 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
2184 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
2185 return mLeadingWhiteSpaceRange
.ref();
2188 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
2190 // Even if the first NBSP is the start, i.e., there is no invisible leading
2191 // white-space, return collapsed range.
2192 mLeadingWhiteSpaceRange
.emplace(mStart
.PointRef(), mNBSPData
.FirstPointRef());
2193 return mLeadingWhiteSpaceRange
.ref();
2196 const EditorDOMRange
&
2197 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
2198 if (mTrailingWhiteSpaceRange
.isSome()) {
2199 return mTrailingWhiteSpaceRange
.ref();
2202 // If it's not immediately before a block boundary nor an invisible
2203 // preformatted linefeed, there is no invisible trailing white-spaces. Note
2204 // that collapsible white-spaces before a `<br>` element is visible.
2205 if (!EndsByBlockBoundary() && !EndsByInvisiblePreformattedLineBreak()) {
2206 mTrailingWhiteSpaceRange
.emplace();
2207 return mTrailingWhiteSpaceRange
.ref();
2210 // If there is no NBSP, all of the given range is trailing white-spaces.
2211 // Note that this result may be collapsed if there is no trailing white-
2213 if (!mNBSPData
.FoundNBSP()) {
2214 MOZ_ASSERT(mStart
.PointRef().IsSet() || mEnd
.PointRef().IsSet());
2215 mTrailingWhiteSpaceRange
.emplace(mStart
.PointRef(), mEnd
.PointRef());
2216 return mTrailingWhiteSpaceRange
.ref();
2219 MOZ_ASSERT(mNBSPData
.LastPointRef().IsSetAndValid());
2221 // If last NBSP is immediately before the end, there is no trailing white-
2223 if (mEnd
.PointRef().IsSet() &&
2224 mNBSPData
.LastPointRef().GetContainer() ==
2225 mEnd
.PointRef().GetContainer() &&
2226 mNBSPData
.LastPointRef().Offset() == mEnd
.PointRef().Offset() - 1) {
2227 mTrailingWhiteSpaceRange
.emplace();
2228 return mTrailingWhiteSpaceRange
.ref();
2231 // Otherwise, the may be some trailing white-spaces.
2232 MOZ_ASSERT(!mNBSPData
.LastPointRef().IsEndOfContainer());
2233 mTrailingWhiteSpaceRange
.emplace(mNBSPData
.LastPointRef().NextPoint(),
2235 return mTrailingWhiteSpaceRange
.ref();
2238 EditorDOMRangeInTexts
2239 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
2240 const EditorDOMRange
& aRange
) const {
2241 if (!aRange
.IsPositioned()) {
2242 return EditorDOMRangeInTexts();
2244 if (aRange
.Collapsed()) {
2245 // If collapsed, we can do nothing.
2246 return EditorDOMRangeInTexts();
2248 if (aRange
.IsInTextNodes()) {
2249 // Note that this may return a range which don't include any invisible
2250 // white-spaces due to empty text nodes.
2251 return aRange
.GetAsInTexts();
2254 const auto firstPoint
=
2255 aRange
.StartRef().IsInTextNode()
2256 ? aRange
.StartRef().AsInText()
2257 : GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
2259 if (!firstPoint
.IsSet()) {
2260 return EditorDOMRangeInTexts();
2262 EditorDOMPointInText endPoint
;
2263 if (aRange
.EndRef().IsInTextNode()) {
2264 endPoint
= aRange
.EndRef().AsInText();
2266 // FYI: GetPreviousEditableCharPoint() returns last character's point
2267 // of preceding text node if it's not empty, but we need end of
2268 // the text node here.
2269 endPoint
= GetPreviousEditableCharPoint(aRange
.EndRef());
2270 if (endPoint
.IsSet() && endPoint
.IsAtLastContent()) {
2271 MOZ_ALWAYS_TRUE(endPoint
.AdvanceOffset());
2274 if (!endPoint
.IsSet() || firstPoint
== endPoint
) {
2275 return EditorDOMRangeInTexts();
2277 return EditorDOMRangeInTexts(firstPoint
, endPoint
);
2280 const WSRunScanner::VisibleWhiteSpacesData
&
2281 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
2282 if (mVisibleWhiteSpacesData
.isSome()) {
2283 return mVisibleWhiteSpacesData
.ref();
2287 // If all things are obviously visible, we can return range for all of the
2289 const bool mayHaveInvisibleLeadingSpace
=
2290 !StartsFromNonCollapsibleCharacters() && !StartsFromSpecialContent();
2291 const bool mayHaveInvisibleTrailingWhiteSpace
=
2292 !EndsByNonCollapsibleCharacters() && !EndsBySpecialContent() &&
2293 !EndsByBRElement() && !EndsByInvisiblePreformattedLineBreak();
2295 if (!mayHaveInvisibleLeadingSpace
&& !mayHaveInvisibleTrailingWhiteSpace
) {
2296 VisibleWhiteSpacesData visibleWhiteSpaces
;
2297 if (mStart
.PointRef().IsSet()) {
2298 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
2300 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
2301 if (mEnd
.PointRef().IsSet()) {
2302 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
2304 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
2305 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
2306 return mVisibleWhiteSpacesData
.ref();
2310 // If all of the range is invisible leading or trailing white-spaces,
2311 // there is no visible content.
2312 const EditorDOMRange
& leadingWhiteSpaceRange
=
2313 InvisibleLeadingWhiteSpaceRangeRef();
2314 const bool maybeHaveLeadingWhiteSpaces
=
2315 leadingWhiteSpaceRange
.StartRef().IsSet() ||
2316 leadingWhiteSpaceRange
.EndRef().IsSet();
2317 if (maybeHaveLeadingWhiteSpaces
&&
2318 leadingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
2319 leadingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
2320 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
2321 return mVisibleWhiteSpacesData
.ref();
2323 const EditorDOMRange
& trailingWhiteSpaceRange
=
2324 InvisibleTrailingWhiteSpaceRangeRef();
2325 const bool maybeHaveTrailingWhiteSpaces
=
2326 trailingWhiteSpaceRange
.StartRef().IsSet() ||
2327 trailingWhiteSpaceRange
.EndRef().IsSet();
2328 if (maybeHaveTrailingWhiteSpaces
&&
2329 trailingWhiteSpaceRange
.StartRef() == mStart
.PointRef() &&
2330 trailingWhiteSpaceRange
.EndRef() == mEnd
.PointRef()) {
2331 mVisibleWhiteSpacesData
.emplace(VisibleWhiteSpacesData());
2332 return mVisibleWhiteSpacesData
.ref();
2335 if (!StartsFromHardLineBreak()) {
2336 VisibleWhiteSpacesData visibleWhiteSpaces
;
2337 if (mStart
.PointRef().IsSet()) {
2338 visibleWhiteSpaces
.SetStartPoint(mStart
.PointRef());
2340 visibleWhiteSpaces
.SetStartFrom(mStart
.RawReason());
2341 if (!maybeHaveTrailingWhiteSpaces
) {
2342 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
2343 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
2344 mVisibleWhiteSpacesData
= Some(visibleWhiteSpaces
);
2345 return mVisibleWhiteSpacesData
.ref();
2347 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
2348 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
2350 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
2351 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
2352 return mVisibleWhiteSpacesData
.ref();
2355 MOZ_ASSERT(StartsFromHardLineBreak());
2356 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces
);
2358 VisibleWhiteSpacesData visibleWhiteSpaces
;
2359 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
2360 visibleWhiteSpaces
.SetStartPoint(leadingWhiteSpaceRange
.EndRef());
2362 visibleWhiteSpaces
.SetStartFromLeadingWhiteSpaces();
2363 if (!EndsByBlockBoundary()) {
2364 // then no trailing ws. this normal run ends the overall ws run.
2365 if (mEnd
.PointRef().IsSet()) {
2366 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
2368 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
2369 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
2370 return mVisibleWhiteSpacesData
.ref();
2373 MOZ_ASSERT(EndsByBlockBoundary());
2375 if (!maybeHaveTrailingWhiteSpaces
) {
2376 // normal ws runs right up to adjacent block (nbsp next to block)
2377 visibleWhiteSpaces
.SetEndPoint(mEnd
.PointRef());
2378 visibleWhiteSpaces
.SetEndBy(mEnd
.RawReason());
2379 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
2380 return mVisibleWhiteSpacesData
.ref();
2383 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
2384 visibleWhiteSpaces
.SetEndPoint(trailingWhiteSpaceRange
.StartRef());
2386 visibleWhiteSpaces
.SetEndByTrailingWhiteSpaces();
2387 mVisibleWhiteSpacesData
.emplace(visibleWhiteSpaces
);
2388 return mVisibleWhiteSpacesData
.ref();
2392 Result
<CaretPoint
, nsresult
> WhiteSpaceVisibilityKeeper::
2393 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
2394 HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRangeToDelete
,
2395 const Element
& aEditingHost
) {
2396 if (NS_WARN_IF(!aRangeToDelete
.IsPositionedAndValid()) ||
2397 NS_WARN_IF(!aRangeToDelete
.IsInContentNodes())) {
2398 return Err(NS_ERROR_INVALID_ARG
);
2401 EditorDOMRange
rangeToDelete(aRangeToDelete
);
2402 bool mayBecomeUnexpectedDOMTree
= aHTMLEditor
.MayHaveMutationEventListeners(
2403 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED
|
2404 NS_EVENT_BITS_MUTATION_NODEREMOVED
|
2405 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT
|
2406 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
);
2408 TextFragmentData
textFragmentDataAtStart(
2409 rangeToDelete
.StartRef(), &aEditingHost
,
2410 BlockInlineCheck::UseComputedDisplayStyle
);
2411 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
2412 return Err(NS_ERROR_FAILURE
);
2414 TextFragmentData
textFragmentDataAtEnd(
2415 rangeToDelete
.EndRef(), &aEditingHost
,
2416 BlockInlineCheck::UseComputedDisplayStyle
);
2417 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
2418 return Err(NS_ERROR_FAILURE
);
2420 ReplaceRangeData replaceRangeDataAtEnd
=
2421 textFragmentDataAtEnd
.GetReplaceRangeDataAtEndOfDeletionRange(
2422 textFragmentDataAtStart
);
2423 EditorDOMPoint pointToPutCaret
;
2424 if (replaceRangeDataAtEnd
.IsSet() && !replaceRangeDataAtEnd
.Collapsed()) {
2425 MOZ_ASSERT(rangeToDelete
.EndRef().EqualsOrIsBefore(
2426 replaceRangeDataAtEnd
.EndRef()));
2427 // If there is some text after deleting range, replacing range start must
2428 // equal or be before end of the deleting range.
2429 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
2430 !rangeToDelete
.EndRef().IsEndOfContainer(),
2431 replaceRangeDataAtEnd
.StartRef().EqualsOrIsBefore(
2432 rangeToDelete
.EndRef()));
2433 // If the deleting range end is end of a text node, the replacing range
2435 // - start with another node if the following text node starts with
2437 // - start from prior point because end of the range may be in collapsible
2439 MOZ_ASSERT_IF(rangeToDelete
.EndRef().IsInTextNode() &&
2440 rangeToDelete
.EndRef().IsEndOfContainer(),
2441 replaceRangeDataAtEnd
.StartRef().EqualsOrIsBefore(
2442 rangeToDelete
.EndRef()) ||
2443 replaceRangeDataAtEnd
.StartRef().IsStartOfContainer());
2444 MOZ_ASSERT(rangeToDelete
.StartRef().EqualsOrIsBefore(
2445 replaceRangeDataAtEnd
.StartRef()));
2446 if (!replaceRangeDataAtEnd
.HasReplaceString()) {
2447 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
2448 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
2450 AutoEditorDOMPointChildInvalidator
lockOffsetOfStart(startToDelete
);
2451 AutoEditorDOMPointChildInvalidator
lockOffsetOfEnd(endToDelete
);
2452 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
2454 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
2456 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2457 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2458 replaceRangeDataAtEnd
.StartRef(),
2459 replaceRangeDataAtEnd
.EndRef(),
2460 HTMLEditor::TreatEmptyTextNodes::
2461 KeepIfContainerOfRangeBoundaries
);
2462 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2464 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2465 return caretPointOrError
;
2467 caretPointOrError
.unwrap().MoveCaretPointTo(
2468 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2470 if (mayBecomeUnexpectedDOMTree
&&
2471 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
2472 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
2473 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
2474 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2476 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2477 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
2479 MOZ_ASSERT(replaceRangeDataAtEnd
.RangeRef().IsInTextNodes());
2480 EditorDOMPoint
startToDelete(aRangeToDelete
.StartRef());
2481 EditorDOMPoint
endToDelete(replaceRangeDataAtEnd
.StartRef());
2483 AutoTrackDOMPoint
trackStartToDelete(aHTMLEditor
.RangeUpdaterRef(),
2485 AutoTrackDOMPoint
trackEndToDelete(aHTMLEditor
.RangeUpdaterRef(),
2487 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2489 // FYI: ReplaceTextAndRemoveEmptyTextNodes() does not have any idea of
2490 // new caret position.
2492 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2493 aHTMLEditor
, replaceRangeDataAtEnd
.RangeRef().AsInTexts(),
2494 replaceRangeDataAtEnd
.ReplaceStringRef());
2495 if (NS_FAILED(rv
)) {
2497 "WhiteSpaceVisibilityKeeper::"
2498 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
2503 if (mayBecomeUnexpectedDOMTree
&&
2504 (NS_WARN_IF(!startToDelete
.IsSetAndValid()) ||
2505 NS_WARN_IF(!endToDelete
.IsSetAndValid()) ||
2506 NS_WARN_IF(!startToDelete
.EqualsOrIsBefore(endToDelete
)))) {
2507 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2509 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2510 rangeToDelete
.SetStartAndEnd(startToDelete
, endToDelete
);
2513 if (mayBecomeUnexpectedDOMTree
) {
2514 // If focus is changed by mutation event listeners, we should stop
2515 // handling this edit action.
2516 if (&aEditingHost
!= aHTMLEditor
.ComputeEditingHost()) {
2517 NS_WARNING("Active editing host was changed");
2518 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2520 if (!rangeToDelete
.IsInContentNodes()) {
2521 NS_WARNING("The modified range was not in content");
2522 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2524 // If the DOM tree might be changed by mutation event listeners, we
2525 // should retrieve the latest data for avoiding to delete/replace
2526 // unexpected range.
2527 textFragmentDataAtStart
=
2528 TextFragmentData(rangeToDelete
.StartRef(), &aEditingHost
,
2529 BlockInlineCheck::UseComputedDisplayStyle
);
2530 textFragmentDataAtEnd
=
2531 TextFragmentData(rangeToDelete
.EndRef(), &aEditingHost
,
2532 BlockInlineCheck::UseComputedDisplayStyle
);
2535 ReplaceRangeData replaceRangeDataAtStart
=
2536 textFragmentDataAtStart
.GetReplaceRangeDataAtStartOfDeletionRange(
2537 textFragmentDataAtEnd
);
2538 if (!replaceRangeDataAtStart
.IsSet() || replaceRangeDataAtStart
.Collapsed()) {
2539 return CaretPoint(std::move(pointToPutCaret
));
2541 if (!replaceRangeDataAtStart
.HasReplaceString()) {
2542 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2544 Result
<CaretPoint
, nsresult
> caretPointOrError
=
2545 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
2546 replaceRangeDataAtStart
.StartRef(),
2547 replaceRangeDataAtStart
.EndRef(),
2548 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
2549 // XXX Should we validate the range for making this return
2550 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2551 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
2552 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2553 return caretPointOrError
.propagateErr();
2555 trackPointToPutCaret
.FlushAndStopTracking();
2556 caretPointOrError
.unwrap().MoveCaretPointTo(
2557 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2558 return CaretPoint(std::move(pointToPutCaret
));
2561 MOZ_ASSERT(replaceRangeDataAtStart
.RangeRef().IsInTextNodes());
2563 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
2565 // FYI: ReplaceTextAndRemoveEmptyTextNodes() does not have any idea of
2566 // new caret position.
2568 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2569 aHTMLEditor
, replaceRangeDataAtStart
.RangeRef().AsInTexts(),
2570 replaceRangeDataAtStart
.ReplaceStringRef());
2571 // XXX Should we validate the range for making this return
2572 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2573 if (NS_FAILED(rv
)) {
2575 "WhiteSpaceVisibilityKeeper::"
2576 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() "
2581 return CaretPoint(std::move(pointToPutCaret
));
2585 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
2586 const TextFragmentData
& aTextFragmentDataAtStartToDelete
) const {
2587 const EditorDOMPoint
& startToDelete
=
2588 aTextFragmentDataAtStartToDelete
.ScanStartRef();
2589 const EditorDOMPoint
& endToDelete
= mScanStartPoint
;
2591 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2592 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2593 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2595 if (EndRef().EqualsOrIsBefore(endToDelete
)) {
2596 return ReplaceRangeData();
2599 // If deleting range is followed by invisible trailing white-spaces, we need
2600 // to remove it for making them not visible.
2601 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd
=
2602 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete
);
2603 if (invisibleTrailingWhiteSpaceRangeAtEnd
.IsPositioned()) {
2604 if (invisibleTrailingWhiteSpaceRangeAtEnd
.Collapsed()) {
2605 return ReplaceRangeData();
2607 // XXX Why don't we remove all invisible white-spaces?
2608 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd
.StartRef() == endToDelete
);
2609 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd
, u
""_ns
);
2612 // If end of the deleting range is followed by visible white-spaces which
2613 // is not preformatted, we might need to replace the following ASCII
2614 // white-spaces with an NBSP.
2615 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtEnd
=
2616 VisibleWhiteSpacesDataRef();
2617 if (!nonPreformattedVisibleWhiteSpacesAtEnd
.IsInitialized()) {
2618 return ReplaceRangeData();
2620 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
=
2621 nonPreformattedVisibleWhiteSpacesAtEnd
.ComparePoint(endToDelete
);
2622 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2623 PointPosition::StartOfFragment
&&
2624 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd
!=
2625 PointPosition::MiddleOfFragment
) {
2626 return ReplaceRangeData();
2628 // If start of deleting range follows white-spaces or end of delete
2629 // will be start of a line, the following text cannot start with an
2630 // ASCII white-space for keeping it visible.
2631 if (!aTextFragmentDataAtStartToDelete
2632 .FollowingContentMayBecomeFirstVisibleContent(startToDelete
)) {
2633 return ReplaceRangeData();
2635 auto nextCharOfStartOfEnd
=
2636 GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(endToDelete
);
2637 if (!nextCharOfStartOfEnd
.IsSet() ||
2638 nextCharOfStartOfEnd
.IsEndOfContainer() ||
2639 !nextCharOfStartOfEnd
.IsCharCollapsibleASCIISpace()) {
2640 return ReplaceRangeData();
2642 if (nextCharOfStartOfEnd
.IsStartOfContainer() ||
2643 nextCharOfStartOfEnd
.IsPreviousCharCollapsibleASCIISpace()) {
2644 nextCharOfStartOfEnd
= aTextFragmentDataAtStartToDelete
2645 .GetFirstASCIIWhiteSpacePointCollapsedTo(
2646 nextCharOfStartOfEnd
, nsIEditor::eNone
);
2648 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2649 aTextFragmentDataAtStartToDelete
.GetEndOfCollapsibleASCIIWhiteSpaces(
2650 nextCharOfStartOfEnd
, nsIEditor::eNone
);
2651 return ReplaceRangeData(nextCharOfStartOfEnd
,
2652 endOfCollapsibleASCIIWhiteSpaces
,
2653 nsDependentSubstring(&HTMLEditUtils::kNBSP
, 1));
2657 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
2658 const TextFragmentData
& aTextFragmentDataAtEndToDelete
) const {
2659 const EditorDOMPoint
& startToDelete
= mScanStartPoint
;
2660 const EditorDOMPoint
& endToDelete
=
2661 aTextFragmentDataAtEndToDelete
.ScanStartRef();
2663 MOZ_ASSERT(startToDelete
.IsSetAndValid());
2664 MOZ_ASSERT(endToDelete
.IsSetAndValid());
2665 MOZ_ASSERT(startToDelete
.EqualsOrIsBefore(endToDelete
));
2667 if (startToDelete
.EqualsOrIsBefore(StartRef())) {
2668 return ReplaceRangeData();
2671 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart
=
2672 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete
);
2674 // If deleting range follows invisible leading white-spaces, we need to
2675 // remove them for making them not visible.
2676 if (invisibleLeadingWhiteSpaceRangeAtStart
.IsPositioned()) {
2677 if (invisibleLeadingWhiteSpaceRangeAtStart
.Collapsed()) {
2678 return ReplaceRangeData();
2681 // XXX Why don't we remove all leading white-spaces?
2682 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart
, u
""_ns
);
2685 // If start of the deleting range follows visible white-spaces which is not
2686 // preformatted, we might need to replace previous ASCII white-spaces with
2688 const VisibleWhiteSpacesData
& nonPreformattedVisibleWhiteSpacesAtStart
=
2689 VisibleWhiteSpacesDataRef();
2690 if (!nonPreformattedVisibleWhiteSpacesAtStart
.IsInitialized()) {
2691 return ReplaceRangeData();
2694 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
=
2695 nonPreformattedVisibleWhiteSpacesAtStart
.ComparePoint(startToDelete
);
2696 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2697 PointPosition::MiddleOfFragment
&&
2698 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart
!=
2699 PointPosition::EndOfFragment
) {
2700 return ReplaceRangeData();
2702 // If end of the deleting range is (was) followed by white-spaces or
2703 // previous character of start of deleting range will be immediately
2704 // before a block boundary, the text cannot ends with an ASCII white-space
2705 // for keeping it visible.
2706 if (!aTextFragmentDataAtEndToDelete
.PrecedingContentMayBecomeInvisible(
2708 return ReplaceRangeData();
2710 EditorDOMPointInText atPreviousCharOfStart
=
2711 GetPreviousEditableCharPoint(startToDelete
);
2712 if (!atPreviousCharOfStart
.IsSet() ||
2713 atPreviousCharOfStart
.IsEndOfContainer() ||
2714 !atPreviousCharOfStart
.IsCharCollapsibleASCIISpace()) {
2715 return ReplaceRangeData();
2717 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2718 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2719 atPreviousCharOfStart
= GetFirstASCIIWhiteSpacePointCollapsedTo(
2720 atPreviousCharOfStart
, nsIEditor::eNone
);
2722 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2723 GetEndOfCollapsibleASCIIWhiteSpaces(atPreviousCharOfStart
,
2725 return ReplaceRangeData(atPreviousCharOfStart
,
2726 endOfCollapsibleASCIIWhiteSpaces
,
2727 nsDependentSubstring(&HTMLEditUtils::kNBSP
, 1));
2732 WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
2733 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPointToSplit
) {
2734 TextFragmentData
textFragmentDataAtSplitPoint(
2735 aPointToSplit
, aHTMLEditor
.ComputeEditingHost(),
2736 BlockInlineCheck::UseComputedDisplayStyle
);
2737 if (NS_WARN_IF(!textFragmentDataAtSplitPoint
.IsInitialized())) {
2738 return NS_ERROR_FAILURE
;
2741 // used to prepare white-space sequence to be split across two blocks.
2742 // The main issue here is make sure white-spaces around the split point
2743 // doesn't end up becoming non-significant leading or trailing ws after
2745 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
2746 textFragmentDataAtSplitPoint
.VisibleWhiteSpacesDataRef();
2747 if (!visibleWhiteSpaces
.IsInitialized()) {
2748 return NS_OK
; // No visible white-space sequence.
2751 PointPosition pointPositionWithVisibleWhiteSpaces
=
2752 visibleWhiteSpaces
.ComparePoint(aPointToSplit
);
2754 // XXX If we split white-space sequence, the following code modify the DOM
2755 // tree twice. This is not reasonable and the latter change may touch
2756 // wrong position. We should do this once.
2758 // If we insert block boundary to start or middle of the white-space sequence,
2759 // the character at the insertion point needs to be an NBSP.
2760 EditorDOMPoint
pointToSplit(aPointToSplit
);
2761 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::StartOfFragment
||
2762 pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
) {
2763 EditorDOMPointInText atNextCharOfStart
=
2764 textFragmentDataAtSplitPoint
.GetInclusiveNextEditableCharPoint(
2766 if (atNextCharOfStart
.IsSet() && !atNextCharOfStart
.IsEndOfContainer() &&
2767 atNextCharOfStart
.IsCharCollapsibleASCIISpace()) {
2768 // pointToSplit will be referred bellow so that we need to keep
2769 // it a valid point.
2770 AutoEditorDOMPointChildInvalidator
forgetChild(pointToSplit
);
2771 AutoTrackDOMPoint
trackSplitPoint(aHTMLEditor
.RangeUpdaterRef(),
2773 if (atNextCharOfStart
.IsStartOfContainer() ||
2774 atNextCharOfStart
.IsPreviousCharASCIISpace()) {
2775 atNextCharOfStart
= textFragmentDataAtSplitPoint
2776 .GetFirstASCIIWhiteSpacePointCollapsedTo(
2777 atNextCharOfStart
, nsIEditor::eNone
);
2779 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2780 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2781 atNextCharOfStart
, nsIEditor::eNone
);
2783 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2785 EditorDOMRangeInTexts(atNextCharOfStart
,
2786 endOfCollapsibleASCIIWhiteSpaces
),
2787 nsDependentSubstring(&HTMLEditUtils::kNBSP
, 1));
2788 if (NS_FAILED(rv
)) {
2790 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2797 // If we insert block boundary to middle of or end of the white-space
2798 // sequence, the previous character at the insertion point needs to be an
2800 if (pointPositionWithVisibleWhiteSpaces
== PointPosition::MiddleOfFragment
||
2801 pointPositionWithVisibleWhiteSpaces
== PointPosition::EndOfFragment
) {
2802 EditorDOMPointInText atPreviousCharOfStart
=
2803 textFragmentDataAtSplitPoint
.GetPreviousEditableCharPoint(pointToSplit
);
2804 if (atPreviousCharOfStart
.IsSet() &&
2805 !atPreviousCharOfStart
.IsEndOfContainer() &&
2806 atPreviousCharOfStart
.IsCharCollapsibleASCIISpace()) {
2807 if (atPreviousCharOfStart
.IsStartOfContainer() ||
2808 atPreviousCharOfStart
.IsPreviousCharASCIISpace()) {
2809 atPreviousCharOfStart
=
2810 textFragmentDataAtSplitPoint
2811 .GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart
,
2814 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces
=
2815 textFragmentDataAtSplitPoint
.GetEndOfCollapsibleASCIIWhiteSpaces(
2816 atPreviousCharOfStart
, nsIEditor::eNone
);
2818 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2820 EditorDOMRangeInTexts(atPreviousCharOfStart
,
2821 endOfCollapsibleASCIIWhiteSpaces
),
2822 nsDependentSubstring(&HTMLEditUtils::kNBSP
, 1));
2823 if (NS_FAILED(rv
)) {
2825 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2834 template <typename EditorDOMPointType
, typename PT
, typename CT
>
2836 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint(
2837 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2838 MOZ_ASSERT(aPoint
.IsSetAndValid());
2840 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2841 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2842 return EditorDOMPointType();
2845 EditorRawDOMPoint point
;
2846 if (nsIContent
* child
=
2847 aPoint
.CanContainerHaveChildren() ? aPoint
.GetChild() : nullptr) {
2848 nsIContent
* leafContent
= child
->HasChildren()
2849 ? HTMLEditUtils::GetFirstLeafContent(
2850 *child
, {LeafNodeType::OnlyLeafNode
})
2852 if (NS_WARN_IF(!leafContent
)) {
2853 return EditorDOMPointType();
2855 point
.Set(leafContent
, 0);
2857 point
= aPoint
.template To
<EditorRawDOMPoint
>();
2860 // If it points a character in a text node, return it.
2861 // XXX For the performance, this does not check whether the container
2862 // is outside of our range.
2863 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2864 !point
.IsEndOfContainer()) {
2865 return EditorDOMPointType(point
.ContainerAs
<Text
>(), point
.Offset());
2868 if (point
.GetContainer() == GetEndReasonContent()) {
2869 return EditorDOMPointType();
2873 EditorUtils::IsEditableContent(*mScanStartPoint
.ContainerAs
<nsIContent
>(),
2875 "Given content is not editable");
2877 mScanStartPoint
.ContainerAs
<nsIContent
>()->GetAsElementOrParentElement(),
2878 "Given content is not an element and an orphan node");
2879 nsIContent
* editableBlockElementOrInlineEditingHost
=
2880 mScanStartPoint
.ContainerAs
<nsIContent
>() &&
2881 EditorUtils::IsEditableContent(
2882 *mScanStartPoint
.ContainerAs
<nsIContent
>(), EditorType::HTML
)
2883 ? HTMLEditUtils::GetInclusiveAncestorElement(
2884 *mScanStartPoint
.ContainerAs
<nsIContent
>(),
2885 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost
,
2888 if (NS_WARN_IF(!editableBlockElementOrInlineEditingHost
)) {
2889 // Meaning that the container of `mScanStartPoint` is not editable.
2890 editableBlockElementOrInlineEditingHost
=
2891 mScanStartPoint
.ContainerAs
<nsIContent
>();
2894 for (nsIContent
* nextContent
=
2895 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2896 *point
.ContainerAs
<nsIContent
>(),
2897 *editableBlockElementOrInlineEditingHost
,
2898 {LeafNodeType::LeafNodeOrNonEditableNode
}, mBlockInlineCheck
,
2901 nextContent
= HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2902 *nextContent
, *editableBlockElementOrInlineEditingHost
,
2903 {LeafNodeType::LeafNodeOrNonEditableNode
}, mBlockInlineCheck
,
2905 if (!nextContent
->IsText() || !nextContent
->IsEditable()) {
2906 if (nextContent
== GetEndReasonContent()) {
2907 break; // Reached end of current runs.
2911 return EditorDOMPointType(nextContent
->AsText(), 0);
2913 return EditorDOMPointType();
2916 template <typename EditorDOMPointType
, typename PT
, typename CT
>
2917 EditorDOMPointType
WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint(
2918 const EditorDOMPointBase
<PT
, CT
>& aPoint
) const {
2919 MOZ_ASSERT(aPoint
.IsSetAndValid());
2921 if (NS_WARN_IF(!aPoint
.IsInContentNode()) ||
2922 NS_WARN_IF(!mScanStartPoint
.IsInContentNode())) {
2923 return EditorDOMPointType();
2926 EditorRawDOMPoint point
;
2927 if (nsIContent
* previousChild
= aPoint
.CanContainerHaveChildren()
2928 ? aPoint
.GetPreviousSiblingOfChild()
2930 nsIContent
* leafContent
=
2931 previousChild
->HasChildren()
2932 ? HTMLEditUtils::GetLastLeafContent(*previousChild
,
2933 {LeafNodeType::OnlyLeafNode
})
2935 if (NS_WARN_IF(!leafContent
)) {
2936 return EditorDOMPointType();
2938 point
.SetToEndOf(leafContent
);
2940 point
= aPoint
.template To
<EditorRawDOMPoint
>();
2943 // If it points a character in a text node and it's not first character
2944 // in it, return its previous point.
2945 // XXX For the performance, this does not check whether the container
2946 // is outside of our range.
2947 if (point
.IsInTextNode() && point
.GetContainer()->IsEditable() &&
2948 !point
.IsStartOfContainer()) {
2949 return EditorDOMPointType(point
.ContainerAs
<Text
>(), point
.Offset() - 1);
2952 if (point
.GetContainer() == GetStartReasonContent()) {
2953 return EditorDOMPointType();
2957 EditorUtils::IsEditableContent(*mScanStartPoint
.ContainerAs
<nsIContent
>(),
2959 "Given content is not editable");
2961 mScanStartPoint
.ContainerAs
<nsIContent
>()->GetAsElementOrParentElement(),
2962 "Given content is not an element and an orphan node");
2963 nsIContent
* editableBlockElementOrInlineEditingHost
=
2964 mScanStartPoint
.ContainerAs
<nsIContent
>() &&
2965 EditorUtils::IsEditableContent(
2966 *mScanStartPoint
.ContainerAs
<nsIContent
>(), EditorType::HTML
)
2967 ? HTMLEditUtils::GetInclusiveAncestorElement(
2968 *mScanStartPoint
.ContainerAs
<nsIContent
>(),
2969 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost
,
2972 if (NS_WARN_IF(!editableBlockElementOrInlineEditingHost
)) {
2973 // Meaning that the container of `mScanStartPoint` is not editable.
2974 editableBlockElementOrInlineEditingHost
=
2975 mScanStartPoint
.ContainerAs
<nsIContent
>();
2978 for (nsIContent
* previousContent
=
2979 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2980 *point
.ContainerAs
<nsIContent
>(),
2981 *editableBlockElementOrInlineEditingHost
,
2982 {LeafNodeType::LeafNodeOrNonEditableNode
}, mBlockInlineCheck
,
2986 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2987 *previousContent
, *editableBlockElementOrInlineEditingHost
,
2988 {LeafNodeType::LeafNodeOrNonEditableNode
}, mBlockInlineCheck
,
2990 if (!previousContent
->IsText() || !previousContent
->IsEditable()) {
2991 if (previousContent
== GetStartReasonContent()) {
2992 break; // Reached start of current runs.
2996 return EditorDOMPointType(previousContent
->AsText(),
2997 previousContent
->AsText()->TextLength()
2998 ? previousContent
->AsText()->TextLength() - 1
3001 return EditorDOMPointType();
3005 template <typename EditorDOMPointType
>
3006 EditorDOMPointType
WSRunScanner::GetAfterLastVisiblePoint(
3007 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
3008 EditorDOMPoint
atLastCharOfTextNode(
3009 &aTextNode
, AssertedCast
<uint32_t>(std::max
<int64_t>(
3010 static_cast<int64_t>(aTextNode
.Length()) - 1, 0)));
3011 if (!atLastCharOfTextNode
.IsContainerEmpty() &&
3012 !atLastCharOfTextNode
.IsCharCollapsibleASCIISpace()) {
3013 return EditorDOMPointType::AtEndOf(aTextNode
);
3015 TextFragmentData
textFragmentData(atLastCharOfTextNode
, aAncestorLimiter
,
3016 BlockInlineCheck::UseComputedDisplayStyle
);
3017 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3018 return EditorDOMPointType(); // TODO: Make here return error with Err.
3020 const EditorDOMRange
& invisibleWhiteSpaceRange
=
3021 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
3022 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
3023 invisibleWhiteSpaceRange
.Collapsed()) {
3024 return EditorDOMPointType::AtEndOf(aTextNode
);
3026 return invisibleWhiteSpaceRange
.StartRef().To
<EditorDOMPointType
>();
3030 template <typename EditorDOMPointType
>
3031 EditorDOMPointType
WSRunScanner::GetFirstVisiblePoint(
3032 Text
& aTextNode
, const Element
* aAncestorLimiter
) {
3033 EditorDOMPoint
atStartOfTextNode(&aTextNode
, 0);
3034 if (!atStartOfTextNode
.IsContainerEmpty() &&
3035 atStartOfTextNode
.IsCharCollapsibleASCIISpace()) {
3036 return atStartOfTextNode
.To
<EditorDOMPointType
>();
3038 TextFragmentData
textFragmentData(atStartOfTextNode
, aAncestorLimiter
,
3039 BlockInlineCheck::UseComputedDisplayStyle
);
3040 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3041 return EditorDOMPointType(); // TODO: Make here return error with Err.
3043 const EditorDOMRange
& invisibleWhiteSpaceRange
=
3044 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
3045 if (!invisibleWhiteSpaceRange
.IsPositioned() ||
3046 invisibleWhiteSpaceRange
.Collapsed()) {
3047 return atStartOfTextNode
.To
<EditorDOMPointType
>();
3049 return invisibleWhiteSpaceRange
.EndRef().To
<EditorDOMPointType
>();
3052 template <typename EditorDOMPointType
>
3054 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
3055 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
3056 nsIEditor::EDirection aDirectionToDelete
) const {
3057 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
3058 aDirectionToDelete
== nsIEditor::eNext
||
3059 aDirectionToDelete
== nsIEditor::ePrevious
);
3060 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
3061 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
3062 MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
3063 *aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>()),
3064 aPointAtASCIIWhiteSpace
.IsCharCollapsibleASCIISpace());
3065 MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
3066 *aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>()),
3067 aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
3069 // If we're deleting text forward and the next visible character is first
3070 // preformatted new line but white-spaces can be collapsed, we need to
3071 // delete its following collapsible white-spaces too.
3072 bool hasSeenPreformattedNewLine
=
3073 aPointAtASCIIWhiteSpace
.IsCharPreformattedNewLine();
3074 auto NeedToScanFollowingWhiteSpaces
=
3075 [&hasSeenPreformattedNewLine
, &aDirectionToDelete
](
3076 const EditorDOMPointInText
& aAtNextVisibleCharacter
) -> bool {
3077 MOZ_ASSERT(!aAtNextVisibleCharacter
.IsEndOfContainer());
3078 return !hasSeenPreformattedNewLine
&&
3079 aDirectionToDelete
== nsIEditor::eNext
&&
3080 aAtNextVisibleCharacter
3081 .IsCharPreformattedNewLineCollapsedWithWhiteSpaces();
3083 auto ScanNextNonCollapsibleChar
=
3084 [&hasSeenPreformattedNewLine
, &NeedToScanFollowingWhiteSpaces
](
3085 const EditorDOMPointInText
& aPoint
) -> EditorDOMPointInText
{
3086 Maybe
<uint32_t> nextVisibleCharOffset
=
3087 HTMLEditUtils::GetNextNonCollapsibleCharOffset(aPoint
);
3088 if (!nextVisibleCharOffset
.isSome()) {
3089 return EditorDOMPointInText(); // Keep scanning following text nodes
3091 EditorDOMPointInText
atNextVisibleChar(aPoint
.ContainerAs
<Text
>(),
3092 nextVisibleCharOffset
.value());
3093 if (!NeedToScanFollowingWhiteSpaces(atNextVisibleChar
)) {
3094 return atNextVisibleChar
;
3096 hasSeenPreformattedNewLine
|= atNextVisibleChar
.IsCharPreformattedNewLine();
3097 nextVisibleCharOffset
=
3098 HTMLEditUtils::GetNextNonCollapsibleCharOffset(atNextVisibleChar
);
3099 if (nextVisibleCharOffset
.isSome()) {
3100 MOZ_ASSERT(aPoint
.ContainerAs
<Text
>() ==
3101 atNextVisibleChar
.ContainerAs
<Text
>());
3102 return EditorDOMPointInText(atNextVisibleChar
.ContainerAs
<Text
>(),
3103 nextVisibleCharOffset
.value());
3105 return EditorDOMPointInText(); // Keep scanning following text nodes
3108 // If it's not the last character in the text node, let's scan following
3109 // characters in it.
3110 if (!aPointAtASCIIWhiteSpace
.IsAtLastContent()) {
3111 const EditorDOMPointInText
atNextVisibleChar(
3112 ScanNextNonCollapsibleChar(aPointAtASCIIWhiteSpace
));
3113 if (atNextVisibleChar
.IsSet()) {
3114 return atNextVisibleChar
.To
<EditorDOMPointType
>();
3118 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
3119 // the following text nodes.
3120 // XXX Perhaps, we should stop scanning if there is non-editable and visible
3122 EditorDOMPointInText afterLastWhiteSpace
= EditorDOMPointInText::AtEndOf(
3123 *aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>());
3124 for (EditorDOMPointInText atEndOfPreviousTextNode
= afterLastWhiteSpace
;;) {
3125 const auto atStartOfNextTextNode
=
3126 GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
3127 atEndOfPreviousTextNode
);
3128 if (!atStartOfNextTextNode
.IsSet()) {
3129 // There is no more text nodes. Return end of the previous text node.
3130 return afterLastWhiteSpace
.To
<EditorDOMPointType
>();
3133 // We can ignore empty text nodes (even if it's preformatted).
3134 if (atStartOfNextTextNode
.IsContainerEmpty()) {
3135 atEndOfPreviousTextNode
= atStartOfNextTextNode
;
3139 // If next node starts with non-white-space character or next node is
3140 // preformatted, return end of previous text node. However, if it
3141 // starts with a preformatted linefeed but white-spaces are collapsible,
3142 // we need to scan following collapsible white-spaces when we're deleting
3144 if (!atStartOfNextTextNode
.IsCharCollapsibleASCIISpace() &&
3145 !NeedToScanFollowingWhiteSpaces(atStartOfNextTextNode
)) {
3146 return afterLastWhiteSpace
.To
<EditorDOMPointType
>();
3149 // Otherwise, scan the text node.
3150 const EditorDOMPointInText
atNextVisibleChar(
3151 ScanNextNonCollapsibleChar(atStartOfNextTextNode
));
3152 if (atNextVisibleChar
.IsSet()) {
3153 return atNextVisibleChar
.To
<EditorDOMPointType
>();
3156 // The next text nodes ends with white-space too. Try next one.
3157 afterLastWhiteSpace
= atEndOfPreviousTextNode
=
3158 EditorDOMPointInText::AtEndOf(
3159 *atStartOfNextTextNode
.ContainerAs
<Text
>());
3163 template <typename EditorDOMPointType
>
3165 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
3166 const EditorDOMPointInText
& aPointAtASCIIWhiteSpace
,
3167 nsIEditor::EDirection aDirectionToDelete
) const {
3168 MOZ_ASSERT(aDirectionToDelete
== nsIEditor::eNone
||
3169 aDirectionToDelete
== nsIEditor::eNext
||
3170 aDirectionToDelete
== nsIEditor::ePrevious
);
3171 MOZ_ASSERT(aPointAtASCIIWhiteSpace
.IsSet());
3172 MOZ_ASSERT(!aPointAtASCIIWhiteSpace
.IsEndOfContainer());
3173 MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
3174 *aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>()),
3175 aPointAtASCIIWhiteSpace
.IsCharCollapsibleASCIISpace());
3176 MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
3177 *aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>()),
3178 aPointAtASCIIWhiteSpace
.IsCharASCIISpace());
3180 // If we're deleting text backward and the previous visible character is first
3181 // preformatted new line but white-spaces can be collapsed, we need to delete
3182 // its preceding collapsible white-spaces too.
3183 bool hasSeenPreformattedNewLine
=
3184 aPointAtASCIIWhiteSpace
.IsCharPreformattedNewLine();
3185 auto NeedToScanPrecedingWhiteSpaces
=
3186 [&hasSeenPreformattedNewLine
, &aDirectionToDelete
](
3187 const EditorDOMPointInText
& aAtPreviousVisibleCharacter
) -> bool {
3188 MOZ_ASSERT(!aAtPreviousVisibleCharacter
.IsEndOfContainer());
3189 return !hasSeenPreformattedNewLine
&&
3190 aDirectionToDelete
== nsIEditor::ePrevious
&&
3191 aAtPreviousVisibleCharacter
3192 .IsCharPreformattedNewLineCollapsedWithWhiteSpaces();
3194 auto ScanPreviousNonCollapsibleChar
=
3195 [&hasSeenPreformattedNewLine
, &NeedToScanPrecedingWhiteSpaces
](
3196 const EditorDOMPointInText
& aPoint
) -> EditorDOMPointInText
{
3197 Maybe
<uint32_t> previousVisibleCharOffset
=
3198 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(aPoint
);
3199 if (previousVisibleCharOffset
.isNothing()) {
3200 return EditorDOMPointInText(); // Keep scanning preceding text nodes
3202 EditorDOMPointInText
atPreviousVisibleCharacter(
3203 aPoint
.ContainerAs
<Text
>(), previousVisibleCharOffset
.value());
3204 if (!NeedToScanPrecedingWhiteSpaces(atPreviousVisibleCharacter
)) {
3205 return atPreviousVisibleCharacter
.NextPoint();
3207 hasSeenPreformattedNewLine
|=
3208 atPreviousVisibleCharacter
.IsCharPreformattedNewLine();
3209 previousVisibleCharOffset
=
3210 HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
3211 atPreviousVisibleCharacter
);
3212 if (previousVisibleCharOffset
.isSome()) {
3213 MOZ_ASSERT(aPoint
.ContainerAs
<Text
>() ==
3214 atPreviousVisibleCharacter
.ContainerAs
<Text
>());
3215 return EditorDOMPointInText(
3216 atPreviousVisibleCharacter
.ContainerAs
<Text
>(),
3217 previousVisibleCharOffset
.value() + 1);
3219 return EditorDOMPointInText(); // Keep scanning preceding text nodes
3222 // If there is some characters before it, scan it in the text node first.
3223 if (!aPointAtASCIIWhiteSpace
.IsStartOfContainer()) {
3224 EditorDOMPointInText
atFirstASCIIWhiteSpace(
3225 ScanPreviousNonCollapsibleChar(aPointAtASCIIWhiteSpace
));
3226 if (atFirstASCIIWhiteSpace
.IsSet()) {
3227 return atFirstASCIIWhiteSpace
.To
<EditorDOMPointType
>();
3231 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
3232 // the preceding text nodes.
3233 // XXX Perhaps, we should stop scanning if there is non-editable and visible
3235 EditorDOMPointInText atLastWhiteSpace
=
3236 EditorDOMPointInText(aPointAtASCIIWhiteSpace
.ContainerAs
<Text
>(), 0u);
3237 for (EditorDOMPointInText atStartOfPreviousTextNode
= atLastWhiteSpace
;;) {
3238 const EditorDOMPointInText atLastCharOfPreviousTextNode
=
3239 GetPreviousEditableCharPoint(atStartOfPreviousTextNode
);
3240 if (!atLastCharOfPreviousTextNode
.IsSet()) {
3241 // There is no more text nodes. Return end of last text node.
3242 return atLastWhiteSpace
.To
<EditorDOMPointType
>();
3245 // We can ignore empty text nodes (even if it's preformatted).
3246 if (atLastCharOfPreviousTextNode
.IsContainerEmpty()) {
3247 atStartOfPreviousTextNode
= atLastCharOfPreviousTextNode
;
3251 // If next node ends with non-white-space character or next node is
3252 // preformatted, return start of previous text node.
3253 if (!atLastCharOfPreviousTextNode
.IsCharCollapsibleASCIISpace() &&
3254 !NeedToScanPrecedingWhiteSpaces(atLastCharOfPreviousTextNode
)) {
3255 return atLastWhiteSpace
.To
<EditorDOMPointType
>();
3258 // Otherwise, scan the text node.
3259 const EditorDOMPointInText
atFirstASCIIWhiteSpace(
3260 ScanPreviousNonCollapsibleChar(atLastCharOfPreviousTextNode
));
3261 if (atFirstASCIIWhiteSpace
.IsSet()) {
3262 return atFirstASCIIWhiteSpace
.To
<EditorDOMPointType
>();
3265 // The next text nodes starts with white-space too. Try next one.
3266 atLastWhiteSpace
= atStartOfPreviousTextNode
= EditorDOMPointInText(
3267 atLastCharOfPreviousTextNode
.ContainerAs
<Text
>(), 0u);
3272 nsresult
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
3273 HTMLEditor
& aHTMLEditor
, const EditorDOMRangeInTexts
& aRangeToReplace
,
3274 const nsAString
& aReplaceString
) {
3275 MOZ_ASSERT(aRangeToReplace
.IsPositioned());
3276 MOZ_ASSERT(aRangeToReplace
.StartRef().IsSetAndValid());
3277 MOZ_ASSERT(aRangeToReplace
.EndRef().IsSetAndValid());
3278 MOZ_ASSERT(aRangeToReplace
.StartRef().IsBefore(aRangeToReplace
.EndRef()));
3281 Result
<InsertTextResult
, nsresult
> caretPointOrError
=
3282 aHTMLEditor
.ReplaceTextWithTransaction(
3283 MOZ_KnownLive(*aRangeToReplace
.StartRef().ContainerAs
<Text
>()),
3284 aRangeToReplace
.StartRef().Offset(),
3285 aRangeToReplace
.InSameContainer()
3286 ? aRangeToReplace
.EndRef().Offset() -
3287 aRangeToReplace
.StartRef().Offset()
3288 : aRangeToReplace
.StartRef().ContainerAs
<Text
>()->TextLength() -
3289 aRangeToReplace
.StartRef().Offset(),
3291 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3292 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3293 return caretPointOrError
.unwrapErr();
3295 // Ignore caret suggestion because there was
3296 // AutoTransactionsConserveSelection.
3297 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
3300 if (aRangeToReplace
.InSameContainer()) {
3304 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3305 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3306 EditorDOMPointInText::AtEndOf(
3307 *aRangeToReplace
.StartRef().ContainerAs
<Text
>()),
3308 aRangeToReplace
.EndRef(),
3309 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3310 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3311 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3312 return caretPointOrError
.unwrapErr();
3314 // Ignore caret suggestion because there was
3315 // AutoTransactionsConserveSelection.
3316 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
3320 char16_t
WSRunScanner::GetCharAt(Text
* aTextNode
, uint32_t aOffset
) const {
3321 // return 0 if we can't get a char, for whatever reason
3322 if (NS_WARN_IF(!aTextNode
) ||
3323 NS_WARN_IF(aOffset
>= aTextNode
->TextDataLength())) {
3326 return aTextNode
->TextFragment().CharAt(aOffset
);
3330 template <typename EditorDOMPointType
>
3331 nsresult
WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
3332 HTMLEditor
& aHTMLEditor
, const EditorDOMPointType
& aPoint
) {
3333 MOZ_ASSERT(aPoint
.IsInContentNode());
3334 MOZ_ASSERT(EditorUtils::IsEditableContent(
3335 *aPoint
.template ContainerAs
<nsIContent
>(), EditorType::HTML
));
3336 Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
3337 TextFragmentData
textFragmentData(aPoint
, editingHost
,
3338 BlockInlineCheck::UseComputedDisplayStyle
);
3339 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3340 return NS_ERROR_FAILURE
;
3343 // this routine examines a run of ws and tries to get rid of some unneeded
3344 // nbsp's, replacing them with regular ascii space if possible. Keeping
3345 // things simple for now and just trying to fix up the trailing ws in the run.
3346 if (!textFragmentData
.FoundNoBreakingWhiteSpaces()) {
3350 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3351 textFragmentData
.VisibleWhiteSpacesDataRef();
3352 if (!visibleWhiteSpaces
.IsInitialized()) {
3356 // Remove this block if we ship Blink-compat white-space normalization.
3357 if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
3358 // now check that what is to the left of it is compatible with replacing
3360 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
=
3361 visibleWhiteSpaces
.EndRef();
3362 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
3363 textFragmentData
.GetPreviousEditableCharPoint(
3364 atEndOfVisibleWhiteSpaces
);
3365 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
3366 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
3367 // If the NBSP is never replaced from an ASCII white-space, we cannot
3368 // replace it with an ASCII white-space.
3369 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharCollapsibleNBSP()) {
3373 // now check that what is to the left of it is compatible with replacing
3375 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
3376 textFragmentData
.GetPreviousEditableCharPoint(
3377 atPreviousCharOfEndOfVisibleWhiteSpaces
);
3378 bool isPreviousCharCollapsibleASCIIWhiteSpace
=
3379 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
3380 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3381 .IsEndOfContainer() &&
3382 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3383 .IsCharCollapsibleASCIISpace();
3384 const bool maybeNBSPFollowsVisibleContent
=
3385 (atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
3386 !isPreviousCharCollapsibleASCIIWhiteSpace
) ||
3387 (!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
3388 (visibleWhiteSpaces
.StartsFromNonCollapsibleCharacters() ||
3389 visibleWhiteSpaces
.StartsFromSpecialContent()));
3390 bool followedByVisibleContent
=
3391 visibleWhiteSpaces
.EndsByNonCollapsibleCharacters() ||
3392 visibleWhiteSpaces
.EndsBySpecialContent();
3393 bool followedByBRElement
= visibleWhiteSpaces
.EndsByBRElement();
3394 bool followedByPreformattedLineBreak
=
3395 visibleWhiteSpaces
.EndsByPreformattedLineBreak();
3397 // If the NBSP follows a visible content or a collapsible ASCII white-space,
3398 // i.e., unless NBSP is first character and start of a block, we may need to
3399 // insert <br> element and restore the NBSP to an ASCII white-space.
3400 if (maybeNBSPFollowsVisibleContent
||
3401 isPreviousCharCollapsibleASCIIWhiteSpace
) {
3402 // First, try to insert <br> element if NBSP is at end of a block.
3403 // XXX We should stop this if there is a visible content.
3404 if (visibleWhiteSpaces
.EndsByBlockBoundary() &&
3405 aPoint
.IsInContentNode()) {
3406 bool insertBRElement
= HTMLEditUtils::IsBlockElement(
3407 *aPoint
.template ContainerAs
<nsIContent
>(),
3408 BlockInlineCheck::UseComputedDisplayStyle
);
3409 if (!insertBRElement
) {
3411 EditorUtils::IsEditableContent(
3412 *aPoint
.template ContainerAs
<nsIContent
>(), EditorType::HTML
),
3413 "Given content is not editable");
3414 NS_ASSERTION(aPoint
.template ContainerAs
<nsIContent
>()
3415 ->GetAsElementOrParentElement(),
3416 "Given content is not an element and an orphan node");
3417 const Element
* editableBlockElement
=
3418 EditorUtils::IsEditableContent(
3419 *aPoint
.template ContainerAs
<nsIContent
>(), EditorType::HTML
)
3420 ? HTMLEditUtils::GetInclusiveAncestorElement(
3421 *aPoint
.template ContainerAs
<nsIContent
>(),
3422 HTMLEditUtils::ClosestEditableBlockElement
,
3423 BlockInlineCheck::UseComputedDisplayStyle
)
3425 insertBRElement
= !!editableBlockElement
;
3427 if (insertBRElement
) {
3428 // We are at a block boundary. Insert a <br>. Why? Well, first note
3429 // that the br will have no visible effect since it is up against a
3430 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
3431 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
3432 // this <br> addition gets us is the ability to convert a trailing
3433 // nbsp to a space. Consider: |<body>foo. '</body>|, where '
3434 // represents selection. User types space attempting to put 2 spaces
3435 // after the end of their sentence. We used to do this as:
3436 // |<body>foo.  </body>| This caused problems with soft wrapping:
3437 // the nbsp would wrap to the next line, which looked attrocious. If
3438 // you try to do: |<body>foo.  </body>| instead, the trailing
3439 // space is invisible because it is against a block boundary. If you
3441 // |<body>foo.  </body>| then you get an even uglier soft
3442 // wrapping problem, where foo is on one line until you type the final
3443 // space, and then "foo " jumps down to the next line. Ugh. The
3444 // best way I can find out of this is to throw in a harmless <br>
3445 // here, which allows us to do: |<body>foo.  <br></body>|, which
3446 // doesn't cause foo to jump lines, doesn't cause spaces to show up at
3447 // the beginning of soft wrapped lines, and lets the user see 2 spaces
3448 // when they type 2 spaces.
3450 Result
<CreateElementResult
, nsresult
> insertBRElementResult
=
3451 aHTMLEditor
.InsertBRElement(WithTransaction::Yes
,
3452 atEndOfVisibleWhiteSpaces
);
3453 if (MOZ_UNLIKELY(insertBRElementResult
.isErr())) {
3455 "HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
3456 return insertBRElementResult
.propagateErr();
3458 MOZ_ASSERT(insertBRElementResult
.inspect().GetNewNode());
3459 // Ignore caret suggestion because the caller must want to restore
3460 // `Selection` due to the purpose of this method.
3461 insertBRElementResult
.unwrap().IgnoreCaretPointSuggestion();
3463 atPreviousCharOfEndOfVisibleWhiteSpaces
=
3464 textFragmentData
.GetPreviousEditableCharPoint(
3465 atEndOfVisibleWhiteSpaces
);
3466 if (NS_WARN_IF(!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet())) {
3467 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
3469 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
3470 textFragmentData
.GetPreviousEditableCharPoint(
3471 atPreviousCharOfEndOfVisibleWhiteSpaces
);
3472 isPreviousCharCollapsibleASCIIWhiteSpace
=
3473 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
3474 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3475 .IsEndOfContainer() &&
3476 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3477 .IsCharCollapsibleASCIISpace();
3478 followedByBRElement
= true;
3479 followedByVisibleContent
= followedByPreformattedLineBreak
= false;
3483 // Once insert a <br>, the remaining work is only for normalizing
3484 // white-space sequence in white-space collapsible text node.
3485 // So, if the the text node's white-spaces are preformatted, we need
3486 // to do nothing anymore.
3487 if (EditorUtils::IsWhiteSpacePreformatted(
3488 *atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAs
<Text
>())) {
3492 // Next, replace the NBSP with an ASCII white-space if it's surrounded
3493 // by visible contents (or immediately before a <br> element).
3494 // However, if it follows or is followed by a preformatted linefeed,
3495 // we shouldn't do this because an ASCII white-space will be collapsed
3496 // **into** the linefeed.
3497 if (maybeNBSPFollowsVisibleContent
&&
3498 (followedByVisibleContent
|| followedByBRElement
) &&
3499 !visibleWhiteSpaces
.StartsFromPreformattedLineBreak()) {
3500 MOZ_ASSERT(!followedByPreformattedLineBreak
);
3501 Result
<InsertTextResult
, nsresult
> replaceTextResult
=
3502 aHTMLEditor
.ReplaceTextWithTransaction(
3503 MOZ_KnownLive(*atPreviousCharOfEndOfVisibleWhiteSpaces
3504 .ContainerAs
<Text
>()),
3505 atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset(), 1, u
" "_ns
);
3506 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
3507 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3508 return replaceTextResult
.propagateErr();
3510 // Ignore caret suggestion because the caller must want to restore
3511 // `Selection` due to the purpose of this method.
3512 replaceTextResult
.unwrap().IgnoreCaretPointSuggestion();
3516 // If the text node is not preformatted, and the NBSP is followed by a <br>
3517 // element and following (maybe multiple) collapsible ASCII white-spaces,
3518 // remove the NBSP, but inserts a NBSP before the spaces. This makes a line
3519 // break opportunity to wrap the line.
3520 // XXX This is different behavior from Blink. Blink generates pairs of
3521 // an NBSP and an ASCII white-space, but put NBSP at the end of the
3522 // sequence. We should follow the behavior for web-compat.
3523 if (maybeNBSPFollowsVisibleContent
||
3524 !isPreviousCharCollapsibleASCIIWhiteSpace
||
3525 !(followedByVisibleContent
|| followedByBRElement
) ||
3526 EditorUtils::IsWhiteSpacePreformatted(
3527 *atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3528 .ContainerAs
<Text
>())) {
3532 // Currently, we're at an NBSP following an ASCII space, and we need to
3533 // replace them with `" "` for avoiding collapsing white-spaces.
3534 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3535 .IsEndOfContainer());
3536 const EditorDOMPointInText atFirstASCIIWhiteSpace
=
3537 textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3538 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
,
3540 uint32_t numberOfASCIIWhiteSpacesInStartNode
=
3541 atFirstASCIIWhiteSpace
.ContainerAs
<Text
>() ==
3542 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAs
<Text
>()
3543 ? atPreviousCharOfEndOfVisibleWhiteSpaces
.Offset() -
3544 atFirstASCIIWhiteSpace
.Offset()
3545 : atFirstASCIIWhiteSpace
.ContainerAs
<Text
>()->Length() -
3546 atFirstASCIIWhiteSpace
.Offset();
3547 // Replace all preceding ASCII white-spaces **and** the NBSP.
3548 uint32_t replaceLengthInStartNode
=
3549 numberOfASCIIWhiteSpacesInStartNode
+
3550 (atFirstASCIIWhiteSpace
.ContainerAs
<Text
>() ==
3551 atPreviousCharOfEndOfVisibleWhiteSpaces
.ContainerAs
<Text
>()
3554 Result
<InsertTextResult
, nsresult
> replaceTextResult
=
3555 aHTMLEditor
.ReplaceTextWithTransaction(
3556 MOZ_KnownLive(*atFirstASCIIWhiteSpace
.ContainerAs
<Text
>()),
3557 atFirstASCIIWhiteSpace
.Offset(), replaceLengthInStartNode
,
3558 textFragmentData
.StartsFromPreformattedLineBreak() &&
3559 textFragmentData
.EndsByPreformattedLineBreak()
3560 ? u
"\x00A0\x00A0"_ns
3561 : (textFragmentData
.EndsByPreformattedLineBreak()
3564 if (MOZ_UNLIKELY(replaceTextResult
.isErr())) {
3565 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
3566 return replaceTextResult
.propagateErr();
3568 // Ignore caret suggestion because the caller must want to restore
3569 // `Selection` due to the purpose of this method.
3570 replaceTextResult
.unwrap().IgnoreCaretPointSuggestion();
3572 if (atFirstASCIIWhiteSpace
.GetContainer() ==
3573 atPreviousCharOfEndOfVisibleWhiteSpaces
.GetContainer()) {
3577 // We need to remove the following unnecessary ASCII white-spaces and
3578 // NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
3579 // into the start node.
3580 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3581 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3582 EditorDOMPointInText::AtEndOf(
3583 *atFirstASCIIWhiteSpace
.ContainerAs
<Text
>()),
3584 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint(),
3585 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3586 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3587 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3588 return caretPointOrError
.propagateErr();
3590 // Ignore caret suggestion because the caller must want to restore
3591 // `Selection` due to the purpose of this method. }
3592 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
3596 // XXX This is called when top-level edit sub-action handling ends for
3597 // 3 points at most. However, this is not compatible with Blink.
3598 // Blink touches white-space sequence which includes new character
3599 // or following white-space sequence of new <br> element or, if and
3600 // only if deleting range is followed by white-space sequence (i.e.,
3601 // not touched previous white-space sequence of deleting range).
3602 // This should be done when we change to make each edit action
3603 // handler directly normalize white-space sequence rather than
3604 // OnEndHandlingTopLevelEditSucAction().
3606 // First, check if the last character is an NBSP. Otherwise, we don't need
3607 // to do nothing here.
3608 const EditorDOMPoint
& atEndOfVisibleWhiteSpaces
= visibleWhiteSpaces
.EndRef();
3609 const EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces
=
3610 textFragmentData
.GetPreviousEditableCharPoint(atEndOfVisibleWhiteSpaces
);
3611 if (!atPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() ||
3612 atPreviousCharOfEndOfVisibleWhiteSpaces
.IsEndOfContainer() ||
3613 !atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharCollapsibleNBSP() ||
3614 // If the next character of the NBSP is a preformatted linefeed, we
3615 // shouldn't replace it with an ASCII white-space for avoiding collapsed
3616 // into the linefeed.
3617 visibleWhiteSpaces
.EndsByPreformattedLineBreak()) {
3621 // Next, consider the range to collapse ASCII white-spaces before there.
3622 EditorDOMPointInText startToDelete
, endToDelete
;
3624 const EditorDOMPointInText
3625 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
=
3626 textFragmentData
.GetPreviousEditableCharPoint(
3627 atPreviousCharOfEndOfVisibleWhiteSpaces
);
3628 // If there are some preceding ASCII white-spaces, we need to treat them
3629 // as one white-space. I.e., we need to collapse them.
3630 if (atPreviousCharOfEndOfVisibleWhiteSpaces
.IsCharNBSP() &&
3631 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
.IsSet() &&
3632 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3633 .IsCharCollapsibleASCIISpace()) {
3634 startToDelete
= textFragmentData
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3635 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
,
3637 endToDelete
= atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
;
3639 // Otherwise, we don't need to remove any white-spaces, but we may need
3640 // to normalize the white-space sequence containing the previous NBSP.
3642 startToDelete
= endToDelete
=
3643 atPreviousCharOfEndOfVisibleWhiteSpaces
.NextPoint();
3646 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3647 aHTMLEditor
.DeleteTextAndNormalizeSurroundingWhiteSpaces(
3648 startToDelete
, endToDelete
,
3649 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
,
3650 HTMLEditor::DeleteDirection::Forward
);
3651 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3653 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpace() failed");
3654 return caretPointOrError
.unwrapErr();
3656 // Ignore caret suggestion because the caller must want to restore
3657 // `Selection` due to the purpose of this method.
3658 caretPointOrError
.unwrap().IgnoreCaretPointSuggestion();
3662 EditorDOMPointInText
WSRunScanner::TextFragmentData::
3663 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
3664 const EditorDOMPoint
& aPointToInsert
) const {
3665 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
3666 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
3667 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3668 PointPosition::MiddleOfFragment
||
3669 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3670 PointPosition::EndOfFragment
,
3671 "Previous char of aPoint should be in the visible white-spaces");
3673 // Try to change an NBSP to a space, if possible, just to prevent NBSP
3674 // proliferation. This routine is called when we are about to make this
3675 // point in the ws abut an inserted break or text, so we don't have to worry
3676 // about what is after it. What is after it now will end up after the
3678 const EditorDOMPointInText atPreviousChar
=
3679 GetPreviousEditableCharPoint(aPointToInsert
);
3680 if (!atPreviousChar
.IsSet() || atPreviousChar
.IsEndOfContainer() ||
3681 !atPreviousChar
.IsCharNBSP() ||
3682 EditorUtils::IsWhiteSpacePreformatted(
3683 *atPreviousChar
.ContainerAs
<Text
>())) {
3684 return EditorDOMPointInText();
3687 const EditorDOMPointInText atPreviousCharOfPreviousChar
=
3688 GetPreviousEditableCharPoint(atPreviousChar
);
3689 if (atPreviousCharOfPreviousChar
.IsSet()) {
3690 // If the previous char is in different text node and it's preformatted,
3691 // we shouldn't touch it.
3692 if (atPreviousChar
.ContainerAs
<Text
>() !=
3693 atPreviousCharOfPreviousChar
.ContainerAs
<Text
>() &&
3694 EditorUtils::IsWhiteSpacePreformatted(
3695 *atPreviousCharOfPreviousChar
.ContainerAs
<Text
>())) {
3696 return EditorDOMPointInText();
3698 // If the previous char of the NBSP at previous position of aPointToInsert
3699 // is an ASCII white-space, we don't need to replace it with same character.
3700 if (!atPreviousCharOfPreviousChar
.IsEndOfContainer() &&
3701 atPreviousCharOfPreviousChar
.IsCharASCIISpace()) {
3702 return EditorDOMPointInText();
3704 return atPreviousChar
;
3707 // If previous content of the NBSP is block boundary, we cannot replace the
3708 // NBSP with an ASCII white-space to keep it rendered.
3709 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3710 VisibleWhiteSpacesDataRef();
3711 if (!visibleWhiteSpaces
.StartsFromNonCollapsibleCharacters() &&
3712 !visibleWhiteSpaces
.StartsFromSpecialContent()) {
3713 return EditorDOMPointInText();
3715 return atPreviousChar
;
3718 EditorDOMPointInText
WSRunScanner::TextFragmentData::
3719 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
3720 const EditorDOMPoint
& aPointToInsert
) const {
3721 MOZ_ASSERT(aPointToInsert
.IsSetAndValid());
3722 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
3723 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3724 PointPosition::StartOfFragment
||
3725 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert
) ==
3726 PointPosition::MiddleOfFragment
,
3727 "Inclusive next char of aPointToInsert should be in the visible "
3730 // Try to change an nbsp to a space, if possible, just to prevent nbsp
3731 // proliferation This routine is called when we are about to make this point
3732 // in the ws abut an inserted text, so we don't have to worry about what is
3733 // before it. What is before it now will end up before the inserted text.
3735 GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(aPointToInsert
);
3736 if (!atNextChar
.IsSet() || NS_WARN_IF(atNextChar
.IsEndOfContainer()) ||
3737 !atNextChar
.IsCharNBSP() ||
3738 EditorUtils::IsWhiteSpacePreformatted(*atNextChar
.ContainerAs
<Text
>())) {
3739 return EditorDOMPointInText();
3742 const auto atNextCharOfNextCharOfNBSP
=
3743 GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
3744 atNextChar
.NextPoint
<EditorRawDOMPointInText
>());
3745 if (atNextCharOfNextCharOfNBSP
.IsSet()) {
3746 // If the next char is in different text node and it's preformatted,
3747 // we shouldn't touch it.
3748 if (atNextChar
.ContainerAs
<Text
>() !=
3749 atNextCharOfNextCharOfNBSP
.ContainerAs
<Text
>() &&
3750 EditorUtils::IsWhiteSpacePreformatted(
3751 *atNextCharOfNextCharOfNBSP
.ContainerAs
<Text
>())) {
3752 return EditorDOMPointInText();
3754 // If following character of an NBSP is an ASCII white-space, we don't
3755 // need to replace it with same character.
3756 if (!atNextCharOfNextCharOfNBSP
.IsEndOfContainer() &&
3757 atNextCharOfNextCharOfNBSP
.IsCharASCIISpace()) {
3758 return EditorDOMPointInText();
3763 // If the NBSP is last character in the hard line, we don't need to
3764 // replace it because it's required to render multiple white-spaces.
3765 const VisibleWhiteSpacesData
& visibleWhiteSpaces
=
3766 VisibleWhiteSpacesDataRef();
3767 if (!visibleWhiteSpaces
.EndsByNonCollapsibleCharacters() &&
3768 !visibleWhiteSpaces
.EndsBySpecialContent() &&
3769 !visibleWhiteSpaces
.EndsByBRElement()) {
3770 return EditorDOMPointInText();
3777 Result
<CaretPoint
, nsresult
>
3778 WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
3779 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aPoint
) {
3780 MOZ_ASSERT(aPoint
.IsSet());
3781 Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
3782 TextFragmentData
textFragmentData(aPoint
, editingHost
,
3783 BlockInlineCheck::UseComputedDisplayStyle
);
3784 if (NS_WARN_IF(!textFragmentData
.IsInitialized())) {
3785 return Err(NS_ERROR_FAILURE
);
3787 const EditorDOMRange
& leadingWhiteSpaceRange
=
3788 textFragmentData
.InvisibleLeadingWhiteSpaceRangeRef();
3789 // XXX Getting trailing white-space range now must be wrong because
3790 // mutation event listener may invalidate it.
3791 const EditorDOMRange
& trailingWhiteSpaceRange
=
3792 textFragmentData
.InvisibleTrailingWhiteSpaceRangeRef();
3793 EditorDOMPoint pointToPutCaret
;
3794 DebugOnly
<bool> leadingWhiteSpacesDeleted
= false;
3795 if (leadingWhiteSpaceRange
.IsPositioned() &&
3796 !leadingWhiteSpaceRange
.Collapsed()) {
3797 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3798 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3799 leadingWhiteSpaceRange
.StartRef(), leadingWhiteSpaceRange
.EndRef(),
3800 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3801 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3802 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3803 return caretPointOrError
;
3805 caretPointOrError
.unwrap().MoveCaretPointTo(
3806 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
3807 leadingWhiteSpacesDeleted
= true;
3809 if (trailingWhiteSpaceRange
.IsPositioned() &&
3810 !trailingWhiteSpaceRange
.Collapsed() &&
3811 leadingWhiteSpaceRange
!= trailingWhiteSpaceRange
) {
3812 NS_ASSERTION(!leadingWhiteSpacesDeleted
,
3813 "We're trying to remove trailing white-spaces with maybe "
3815 AutoTrackDOMPoint
trackPointToPutCaret(aHTMLEditor
.RangeUpdaterRef(),
3817 Result
<CaretPoint
, nsresult
> caretPointOrError
=
3818 aHTMLEditor
.DeleteTextAndTextNodesWithTransaction(
3819 trailingWhiteSpaceRange
.StartRef(),
3820 trailingWhiteSpaceRange
.EndRef(),
3821 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries
);
3822 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
3823 NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
3824 return caretPointOrError
.propagateErr();
3826 trackPointToPutCaret
.FlushAndStopTracking();
3827 caretPointOrError
.unwrap().MoveCaretPointTo(
3828 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
3830 return CaretPoint(std::move(pointToPutCaret
));
3833 /*****************************************************************************
3834 * Implementation for new white-space normalizer
3835 *****************************************************************************/
3838 EditorDOMRangeInTexts
3839 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3840 const TextFragmentData
& aStart
, const TextFragmentData
& aEnd
) {
3841 // Corresponding to handling invisible white-spaces part of
3842 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
3843 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
3845 MOZ_ASSERT(aStart
.ScanStartRef().IsSetAndValid());
3846 MOZ_ASSERT(aEnd
.ScanStartRef().IsSetAndValid());
3847 MOZ_ASSERT(aStart
.ScanStartRef().EqualsOrIsBefore(aEnd
.ScanStartRef()));
3848 MOZ_ASSERT(aStart
.ScanStartRef().IsInTextNode());
3849 MOZ_ASSERT(aEnd
.ScanStartRef().IsInTextNode());
3851 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
3852 // `GetReplaceRangeDataAtStartOfDeletionRange()` use
3853 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
3854 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
3855 // However, they are really odd as mentioned with "XXX" comments
3856 // in them. For the new white-space normalizer, we need to treat
3857 // invisible white-spaces stricter because the legacy path handles
3858 // white-spaces multiple times (e.g., calling `HTMLEditor::
3859 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
3860 // the bug, but in the new path, we should stop doing same things
3861 // multiple times for both performance and footprint. Therefore,
3862 // even though the result might be different in some edge cases,
3863 // we should use clean path for now. Perhaps, we should fix the odd
3864 // cases before shipping `beforeinput` event in release channel.
3866 const EditorDOMRange
& invisibleLeadingWhiteSpaceRange
=
3867 aStart
.InvisibleLeadingWhiteSpaceRangeRef();
3868 const EditorDOMRange
& invisibleTrailingWhiteSpaceRange
=
3869 aEnd
.InvisibleTrailingWhiteSpaceRangeRef();
3870 const bool hasInvisibleLeadingWhiteSpaces
=
3871 invisibleLeadingWhiteSpaceRange
.IsPositioned() &&
3872 !invisibleLeadingWhiteSpaceRange
.Collapsed();
3873 const bool hasInvisibleTrailingWhiteSpaces
=
3874 invisibleLeadingWhiteSpaceRange
!= invisibleTrailingWhiteSpaceRange
&&
3875 invisibleTrailingWhiteSpaceRange
.IsPositioned() &&
3876 !invisibleTrailingWhiteSpaceRange
.Collapsed();
3878 EditorDOMRangeInTexts
result(aStart
.ScanStartRef().AsInText(),
3879 aEnd
.ScanStartRef().AsInText());
3880 MOZ_ASSERT(result
.IsPositionedAndValid());
3881 if (!hasInvisibleLeadingWhiteSpaces
&& !hasInvisibleTrailingWhiteSpaces
) {
3886 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3887 invisibleLeadingWhiteSpaceRange
.StartRef().IsBefore(
3888 invisibleTrailingWhiteSpaceRange
.StartRef()));
3889 const EditorDOMPoint
& aroundFirstInvisibleWhiteSpace
=
3890 hasInvisibleLeadingWhiteSpaces
3891 ? invisibleLeadingWhiteSpaceRange
.StartRef()
3892 : invisibleTrailingWhiteSpaceRange
.StartRef();
3893 if (aroundFirstInvisibleWhiteSpace
.IsBefore(result
.StartRef())) {
3894 if (aroundFirstInvisibleWhiteSpace
.IsInTextNode()) {
3895 result
.SetStart(aroundFirstInvisibleWhiteSpace
.AsInText());
3896 MOZ_ASSERT(result
.IsPositionedAndValid());
3898 const auto atFirstInvisibleWhiteSpace
=
3899 hasInvisibleLeadingWhiteSpaces
3900 ? aStart
.GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
3901 aroundFirstInvisibleWhiteSpace
)
3902 : aEnd
.GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(
3903 aroundFirstInvisibleWhiteSpace
);
3904 MOZ_ASSERT(atFirstInvisibleWhiteSpace
.IsSet());
3906 atFirstInvisibleWhiteSpace
.EqualsOrIsBefore(result
.StartRef()));
3907 result
.SetStart(atFirstInvisibleWhiteSpace
);
3908 MOZ_ASSERT(result
.IsPositionedAndValid());
3912 hasInvisibleLeadingWhiteSpaces
&& hasInvisibleTrailingWhiteSpaces
,
3913 invisibleLeadingWhiteSpaceRange
.EndRef().IsBefore(
3914 invisibleTrailingWhiteSpaceRange
.EndRef()));
3915 const EditorDOMPoint
& afterLastInvisibleWhiteSpace
=
3916 hasInvisibleTrailingWhiteSpaces
3917 ? invisibleTrailingWhiteSpaceRange
.EndRef()
3918 : invisibleLeadingWhiteSpaceRange
.EndRef();
3919 if (afterLastInvisibleWhiteSpace
.EqualsOrIsBefore(result
.EndRef())) {
3920 MOZ_ASSERT(result
.IsPositionedAndValid());
3923 if (afterLastInvisibleWhiteSpace
.IsInTextNode()) {
3924 result
.SetEnd(afterLastInvisibleWhiteSpace
.AsInText());
3925 MOZ_ASSERT(result
.IsPositionedAndValid());
3928 const auto atLastInvisibleWhiteSpace
=
3929 hasInvisibleTrailingWhiteSpaces
3930 ? aEnd
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
)
3931 : aStart
.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace
);
3932 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsSet());
3933 MOZ_ASSERT(atLastInvisibleWhiteSpace
.IsContainerEmpty() ||
3934 atLastInvisibleWhiteSpace
.IsAtLastContent());
3935 MOZ_ASSERT(result
.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace
));
3936 result
.SetEnd(atLastInvisibleWhiteSpace
.IsEndOfContainer()
3937 ? atLastInvisibleWhiteSpace
3938 : atLastInvisibleWhiteSpace
.NextPoint());
3939 MOZ_ASSERT(result
.IsPositionedAndValid());
3944 Result
<EditorDOMRangeInTexts
, nsresult
>
3945 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(const EditorDOMPoint
& aPoint
,
3946 const Element
& aEditingHost
) {
3947 // Corresponding to computing delete range part of
3948 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
3949 MOZ_ASSERT(aPoint
.IsSetAndValid());
3951 TextFragmentData
textFragmentDataAtCaret(
3952 aPoint
, &aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
3953 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
3954 return Err(NS_ERROR_FAILURE
);
3956 EditorDOMPointInText atPreviousChar
=
3957 textFragmentDataAtCaret
.GetPreviousEditableCharPoint(aPoint
);
3958 if (!atPreviousChar
.IsSet()) {
3959 return EditorDOMRangeInTexts(); // There is no content in the block.
3962 // XXX When previous char point is in an empty text node, we do nothing,
3963 // but this must look odd from point of user view. We should delete
3964 // something before aPoint.
3965 if (atPreviousChar
.IsEndOfContainer()) {
3966 return EditorDOMRangeInTexts();
3969 // Extend delete range if previous char is a low surrogate following
3970 // a high surrogate.
3971 EditorDOMPointInText atNextChar
= atPreviousChar
.NextPoint();
3972 if (!atPreviousChar
.IsStartOfContainer()) {
3973 if (atPreviousChar
.IsCharLowSurrogateFollowingHighSurrogate()) {
3974 atPreviousChar
= atPreviousChar
.PreviousPoint();
3976 // If caret is in middle of a surrogate pair, delete the surrogate pair
3978 else if (atPreviousChar
.IsCharHighSurrogateFollowedByLowSurrogate()) {
3979 atNextChar
= atNextChar
.NextPoint();
3983 // If previous char is an collapsible white-spaces, delete all adjcent
3984 // white-spaces which are collapsed together.
3985 EditorDOMRangeInTexts rangeToDelete
;
3986 if (atPreviousChar
.IsCharCollapsibleASCIISpace() ||
3987 atPreviousChar
.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
3988 const EditorDOMPointInText startToDelete
=
3989 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
3990 atPreviousChar
, nsIEditor::ePrevious
);
3991 if (!startToDelete
.IsSet()) {
3993 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3994 return Err(NS_ERROR_FAILURE
);
3996 const EditorDOMPointInText endToDelete
=
3997 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(
3998 atPreviousChar
, nsIEditor::ePrevious
);
3999 if (!endToDelete
.IsSet()) {
4000 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
4001 return Err(NS_ERROR_FAILURE
);
4003 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
4005 // if previous char is not a collapsible white-space, remove it.
4007 rangeToDelete
= EditorDOMRangeInTexts(atPreviousChar
, atNextChar
);
4010 // If there is no removable and visible content, we should do nothing.
4011 if (rangeToDelete
.Collapsed()) {
4012 return EditorDOMRangeInTexts();
4015 // And also delete invisible white-spaces if they become visible.
4016 TextFragmentData textFragmentDataAtStart
=
4017 rangeToDelete
.StartRef() != aPoint
4018 ? TextFragmentData(rangeToDelete
.StartRef(), &aEditingHost
,
4019 BlockInlineCheck::UseComputedDisplayStyle
)
4020 : textFragmentDataAtCaret
;
4021 TextFragmentData textFragmentDataAtEnd
=
4022 rangeToDelete
.EndRef() != aPoint
4023 ? TextFragmentData(rangeToDelete
.EndRef(), &aEditingHost
,
4024 BlockInlineCheck::UseComputedDisplayStyle
)
4025 : textFragmentDataAtCaret
;
4026 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
4027 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
4028 return Err(NS_ERROR_FAILURE
);
4030 EditorDOMRangeInTexts extendedRangeToDelete
=
4031 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
4032 textFragmentDataAtStart
, textFragmentDataAtEnd
);
4033 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
4034 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
4039 Result
<EditorDOMRangeInTexts
, nsresult
>
4040 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
4041 const EditorDOMPoint
& aPoint
, const Element
& aEditingHost
) {
4042 // Corresponding to computing delete range part of
4043 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
4044 MOZ_ASSERT(aPoint
.IsSetAndValid());
4046 TextFragmentData
textFragmentDataAtCaret(
4047 aPoint
, &aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
4048 if (NS_WARN_IF(!textFragmentDataAtCaret
.IsInitialized())) {
4049 return Err(NS_ERROR_FAILURE
);
4052 textFragmentDataAtCaret
4053 .GetInclusiveNextEditableCharPoint
<EditorDOMPointInText
>(aPoint
);
4054 if (!atCaret
.IsSet()) {
4055 return EditorDOMRangeInTexts(); // There is no content in the block.
4057 // If caret is in middle of a surrogate pair, we should remove next
4058 // character (blink-compat).
4059 if (!atCaret
.IsEndOfContainer() &&
4060 atCaret
.IsCharLowSurrogateFollowingHighSurrogate()) {
4061 atCaret
= atCaret
.NextPoint();
4064 // XXX When next char point is in an empty text node, we do nothing,
4065 // but this must look odd from point of user view. We should delete
4066 // something after aPoint.
4067 if (atCaret
.IsEndOfContainer()) {
4068 return EditorDOMRangeInTexts();
4071 // Extend delete range if previous char is a low surrogate following
4072 // a high surrogate.
4073 EditorDOMPointInText atNextChar
= atCaret
.NextPoint();
4074 if (atCaret
.IsCharHighSurrogateFollowedByLowSurrogate()) {
4075 atNextChar
= atNextChar
.NextPoint();
4078 // If next char is a collapsible white-space, delete all adjcent white-spaces
4079 // which are collapsed together.
4080 EditorDOMRangeInTexts rangeToDelete
;
4081 if (atCaret
.IsCharCollapsibleASCIISpace() ||
4082 atCaret
.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) {
4083 const EditorDOMPointInText startToDelete
=
4084 textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo(
4085 atCaret
, nsIEditor::eNext
);
4086 if (!startToDelete
.IsSet()) {
4088 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
4089 return Err(NS_ERROR_FAILURE
);
4091 const EditorDOMPointInText endToDelete
=
4092 textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces(
4093 atCaret
, nsIEditor::eNext
);
4094 if (!endToDelete
.IsSet()) {
4095 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
4096 return Err(NS_ERROR_FAILURE
);
4098 rangeToDelete
= EditorDOMRangeInTexts(startToDelete
, endToDelete
);
4100 // if next char is not a collapsible white-space, remove it.
4102 rangeToDelete
= EditorDOMRangeInTexts(atCaret
, atNextChar
);
4105 // If there is no removable and visible content, we should do nothing.
4106 if (rangeToDelete
.Collapsed()) {
4107 return EditorDOMRangeInTexts();
4110 // And also delete invisible white-spaces if they become visible.
4111 TextFragmentData textFragmentDataAtStart
=
4112 rangeToDelete
.StartRef() != aPoint
4113 ? TextFragmentData(rangeToDelete
.StartRef(), &aEditingHost
,
4114 BlockInlineCheck::UseComputedDisplayStyle
)
4115 : textFragmentDataAtCaret
;
4116 TextFragmentData textFragmentDataAtEnd
=
4117 rangeToDelete
.EndRef() != aPoint
4118 ? TextFragmentData(rangeToDelete
.EndRef(), &aEditingHost
,
4119 BlockInlineCheck::UseComputedDisplayStyle
)
4120 : textFragmentDataAtCaret
;
4121 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized()) ||
4122 NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
4123 return Err(NS_ERROR_FAILURE
);
4125 EditorDOMRangeInTexts extendedRangeToDelete
=
4126 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
4127 textFragmentDataAtStart
, textFragmentDataAtEnd
);
4128 MOZ_ASSERT(extendedRangeToDelete
.IsPositionedAndValid());
4129 return extendedRangeToDelete
.IsPositioned() ? extendedRangeToDelete
4134 EditorDOMRange
WSRunScanner::GetRangesForDeletingAtomicContent(
4135 Element
* aEditingHost
, const nsIContent
& aAtomicContent
) {
4136 if (aAtomicContent
.IsHTMLElement(nsGkAtoms::br
)) {
4137 // Preceding white-spaces should be preserved, but the following
4138 // white-spaces should be invisible around `<br>` element.
4139 TextFragmentData
textFragmentDataAfterBRElement(
4140 EditorDOMPoint::After(aAtomicContent
), aEditingHost
,
4141 BlockInlineCheck::UseComputedDisplayStyle
);
4142 if (NS_WARN_IF(!textFragmentDataAfterBRElement
.IsInitialized())) {
4143 return EditorDOMRange(); // TODO: Make here return error with Err.
4145 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
4146 textFragmentDataAfterBRElement
.GetNonCollapsedRangeInTexts(
4147 textFragmentDataAfterBRElement
4148 .InvisibleLeadingWhiteSpaceRangeRef());
4149 return followingInvisibleWhiteSpaces
.IsPositioned() &&
4150 !followingInvisibleWhiteSpaces
.Collapsed()
4152 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
4153 followingInvisibleWhiteSpaces
.EndRef())
4155 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
4156 EditorDOMPoint::After(aAtomicContent
));
4159 if (!HTMLEditUtils::IsBlockElement(
4160 aAtomicContent
, BlockInlineCheck::UseComputedDisplayStyle
)) {
4161 // Both preceding and following white-spaces around it should be preserved
4162 // around inline elements like `<img>`.
4163 return EditorDOMRange(
4164 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
4165 EditorDOMPoint::After(aAtomicContent
));
4168 // Both preceding and following white-spaces can be invisible around a
4170 TextFragmentData
textFragmentDataBeforeAtomicContent(
4171 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)), aEditingHost
,
4172 BlockInlineCheck::UseComputedDisplayStyle
);
4173 if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent
.IsInitialized())) {
4174 return EditorDOMRange(); // TODO: Make here return error with Err.
4176 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces
=
4177 textFragmentDataBeforeAtomicContent
.GetNonCollapsedRangeInTexts(
4178 textFragmentDataBeforeAtomicContent
4179 .InvisibleTrailingWhiteSpaceRangeRef());
4180 TextFragmentData
textFragmentDataAfterAtomicContent(
4181 EditorDOMPoint::After(aAtomicContent
), aEditingHost
,
4182 BlockInlineCheck::UseComputedDisplayStyle
);
4183 if (NS_WARN_IF(!textFragmentDataAfterAtomicContent
.IsInitialized())) {
4184 return EditorDOMRange(); // TODO: Make here return error with Err.
4186 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces
=
4187 textFragmentDataAfterAtomicContent
.GetNonCollapsedRangeInTexts(
4188 textFragmentDataAfterAtomicContent
4189 .InvisibleLeadingWhiteSpaceRangeRef());
4190 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet() &&
4191 followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
4192 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
4193 followingInvisibleWhiteSpaces
.EndRef());
4195 if (precedingInvisibleWhiteSpaces
.StartRef().IsSet()) {
4196 return EditorDOMRange(precedingInvisibleWhiteSpaces
.StartRef(),
4197 EditorDOMPoint::After(aAtomicContent
));
4199 if (followingInvisibleWhiteSpaces
.EndRef().IsSet()) {
4200 return EditorDOMRange(
4201 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
4202 followingInvisibleWhiteSpaces
.EndRef());
4204 return EditorDOMRange(
4205 EditorDOMPoint(const_cast<nsIContent
*>(&aAtomicContent
)),
4206 EditorDOMPoint::After(aAtomicContent
));
4210 EditorDOMRange
WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
4211 const HTMLEditor
& aHTMLEditor
, const Element
& aLeftBlockElement
,
4212 const Element
& aRightBlockElement
,
4213 const EditorDOMPoint
& aPointContainingTheOtherBlock
) {
4214 MOZ_ASSERT(&aLeftBlockElement
!= &aRightBlockElement
);
4216 aPointContainingTheOtherBlock
.IsSet(),
4217 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
||
4218 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
);
4220 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
,
4221 aRightBlockElement
.IsInclusiveDescendantOf(
4222 aPointContainingTheOtherBlock
.GetChild()));
4224 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
,
4225 aLeftBlockElement
.IsInclusiveDescendantOf(
4226 aPointContainingTheOtherBlock
.GetChild()));
4228 !aPointContainingTheOtherBlock
.IsSet(),
4229 !aRightBlockElement
.IsInclusiveDescendantOf(&aLeftBlockElement
));
4231 !aPointContainingTheOtherBlock
.IsSet(),
4232 !aLeftBlockElement
.IsInclusiveDescendantOf(&aRightBlockElement
));
4233 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock
.IsSet(),
4234 EditorRawDOMPoint(const_cast<Element
*>(&aLeftBlockElement
))
4235 .IsBefore(EditorRawDOMPoint(
4236 const_cast<Element
*>(&aRightBlockElement
))));
4238 const Element
* editingHost
= aHTMLEditor
.ComputeEditingHost();
4240 EditorDOMRange range
;
4241 // Include trailing invisible white-spaces in aLeftBlockElement.
4242 TextFragmentData
textFragmentDataAtEndOfLeftBlockElement(
4243 aPointContainingTheOtherBlock
.GetContainer() == &aLeftBlockElement
4244 ? aPointContainingTheOtherBlock
4245 : EditorDOMPoint::AtEndOf(const_cast<Element
&>(aLeftBlockElement
)),
4246 editingHost
, BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4247 if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement
.IsInitialized())) {
4248 return EditorDOMRange(); // TODO: Make here return error with Err.
4250 if (textFragmentDataAtEndOfLeftBlockElement
.StartsFromInvisibleBRElement()) {
4251 // If the left block element ends with an invisible `<br>` element,
4252 // it'll be deleted (and it means there is no invisible trailing
4253 // white-spaces). Therefore, the range should start from the invisible
4255 range
.SetStart(EditorDOMPoint(
4256 textFragmentDataAtEndOfLeftBlockElement
.StartReasonBRElementPtr()));
4258 const EditorDOMRange
& trailingWhiteSpaceRange
=
4259 textFragmentDataAtEndOfLeftBlockElement
4260 .InvisibleTrailingWhiteSpaceRangeRef();
4261 if (trailingWhiteSpaceRange
.StartRef().IsSet()) {
4262 range
.SetStart(trailingWhiteSpaceRange
.StartRef());
4264 range
.SetStart(textFragmentDataAtEndOfLeftBlockElement
.ScanStartRef());
4267 // Include leading invisible white-spaces in aRightBlockElement.
4268 TextFragmentData
textFragmentDataAtStartOfRightBlockElement(
4269 aPointContainingTheOtherBlock
.GetContainer() == &aRightBlockElement
&&
4270 !aPointContainingTheOtherBlock
.IsEndOfContainer()
4271 ? aPointContainingTheOtherBlock
.NextPoint()
4272 : EditorDOMPoint(const_cast<Element
*>(&aRightBlockElement
), 0u),
4273 editingHost
, BlockInlineCheck::UseComputedDisplayOutsideStyle
);
4274 if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement
.IsInitialized())) {
4275 return EditorDOMRange(); // TODO: Make here return error with Err.
4277 const EditorDOMRange
& leadingWhiteSpaceRange
=
4278 textFragmentDataAtStartOfRightBlockElement
4279 .InvisibleLeadingWhiteSpaceRangeRef();
4280 if (leadingWhiteSpaceRange
.EndRef().IsSet()) {
4281 range
.SetEnd(leadingWhiteSpaceRange
.EndRef());
4283 range
.SetEnd(textFragmentDataAtStartOfRightBlockElement
.ScanStartRef());
4290 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
4291 Element
* aEditingHost
, const EditorDOMRange
& aRange
) {
4292 MOZ_ASSERT(aRange
.IsPositionedAndValid());
4293 MOZ_ASSERT(aRange
.EndRef().IsSetAndValid());
4294 MOZ_ASSERT(aRange
.StartRef().IsSetAndValid());
4296 EditorDOMRange result
;
4297 TextFragmentData
textFragmentDataAtStart(
4298 aRange
.StartRef(), aEditingHost
,
4299 BlockInlineCheck::UseComputedDisplayStyle
);
4300 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
4301 return EditorDOMRange(); // TODO: Make here return error with Err.
4303 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart
=
4304 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
4305 textFragmentDataAtStart
.InvisibleLeadingWhiteSpaceRangeRef());
4306 if (invisibleLeadingWhiteSpacesAtStart
.IsPositioned() &&
4307 !invisibleLeadingWhiteSpacesAtStart
.Collapsed()) {
4308 result
.SetStart(invisibleLeadingWhiteSpacesAtStart
.StartRef());
4310 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart
=
4311 textFragmentDataAtStart
.GetNonCollapsedRangeInTexts(
4312 textFragmentDataAtStart
.InvisibleTrailingWhiteSpaceRangeRef());
4313 if (invisibleTrailingWhiteSpacesAtStart
.IsPositioned() &&
4314 !invisibleTrailingWhiteSpacesAtStart
.Collapsed()) {
4316 invisibleTrailingWhiteSpacesAtStart
.StartRef().EqualsOrIsBefore(
4317 aRange
.StartRef()));
4318 result
.SetStart(invisibleTrailingWhiteSpacesAtStart
.StartRef());
4320 // If there is no invisible white-space and the line starts with a
4321 // text node, shrink the range to start of the text node.
4322 else if (!aRange
.StartRef().IsInTextNode() &&
4323 textFragmentDataAtStart
.StartsFromBlockBoundary() &&
4324 textFragmentDataAtStart
.EndRef().IsInTextNode()) {
4325 result
.SetStart(textFragmentDataAtStart
.EndRef());
4328 if (!result
.StartRef().IsSet()) {
4329 result
.SetStart(aRange
.StartRef());
4332 TextFragmentData
textFragmentDataAtEnd(
4333 aRange
.EndRef(), aEditingHost
, BlockInlineCheck::UseComputedDisplayStyle
);
4334 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
4335 return EditorDOMRange(); // TODO: Make here return error with Err.
4337 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
4338 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
4339 textFragmentDataAtEnd
.InvisibleTrailingWhiteSpaceRangeRef());
4340 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
4341 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
4342 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
4344 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd
=
4345 textFragmentDataAtEnd
.GetNonCollapsedRangeInTexts(
4346 textFragmentDataAtEnd
.InvisibleLeadingWhiteSpaceRangeRef());
4347 if (invisibleLeadingWhiteSpacesAtEnd
.IsPositioned() &&
4348 !invisibleLeadingWhiteSpacesAtEnd
.Collapsed()) {
4349 MOZ_ASSERT(aRange
.EndRef().EqualsOrIsBefore(
4350 invisibleLeadingWhiteSpacesAtEnd
.EndRef()));
4351 result
.SetEnd(invisibleLeadingWhiteSpacesAtEnd
.EndRef());
4353 // If there is no invisible white-space and the line ends with a text
4354 // node, shrink the range to end of the text node.
4355 else if (!aRange
.EndRef().IsInTextNode() &&
4356 textFragmentDataAtEnd
.EndsByBlockBoundary() &&
4357 textFragmentDataAtEnd
.StartRef().IsInTextNode()) {
4358 result
.SetEnd(EditorDOMPoint::AtEndOf(
4359 *textFragmentDataAtEnd
.StartRef().ContainerAs
<Text
>()));
4362 if (!result
.EndRef().IsSet()) {
4363 result
.SetEnd(aRange
.EndRef());
4365 MOZ_ASSERT(result
.IsPositionedAndValid());
4369 /******************************************************************************
4370 * Utilities for other things.
4371 ******************************************************************************/
4374 Result
<bool, nsresult
>
4375 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
4376 const HTMLEditor
& aHTMLEditor
, nsRange
& aRange
,
4377 const Element
* aEditingHost
) {
4378 MOZ_ASSERT(aRange
.IsPositioned());
4379 MOZ_ASSERT(!aRange
.IsInAnySelection(),
4380 "Changing range in selection may cause running script");
4382 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
4383 NS_WARN_IF(!aRange
.GetEndContainer())) {
4384 return Err(NS_ERROR_FAILURE
);
4387 if (!aRange
.GetStartContainer()->IsContent() ||
4388 !aRange
.GetEndContainer()->IsContent()) {
4392 // If the range crosses a block boundary, we should do nothing for now
4393 // because it hits a bug of inserting a padding `<br>` element after
4394 // joining the blocks.
4395 if (HTMLEditUtils::GetInclusiveAncestorElement(
4396 *aRange
.GetStartContainer()->AsContent(),
4397 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
4398 BlockInlineCheck::UseComputedDisplayStyle
) !=
4399 HTMLEditUtils::GetInclusiveAncestorElement(
4400 *aRange
.GetEndContainer()->AsContent(),
4401 HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
,
4402 BlockInlineCheck::UseComputedDisplayStyle
)) {
4406 nsIContent
* startContent
= nullptr;
4407 if (aRange
.GetStartContainer() && aRange
.GetStartContainer()->IsText() &&
4408 aRange
.GetStartContainer()->AsText()->Length() == aRange
.StartOffset()) {
4409 // If next content is a visible `<br>` element, special inline content
4410 // (e.g., `<img>`, non-editable text node, etc) or a block level void
4411 // element like `<hr>`, the range should start with it.
4412 TextFragmentData
textFragmentDataAtStart(
4413 EditorRawDOMPoint(aRange
.StartRef()), aEditingHost
,
4414 BlockInlineCheck::UseComputedDisplayStyle
);
4415 if (NS_WARN_IF(!textFragmentDataAtStart
.IsInitialized())) {
4416 return Err(NS_ERROR_FAILURE
);
4418 if (textFragmentDataAtStart
.EndsByVisibleBRElement()) {
4419 startContent
= textFragmentDataAtStart
.EndReasonBRElementPtr();
4420 } else if (textFragmentDataAtStart
.EndsBySpecialContent() ||
4421 (textFragmentDataAtStart
.EndsByOtherBlockElement() &&
4422 !HTMLEditUtils::IsContainerNode(
4423 *textFragmentDataAtStart
4424 .EndReasonOtherBlockElementPtr()))) {
4425 startContent
= textFragmentDataAtStart
.GetEndReasonContent();
4429 nsIContent
* endContent
= nullptr;
4430 if (aRange
.GetEndContainer() && aRange
.GetEndContainer()->IsText() &&
4431 !aRange
.EndOffset()) {
4432 // If previous content is a visible `<br>` element, special inline content
4433 // (e.g., `<img>`, non-editable text node, etc) or a block level void
4434 // element like `<hr>`, the range should end after it.
4435 TextFragmentData
textFragmentDataAtEnd(
4436 EditorRawDOMPoint(aRange
.EndRef()), aEditingHost
,
4437 BlockInlineCheck::UseComputedDisplayStyle
);
4438 if (NS_WARN_IF(!textFragmentDataAtEnd
.IsInitialized())) {
4439 return Err(NS_ERROR_FAILURE
);
4441 if (textFragmentDataAtEnd
.StartsFromVisibleBRElement()) {
4442 endContent
= textFragmentDataAtEnd
.StartReasonBRElementPtr();
4443 } else if (textFragmentDataAtEnd
.StartsFromSpecialContent() ||
4444 (textFragmentDataAtEnd
.StartsFromOtherBlockElement() &&
4445 !HTMLEditUtils::IsContainerNode(
4446 *textFragmentDataAtEnd
4447 .StartReasonOtherBlockElementPtr()))) {
4448 endContent
= textFragmentDataAtEnd
.GetStartReasonContent();
4452 if (!startContent
&& !endContent
) {
4456 nsresult rv
= aRange
.SetStartAndEnd(
4457 startContent
? RangeBoundary(
4458 startContent
->GetParentNode(),
4459 startContent
->GetPreviousSibling()) // at startContent
4460 : aRange
.StartRef(),
4461 endContent
? RangeBoundary(endContent
->GetParentNode(),
4462 endContent
) // after endContent
4464 if (NS_FAILED(rv
)) {
4465 NS_WARNING("nsRange::SetStartAndEnd() failed");
4471 } // namespace mozilla