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