Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / WSRunObject.cpp
blob7149578be1137cb3d2c54cd7ec9ad082419660f8
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"
29 #include "nsCRT.h"
30 #include "nsContentUtils.h"
31 #include "nsDebug.h"
32 #include "nsError.h"
33 #include "nsIContent.h"
34 #include "nsIContentInlines.h"
35 #include "nsISupportsImpl.h"
36 #include "nsRange.h"
37 #include "nsString.h"
38 #include "nsTextFragment.h"
40 namespace mozilla {
42 using namespace dom;
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) {
124 break;
126 if (HTMLEditUtils::IsSplittableNode(*content)) {
127 break;
129 pointToSplit.Set(content);
133 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &pointToSplit);
135 nsresult rv = WhiteSpaceVisibilityKeeper::
136 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(aHTMLEditor,
137 pointToSplit);
138 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
139 return Err(NS_ERROR_EDITOR_DESTROYED);
141 if (NS_FAILED(rv)) {
142 NS_WARNING(
143 "WhiteSpaceVisibilityKeeper::"
144 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() failed");
145 return Err(rv);
149 if (NS_WARN_IF(!pointToSplit.IsInContentNode()) ||
150 NS_WARN_IF(
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);
159 return pointToSplit;
162 // static
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) {
170 MOZ_ASSERT(
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())) {
186 NS_WARNING(
187 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
188 "failed");
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))) {
203 NS_WARNING(
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)) {
211 NS_WARNING(
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))) {
219 NS_WARNING(
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))) {
227 NS_WARNING(
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())) {
244 NS_WARNING(
245 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
246 "failed");
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);
263 } else {
264 rightBlockElement = *afterRightBlockChild.GetContainerParentAs<Element>();
268 // Do br adjustment.
269 RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
270 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
271 aHTMLEditor.ComputeEditingHost(),
272 EditorDOMPoint::AtEndOf(aLeftBlockElement),
273 BlockInlineCheck::UseComputedDisplayStyle);
274 NS_ASSERTION(
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.
297 ret.MarkAsHandled();
298 } else {
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");
302 #ifdef DEBUG
303 Result<bool, nsresult> firstLineHasContent =
304 HTMLEditor::AutoMoveOneLineHandler::CanMoveOrDeleteSomethingInLine(
305 EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
306 aEditingHost);
307 #endif // #ifdef DEBUG
308 HTMLEditor::AutoMoveOneLineHandler lineMoverToEndOfLeftBlock(
309 aLeftBlockElement);
310 nsresult rv = lineMoverToEndOfLeftBlock.Prepare(
311 aHTMLEditor,
312 EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
313 aEditingHost);
314 if (NS_FAILED(rv)) {
315 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
316 return Err(rv);
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();
325 #ifdef DEBUG
326 MOZ_ASSERT(!firstLineHasContent.isErr());
327 if (firstLineHasContent.inspect()) {
328 NS_ASSERTION(moveNodeResult.inspect().Handled(),
329 "Failed to consider whether moving or not something");
330 } else {
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
337 // above.
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()) {
347 return ret;
350 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
351 *invisibleBRElementAtEndOfLeftBlockElement);
352 if (NS_FAILED(rv)) {
353 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
354 return Err(rv);
356 return EditActionResult::HandledResult();
359 // static
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) {
368 MOZ_ASSERT(
369 EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement));
370 MOZ_ASSERT(
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())) {
387 NS_WARNING(
388 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
389 "failed");
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))) {
404 NS_WARNING(
405 "Deleting invisible whitespace at start of right block element "
406 "caused moving the right block element outside the left block "
407 "element");
408 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
411 if (MOZ_UNLIKELY(rightBlockContainingPointInLeftBlockElement !=
412 aAtLeftBlockChild)) {
413 NS_WARNING(
414 "Deleting invisible whitespace at start of right block element "
415 "caused changing the right block element position in the left block "
416 "element");
417 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
420 if (MOZ_UNLIKELY(!EditorUtils::IsEditableContent(aLeftBlockElement,
421 EditorType::HTML))) {
422 NS_WARNING(
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))) {
430 NS_WARNING(
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
442 // something else.
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())) {
449 NS_WARNING(
450 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
451 "failed");
452 return caretPointOrError.propagateErr();
454 // Ignore caret suggestion because there was
455 // AutoTransactionsConserveSelection.
456 caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
458 if (MOZ_UNLIKELY(!atLeftBlockChild.IsSetAndValid())) {
459 NS_WARNING(
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;
470 } else {
471 return Err(NS_ERROR_UNEXPECTED);
474 // Do br adjustment.
475 RefPtr<HTMLBRElement> invisibleBRElementBeforeLeftBlockElement =
476 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
477 aHTMLEditor.ComputeEditingHost(), atLeftBlockChild,
478 BlockInlineCheck::UseComputedDisplayStyle);
479 NS_ASSERTION(
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");
490 #ifdef DEBUG
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(
497 aRightBlockElement,
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());
507 NS_WARNING(
508 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
509 } else {
510 #ifdef DEBUG
511 MOZ_ASSERT(!rightBlockHasContent.isErr());
512 if (rightBlockHasContent.inspect()) {
513 NS_ASSERTION(moveNodeResult.inspect().Handled(),
514 "Failed to consider whether moving or not children");
515 } else {
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
521 // above.
522 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
523 ret |= moveNodeResult.unwrap();
525 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
526 atLeftBlockChild.Clear();
527 } else {
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
530 // want to move.
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
537 // out of its block.
538 pointToMoveFirstLineContent = atLeftBlockChild;
539 MOZ_ASSERT(pointToMoveFirstLineContent.GetContainer() ==
540 &aLeftBlockElement);
541 } else {
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.
565 #ifdef DEBUG
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});
584 if (NS_FAILED(rv)) {
585 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
586 return Err(rv);
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() ==
599 &aEditingHost)) {
600 break;
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);
615 } else {
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);
630 if (NS_FAILED(rv)) {
631 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
632 return Err(rv);
634 Result<MoveNodeResult, nsresult> moveNodeResult =
635 lineMoverToPoint.Run(aHTMLEditor, aEditingHost);
636 if (moveNodeResult.isErr()) {
637 NS_WARNING("AutoMoveOneLineHandler::Run() failed");
638 return moveNodeResult.propagateErr();
641 #ifdef DEBUG
642 MOZ_ASSERT(!firstLineHasContent.isErr());
643 if (firstLineHasContent.inspect()) {
644 NS_ASSERTION(moveNodeResult.inspect().Handled(),
645 "Failed to consider whether moving or not something");
646 } else {
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
653 // above.
654 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
655 ret |= moveNodeResult.unwrap();
658 if (!invisibleBRElementBeforeLeftBlockElement ||
659 !invisibleBRElementBeforeLeftBlockElement->IsInComposedDoc()) {
660 return ret;
663 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(
664 *invisibleBRElementBeforeLeftBlockElement);
665 if (NS_FAILED(rv)) {
666 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed, but ignored");
667 return Err(rv);
669 return EditActionResult::HandledResult();
672 // static
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) {
679 MOZ_ASSERT(
680 !EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement));
681 MOZ_ASSERT(
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(
694 aHTMLEditor,
695 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement),
696 EditorDOMPoint(&aRightBlockElement, 0)),
697 aEditingHost);
698 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
699 NS_WARNING(
700 "WhiteSpaceVisibilityKeeper::"
701 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() "
702 "failed");
703 return caretPointOrError.propagateErr();
705 // Ignore caret point suggestion because there was
706 // AutoTransactionsConserveSelection.
707 caretPointOrError.unwrap().IgnoreCaretPointSuggestion();
709 // Do br adjustment.
710 RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
711 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
712 aHTMLEditor.ComputeEditingHost(),
713 EditorDOMPoint::AtEndOf(aLeftBlockElement),
714 BlockInlineCheck::UseComputedDisplayStyle);
715 NS_ASSERTION(
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()),
743 *nsGkAtoms::li);
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");
750 } else {
751 // There is AutoTransactionConserveSelection above, therefore, we don't
752 // need to update selection here.
753 convertListTypeResult.inspect().IgnoreCaretPointSuggestion();
756 ret.MarkAsHandled();
757 } else {
758 #ifdef DEBUG
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(
766 aLeftBlockElement);
767 nsresult rv = lineMoverToEndOfLeftBlock.Prepare(
768 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0u), aEditingHost);
769 if (NS_FAILED(rv)) {
770 NS_WARNING("AutoMoveOneLineHandler::Prepare() failed");
771 return Err(rv);
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();
780 #ifdef DEBUG
781 MOZ_ASSERT(!firstLineHasContent.isErr());
782 if (firstLineHasContent.inspect()) {
783 NS_ASSERTION(moveNodeResult.inspect().Handled(),
784 "Failed to consider whether moving or not something");
785 } else {
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
792 // above.
793 moveNodeResult.inspect().IgnoreCaretPointSuggestion();
794 ret |= moveNodeResult.unwrap();
797 if (!invisibleBRElementAtEndOfLeftBlockElement ||
798 !invisibleBRElementAtEndOfLeftBlockElement->IsInComposedDoc()) {
799 ret.MarkAsHandled();
800 return ret;
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
807 // is respected?
808 if (NS_FAILED(rv)) {
809 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
810 return Err(rv);
812 return EditActionResult::HandledResult();
815 // static
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);
830 if (MOZ_UNLIKELY(
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())
844 : Nothing();
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(
858 pointToInsert)
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() ==
867 pointToInsert);
868 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
869 &pointToInsert);
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())) {
882 NS_WARNING(
883 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
884 return caretPointOrError.propagateErr();
886 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
887 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
888 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
889 SuggestCaret::AndIgnoreTrivialError});
890 if (NS_FAILED(rv)) {
891 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
892 return Err(rv);
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
898 // change.
899 invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
902 // If new line will start with visible white-spaces, it needs to be start
903 // with an NBSP.
904 else if (pointPositionWithVisibleWhiteSpaces ==
905 PointPosition::StartOfFragment ||
906 pointPositionWithVisibleWhiteSpaces ==
907 PointPosition::MiddleOfFragment) {
908 auto atNextCharOfInsertionPoint =
909 textFragmentDataAtInsertionPoint
910 .GetInclusiveNextEditableCharPoint<EditorDOMPointInText>(
911 pointToInsert);
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(),
922 &pointToInsert);
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);
933 nsresult rv =
934 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
935 aHTMLEditor,
936 EditorDOMRangeInTexts(atNextCharOfInsertionPoint,
937 endOfCollapsibleASCIIWhiteSpaces),
938 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
939 if (MOZ_UNLIKELY(NS_FAILED(rv))) {
940 NS_WARNING(
941 "WhiteSpaceVisibilityKeeper::"
942 "ReplaceTextAndRemoveEmptyTextNodes() failed");
943 return Err(rv);
945 // Don't refer the following variables anymore unless tracking the
946 // change.
947 invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
952 if (invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned()) {
953 if (!invisibleLeadingWhiteSpaceRangeOfNewLine.Collapsed()) {
954 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
955 &pointToInsert);
956 // XXX Why don't we remove all of the invisible white-spaces?
957 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef() ==
958 pointToInsert);
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())) {
966 NS_WARNING(
967 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
968 return caretPointOrError.propagateErr();
970 nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
971 aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
972 SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
973 SuggestCaret::AndIgnoreTrivialError});
974 if (NS_FAILED(rv)) {
975 NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
976 return Err(rv);
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
982 // change.
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(),
996 &pointToInsert);
997 Result<InsertTextResult, nsresult> replaceTextResult =
998 aHTMLEditor.ReplaceTextWithTransaction(
999 MOZ_KnownLive(
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
1010 // change.
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;
1026 // static
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())
1078 : Nothing();
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())
1088 : Nothing();
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(
1107 pointToInsert)
1108 .To<EditorDOMPoint>();
1110 nsAutoString theString(aStringToInsert);
1112 if (invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned()) {
1113 if (!invisibleTrailingWhiteSpaceRangeAtEnd.Collapsed()) {
1114 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
1115 &pointToInsert);
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() ==
1126 pointToInsert);
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())) {
1134 NS_WARNING(
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(),
1158 &pointToPutCaret);
1159 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
1160 &pointToInsert);
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(
1171 MOZ_KnownLive(
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(),
1187 &pointToPutCaret);
1188 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
1189 &pointToInsert);
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() ==
1195 pointToInsert);
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())) {
1203 NS_WARNING(
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
1211 // change.
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(),
1228 &pointToPutCaret);
1229 AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
1230 &pointToInsert);
1231 AutoTrackDOMRange trackInvisibleTrailingWhiteSpaceRange(
1232 aHTMLEditor.RangeUpdaterRef(),
1233 &invisibleTrailingWhiteSpaceRangeAtEnd);
1234 Result<InsertTextResult, nsresult> replaceTextResult =
1235 aHTMLEditor.ReplaceTextWithTransaction(
1236 MOZ_KnownLive(
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
1247 // change.
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>(
1286 pointToInsert);
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
1311 // white-space.
1312 if (pointPositionWithVisibleWhiteSpacesAtEnd ==
1313 PointPosition::StartOfFragment ||
1314 pointPositionWithVisibleWhiteSpacesAtEnd ==
1315 PointPosition::MiddleOfFragment) {
1316 const auto atNextChar =
1317 textFragmentDataAtEnd
1318 .GetInclusiveNextEditableCharPoint<EditorRawDOMPointInText>(
1319 pointToInsert);
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 {
1339 NonCollapsibleChar,
1340 CollapsibleChar,
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) {
1350 MOZ_ASSERT(i > 0);
1351 theString.SetCharAt(HTMLEditUtils::kNBSP, i - 1);
1352 // Keep previousChar as PreviousChar::CollapsibleChar.
1353 continue;
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
1359 // linefeed.
1360 if (previousChar == PreviousChar::PreformattedNewLine) {
1361 MOZ_ASSERT(i > 0);
1362 theString.SetCharAt(HTMLEditUtils::kNBSP, i);
1363 previousChar = PreviousChar::NonCollapsibleChar;
1364 continue;
1367 previousChar = PreviousChar::CollapsibleChar;
1368 continue;
1371 if (theString[i] != HTMLEditUtils::kNewLine) {
1372 previousChar = PreviousChar::NonCollapsibleChar;
1373 continue;
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) {
1382 MOZ_ASSERT(i > 0);
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())) {
1395 NS_WARNING(
1396 "WhiteSpaceVisibilityKeeper::ReplaceText() lost proper document");
1397 return Err(NS_ERROR_UNEXPECTED);
1399 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
1400 &pointToPutCaret);
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));
1416 // static
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())) {
1452 NS_WARNING(
1453 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1454 "failed");
1455 return caretPointOrError;
1457 caretPointOrError.unwrap().MoveCaretPointTo(
1458 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
1462 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
1463 &pointToPutCaret);
1464 Result<CaretPoint, nsresult> caretPointOrError =
1465 aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
1466 startToDelete, endToDelete,
1467 HTMLEditor::TreatEmptyTextNodes::
1468 KeepIfContainerOfRangeBoundaries);
1469 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
1470 NS_WARNING(
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())) {
1490 NS_WARNING(
1491 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1492 "failed");
1493 return caretPointOrError;
1495 caretPointOrError.unwrap().MoveCaretPointTo(
1496 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
1500 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
1501 &pointToPutCaret);
1502 Result<CaretPoint, nsresult> caretPointOrError =
1503 aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
1504 startToDelete, endToDelete,
1505 HTMLEditor::TreatEmptyTextNodes::
1506 KeepIfContainerOfRangeBoundaries);
1507 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
1508 NS_WARNING(
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;
1529 // static
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())) {
1564 NS_WARNING(
1565 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1566 "failed");
1567 return caretPointOrError;
1569 caretPointOrError.unwrap().MoveCaretPointTo(
1570 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
1574 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
1575 &pointToPutCaret);
1576 Result<CaretPoint, nsresult> caretPointOrError =
1577 aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
1578 startToDelete, endToDelete,
1579 HTMLEditor::TreatEmptyTextNodes::
1580 KeepIfContainerOfRangeBoundaries);
1581 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
1582 NS_WARNING(
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())) {
1602 NS_WARNING(
1603 "WhiteSpaceVisibilityKeeper::PrepareToDeleteRangeAndTrackPoints() "
1604 "failed");
1605 return caretPointOrError;
1607 caretPointOrError.unwrap().MoveCaretPointTo(
1608 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
1612 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
1613 &pointToPutCaret);
1614 Result<CaretPoint, nsresult> caretPointOrError =
1615 aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
1616 startToDelete, endToDelete,
1617 HTMLEditor::TreatEmptyTextNodes::
1618 KeepIfContainerOfRangeBoundaries);
1619 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
1620 NS_WARNING(
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;
1641 // static
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(),
1658 &pointToPutCaret);
1659 Result<CaretPoint, nsresult> caretPointOrError =
1660 WhiteSpaceVisibilityKeeper::
1661 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1662 aHTMLEditor, EditorDOMRange(atContent, atContent.NextPoint()),
1663 aEditingHost);
1664 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
1665 NS_WARNING(
1666 "WhiteSpaceVisibilityKeeper::"
1667 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() "
1668 "failed");
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(),
1682 &pointToPutCaret);
1683 nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
1684 if (NS_FAILED(rv)) {
1685 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
1686 return Err(rv);
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>()),
1706 &pointToPutCaret);
1707 if (NS_FAILED(rv)) {
1708 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1709 return Err(rv);
1711 if (!pointToPutCaret.IsSet()) {
1712 NS_WARNING(
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,
1740 mBlockInlineCheck);
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,
1751 mBlockInlineCheck);
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
1759 // meaningful.
1760 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1761 TextFragmentDataAtStartRef().StartRawReason(),
1762 mBlockInlineCheck);
1764 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1765 TextFragmentDataAtStartRef().StartRawReason(),
1766 mBlockInlineCheck);
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,
1789 mBlockInlineCheck);
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,
1800 mBlockInlineCheck);
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
1808 // meaningful.
1809 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1810 TextFragmentDataAtStartRef().EndRawReason(),
1811 mBlockInlineCheck);
1813 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1814 TextFragmentDataAtStartRef().EndRawReason(),
1815 mBlockInlineCheck);
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");
1825 return;
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.
1832 return;
1835 mScanStartPoint = aPoint.template To<EditorDOMPoint>();
1836 NS_ASSERTION(
1837 EditorUtils::IsEditableContent(*mScanStartPoint.ContainerAs<nsIContent>(),
1838 EditorType::HTML),
1839 "Given content is not editable");
1840 NS_ASSERTION(
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))) {
1845 return;
1847 const Element* editableBlockElementOrInlineEditingHost =
1848 HTMLEditUtils::GetInclusiveAncestorElement(
1849 *mScanStartPoint.ContainerAs<nsIContent>(),
1850 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost,
1851 aBlockInlineCheck);
1852 if (!editableBlockElementOrInlineEditingHost) {
1853 NS_WARNING(
1854 "HTMLEditUtils::GetInclusiveAncestorElement(HTMLEditUtils::"
1855 "ClosestEditableBlockElementOrInlineEditingHost) couldn't find "
1856 "editing host");
1857 return;
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());
1876 // static
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;
1892 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;
1903 break;
1904 case HTMLEditUtils::kNewLine:
1905 if (isNewLineCollapsible) {
1906 continue; // collapsible linefeed.
1908 // preformatted linefeed.
1909 wsTypeOfNonCollapsibleChar = WSType::PreformattedLineBreak;
1910 break;
1911 case HTMLEditUtils::kNBSP:
1912 if (isWhiteSpaceCollapsible) {
1913 if (aNBSPData) {
1914 aNBSPData->NotifyNBSP(
1915 EditorDOMPointInText(aPoint.template ContainerAs<Text>(),
1916 i - 1),
1917 NoBreakingSpaceData::Scanning::Backward);
1919 continue;
1921 // NBSP is never converted from collapsible white-space/linefeed.
1922 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
1923 break;
1924 default:
1925 MOZ_ASSERT(!nsCRT::IsAsciiSpace(textFragment.CharAt(i - 1)));
1926 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
1927 break;
1930 return Some(BoundaryData(
1931 EditorDOMPoint(aPoint.template ContainerAs<Text>(), i),
1932 *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar));
1935 return Nothing();
1938 // static
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
1956 // preceding nodes.
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,
1968 aEditingHost);
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)
1992 ? WSType::BRElement
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
2015 // preceding nodes.
2016 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
2017 EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0),
2018 aEditableBlockParentOrTopmostEditableInlineElement, aEditingHost,
2019 aNBSPData, aBlockInlineCheck);
2022 // static
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;
2048 break;
2049 case HTMLEditUtils::kNewLine:
2050 if (isNewLineCollapsible) {
2051 continue; // collapsible linefeed.
2053 // preformatted linefeed.
2054 wsTypeOfNonCollapsibleChar = WSType::PreformattedLineBreak;
2055 break;
2056 case HTMLEditUtils::kNBSP:
2057 if (isWhiteSpaceCollapsible) {
2058 if (aNBSPData) {
2059 aNBSPData->NotifyNBSP(
2060 EditorDOMPointInText(aPoint.template ContainerAs<Text>(), i),
2061 NoBreakingSpaceData::Scanning::Forward);
2063 continue;
2065 // NBSP is never converted from collapsible white-space/linefeed.
2066 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
2067 break;
2068 default:
2069 MOZ_ASSERT(!nsCRT::IsAsciiSpace(textFragment.CharAt(i)));
2070 wsTypeOfNonCollapsibleChar = WSType::NonCollapsibleCharacters;
2071 break;
2074 return Some(BoundaryData(
2075 EditorDOMPoint(aPoint.template ContainerAs<Text>(), i),
2076 *aPoint.template ContainerAs<Text>(), wsTypeOfNonCollapsibleChar));
2079 return Nothing();
2082 // static
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,
2095 aBlockInlineCheck);
2096 if (endInTextNode.isSome()) {
2097 return endInTextNode.ref();
2099 // The text node does not have visible character, let's keep scanning
2100 // following nodes.
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,
2112 aEditingHost);
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)
2138 ? WSType::BRElement
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,
2155 aBlockInlineCheck);
2156 if (endInTextNode.isSome()) {
2157 return endInTextNode.ref();
2160 // The text node does not have visible character, let's keep scanning
2161 // following nodes.
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-
2212 // spaces.
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-
2222 // spaces.
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(),
2234 mEnd.PointRef());
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>(
2258 aRange.StartRef());
2259 if (!firstPoint.IsSet()) {
2260 return EditorDOMRangeInTexts();
2262 EditorDOMPointInText endPoint;
2263 if (aRange.EndRef().IsInTextNode()) {
2264 endPoint = aRange.EndRef().AsInText();
2265 } else {
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
2288 // things quickly.
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();
2391 // static
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
2434 // should:
2435 // - start with another node if the following text node starts with
2436 // white-spaces.
2437 // - start from prior point because end of the range may be in collapsible
2438 // white-spaces.
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(),
2453 &startToDelete);
2454 AutoTrackDOMPoint trackEndToDelete(aHTMLEditor.RangeUpdaterRef(),
2455 &endToDelete);
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())) {
2463 NS_WARNING(
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);
2478 } else {
2479 MOZ_ASSERT(replaceRangeDataAtEnd.RangeRef().IsInTextNodes());
2480 EditorDOMPoint startToDelete(aRangeToDelete.StartRef());
2481 EditorDOMPoint endToDelete(replaceRangeDataAtEnd.StartRef());
2483 AutoTrackDOMPoint trackStartToDelete(aHTMLEditor.RangeUpdaterRef(),
2484 &startToDelete);
2485 AutoTrackDOMPoint trackEndToDelete(aHTMLEditor.RangeUpdaterRef(),
2486 &endToDelete);
2487 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
2488 &pointToPutCaret);
2489 // FYI: ReplaceTextAndRemoveEmptyTextNodes() does not have any idea of
2490 // new caret position.
2491 nsresult rv =
2492 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2493 aHTMLEditor, replaceRangeDataAtEnd.RangeRef().AsInTexts(),
2494 replaceRangeDataAtEnd.ReplaceStringRef());
2495 if (NS_FAILED(rv)) {
2496 NS_WARNING(
2497 "WhiteSpaceVisibilityKeeper::"
2498 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
2499 "failed");
2500 return Err(rv);
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(),
2543 &pointToPutCaret);
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(),
2564 &pointToPutCaret);
2565 // FYI: ReplaceTextAndRemoveEmptyTextNodes() does not have any idea of
2566 // new caret position.
2567 nsresult rv =
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)) {
2574 NS_WARNING(
2575 "WhiteSpaceVisibilityKeeper::"
2576 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() "
2577 "failed");
2578 return Err(rv);
2581 return CaretPoint(std::move(pointToPutCaret));
2584 ReplaceRangeData
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));
2656 ReplaceRangeData
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
2687 // an NBSP.
2688 const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtStart =
2689 VisibleWhiteSpacesDataRef();
2690 if (!nonPreformattedVisibleWhiteSpacesAtStart.IsInitialized()) {
2691 return ReplaceRangeData();
2693 const PointPosition
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(
2707 endToDelete)) {
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,
2724 nsIEditor::eNone);
2725 return ReplaceRangeData(atPreviousCharOfStart,
2726 endOfCollapsibleASCIIWhiteSpaces,
2727 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
2730 // static
2731 nsresult
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
2744 // the split.
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(
2765 pointToSplit);
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(),
2772 &pointToSplit);
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);
2782 nsresult rv =
2783 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2784 aHTMLEditor,
2785 EditorDOMRangeInTexts(atNextCharOfStart,
2786 endOfCollapsibleASCIIWhiteSpaces),
2787 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
2788 if (NS_FAILED(rv)) {
2789 NS_WARNING(
2790 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2791 "failed");
2792 return rv;
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
2799 // NBSP.
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,
2812 nsIEditor::eNone);
2814 const EditorDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
2815 textFragmentDataAtSplitPoint.GetEndOfCollapsibleASCIIWhiteSpaces(
2816 atPreviousCharOfStart, nsIEditor::eNone);
2817 nsresult rv =
2818 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2819 aHTMLEditor,
2820 EditorDOMRangeInTexts(atPreviousCharOfStart,
2821 endOfCollapsibleASCIIWhiteSpaces),
2822 nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
2823 if (NS_FAILED(rv)) {
2824 NS_WARNING(
2825 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2826 "failed");
2827 return rv;
2831 return NS_OK;
2834 template <typename EditorDOMPointType, typename PT, typename CT>
2835 EditorDOMPointType
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})
2851 : child;
2852 if (NS_WARN_IF(!leafContent)) {
2853 return EditorDOMPointType();
2855 point.Set(leafContent, 0);
2856 } else {
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();
2872 NS_ASSERTION(
2873 EditorUtils::IsEditableContent(*mScanStartPoint.ContainerAs<nsIContent>(),
2874 EditorType::HTML),
2875 "Given content is not editable");
2876 NS_ASSERTION(
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,
2886 mBlockInlineCheck)
2887 : nullptr;
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,
2899 mEditingHost);
2900 nextContent;
2901 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2902 *nextContent, *editableBlockElementOrInlineEditingHost,
2903 {LeafNodeType::LeafNodeOrNonEditableNode}, mBlockInlineCheck,
2904 mEditingHost)) {
2905 if (!nextContent->IsText() || !nextContent->IsEditable()) {
2906 if (nextContent == GetEndReasonContent()) {
2907 break; // Reached end of current runs.
2909 continue;
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()
2929 : nullptr) {
2930 nsIContent* leafContent =
2931 previousChild->HasChildren()
2932 ? HTMLEditUtils::GetLastLeafContent(*previousChild,
2933 {LeafNodeType::OnlyLeafNode})
2934 : previousChild;
2935 if (NS_WARN_IF(!leafContent)) {
2936 return EditorDOMPointType();
2938 point.SetToEndOf(leafContent);
2939 } else {
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();
2956 NS_ASSERTION(
2957 EditorUtils::IsEditableContent(*mScanStartPoint.ContainerAs<nsIContent>(),
2958 EditorType::HTML),
2959 "Given content is not editable");
2960 NS_ASSERTION(
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,
2970 mBlockInlineCheck)
2971 : nullptr;
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,
2983 mEditingHost);
2984 previousContent;
2985 previousContent =
2986 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2987 *previousContent, *editableBlockElementOrInlineEditingHost,
2988 {LeafNodeType::LeafNodeOrNonEditableNode}, mBlockInlineCheck,
2989 mEditingHost)) {
2990 if (!previousContent->IsText() || !previousContent->IsEditable()) {
2991 if (previousContent == GetStartReasonContent()) {
2992 break; // Reached start of current runs.
2994 continue;
2996 return EditorDOMPointType(previousContent->AsText(),
2997 previousContent->AsText()->TextLength()
2998 ? previousContent->AsText()->TextLength() - 1
2999 : 0);
3001 return EditorDOMPointType();
3004 // static
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>();
3029 // static
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>
3053 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
3121 // content.
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;
3136 continue;
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
3143 // text forward.
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>
3164 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
3234 // content.
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;
3248 continue;
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);
3271 // static
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(),
3290 aReplaceString);
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()) {
3301 return NS_OK;
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();
3317 return NS_OK;
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())) {
3324 return 0;
3326 return aTextNode->TextFragment().CharAt(aOffset);
3329 // static
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()) {
3347 // nothing to do!
3348 return NS_OK;
3350 const VisibleWhiteSpacesData& visibleWhiteSpaces =
3351 textFragmentData.VisibleWhiteSpacesDataRef();
3352 if (!visibleWhiteSpaces.IsInitialized()) {
3353 return NS_OK;
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
3359 // nbsp with space
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()) {
3370 return NS_OK;
3373 // now check that what is to the left of it is compatible with replacing
3374 // nbsp with space
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) {
3410 NS_ASSERTION(
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)
3424 : nullptr;
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. &nbsp</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.&nbsp </body>| instead, the trailing
3439 // space is invisible because it is against a block boundary. If you
3440 // do:
3441 // |<body>foo.&nbsp&nbsp</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.&nbsp <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())) {
3454 NS_WARNING(
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>())) {
3489 return NS_OK;
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();
3513 return NS_OK;
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>())) {
3529 return NS_OK;
3532 // Currently, we're at an NBSP following an ASCII space, and we need to
3533 // replace them with `"&nbsp; "` for avoiding collapsing white-spaces.
3534 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
3535 .IsEndOfContainer());
3536 const EditorDOMPointInText atFirstASCIIWhiteSpace =
3537 textFragmentData.GetFirstASCIIWhiteSpacePointCollapsedTo(
3538 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces,
3539 nsIEditor::eNone);
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>()
3553 : 0);
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()
3562 ? u" \x00A0"_ns
3563 : u"\x00A0 "_ns));
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()) {
3574 return NS_OK;
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();
3593 return NS_OK;
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()) {
3618 return NS_OK;
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,
3636 nsIEditor::eNone);
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.
3641 else {
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())) {
3652 NS_WARNING(
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();
3659 return NS_OK;
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
3677 // inserted object.
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 "
3728 "white-spaces");
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.
3734 auto atNextChar =
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();
3760 return atNextChar;
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();
3773 return atNextChar;
3776 // static
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 "
3814 "outdated range");
3815 AutoTrackDOMPoint trackPointToPutCaret(aHTMLEditor.RangeUpdaterRef(),
3816 &pointToPutCaret);
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 *****************************************************************************/
3837 // static
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) {
3882 return result;
3885 MOZ_ASSERT_IF(
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());
3897 } else {
3898 const auto atFirstInvisibleWhiteSpace =
3899 hasInvisibleLeadingWhiteSpaces
3900 ? aStart.GetInclusiveNextEditableCharPoint<EditorDOMPointInText>(
3901 aroundFirstInvisibleWhiteSpace)
3902 : aEnd.GetInclusiveNextEditableCharPoint<EditorDOMPointInText>(
3903 aroundFirstInvisibleWhiteSpace);
3904 MOZ_ASSERT(atFirstInvisibleWhiteSpace.IsSet());
3905 MOZ_ASSERT(
3906 atFirstInvisibleWhiteSpace.EqualsOrIsBefore(result.StartRef()));
3907 result.SetStart(atFirstInvisibleWhiteSpace);
3908 MOZ_ASSERT(result.IsPositionedAndValid());
3911 MOZ_ASSERT_IF(
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());
3921 return result;
3923 if (afterLastInvisibleWhiteSpace.IsInTextNode()) {
3924 result.SetEnd(afterLastInvisibleWhiteSpace.AsInText());
3925 MOZ_ASSERT(result.IsPositionedAndValid());
3926 return result;
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());
3940 return result;
3943 // static
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
3977 // (blink-compat).
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()) {
3992 NS_WARNING(
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.
4006 else {
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
4035 : rangeToDelete;
4038 // static
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);
4051 auto atCaret =
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()) {
4087 NS_WARNING(
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.
4101 else {
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
4130 : rangeToDelete;
4133 // static
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()
4151 ? EditorDOMRange(
4152 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
4153 followingInvisibleWhiteSpaces.EndRef())
4154 : EditorDOMRange(
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
4169 // block element.
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));
4209 // static
4210 EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
4211 const HTMLEditor& aHTMLEditor, const Element& aLeftBlockElement,
4212 const Element& aRightBlockElement,
4213 const EditorDOMPoint& aPointContainingTheOtherBlock) {
4214 MOZ_ASSERT(&aLeftBlockElement != &aRightBlockElement);
4215 MOZ_ASSERT_IF(
4216 aPointContainingTheOtherBlock.IsSet(),
4217 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement ||
4218 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement);
4219 MOZ_ASSERT_IF(
4220 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement,
4221 aRightBlockElement.IsInclusiveDescendantOf(
4222 aPointContainingTheOtherBlock.GetChild()));
4223 MOZ_ASSERT_IF(
4224 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement,
4225 aLeftBlockElement.IsInclusiveDescendantOf(
4226 aPointContainingTheOtherBlock.GetChild()));
4227 MOZ_ASSERT_IF(
4228 !aPointContainingTheOtherBlock.IsSet(),
4229 !aRightBlockElement.IsInclusiveDescendantOf(&aLeftBlockElement));
4230 MOZ_ASSERT_IF(
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
4254 // `<br>` element.
4255 range.SetStart(EditorDOMPoint(
4256 textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr()));
4257 } else {
4258 const EditorDOMRange& trailingWhiteSpaceRange =
4259 textFragmentDataAtEndOfLeftBlockElement
4260 .InvisibleTrailingWhiteSpaceRangeRef();
4261 if (trailingWhiteSpaceRange.StartRef().IsSet()) {
4262 range.SetStart(trailingWhiteSpaceRange.StartRef());
4263 } else {
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());
4282 } else {
4283 range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef());
4285 return range;
4288 // static
4289 EditorDOMRange
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());
4309 } else {
4310 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
4311 textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
4312 textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef());
4313 if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() &&
4314 !invisibleTrailingWhiteSpacesAtStart.Collapsed()) {
4315 MOZ_ASSERT(
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());
4343 } else {
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());
4366 return result;
4369 /******************************************************************************
4370 * Utilities for other things.
4371 ******************************************************************************/
4373 // static
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()) {
4389 return false;
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)) {
4403 return false;
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) {
4453 return false;
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
4463 : aRange.EndRef());
4464 if (NS_FAILED(rv)) {
4465 NS_WARNING("nsRange::SetStartAndEnd() failed");
4466 return Err(rv);
4468 return true;
4471 } // namespace mozilla