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