Bug 1712849 [wpt PR 29110] - Keep 3D points in a quad coplanar when clamping them...
[gecko.git] / editor / libeditor / WSRunObject.cpp
blob39bd9645514aa3b35cb0ac8a1ab9d1a06dcff3ca
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;
41 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
43 const char16_t kNBSP = 160;
45 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
46 const EditorDOMPoint& aPoint) const;
47 template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
48 const EditorRawDOMPoint& aPoint) const;
49 template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
50 const EditorDOMPoint& aPoint) const;
51 template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
52 const EditorRawDOMPoint& aPoint) const;
53 template EditorDOMPoint WSRunScanner::GetAfterLastVisiblePoint(
54 Text& aTextNode, const Element* aAncestorLimiter);
55 template EditorRawDOMPoint WSRunScanner::GetAfterLastVisiblePoint(
56 Text& aTextNode, const Element* aAncestorLimiter);
57 template EditorDOMPoint WSRunScanner::GetFirstVisiblePoint(
58 Text& aTextNode, const Element* aAncestorLimiter);
59 template EditorRawDOMPoint WSRunScanner::GetFirstVisiblePoint(
60 Text& aTextNode, const Element* aAncestorLimiter);
62 template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
63 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aScanStartPoint);
64 template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
65 HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aScanStartPoint);
66 template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
67 HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aScanStartPoint);
69 template WSRunScanner::TextFragmentData::TextFragmentData(
70 const EditorDOMPoint& aPoint, const Element* aEditingHost);
71 template WSRunScanner::TextFragmentData::TextFragmentData(
72 const EditorRawDOMPoint& aPoint, const Element* aEditingHost);
73 template WSRunScanner::TextFragmentData::TextFragmentData(
74 const EditorDOMPointInText& aPoint, const Element* aEditingHost);
76 nsresult WhiteSpaceVisibilityKeeper::PrepareToSplitAcrossBlocks(
77 HTMLEditor& aHTMLEditor, nsCOMPtr<nsINode>* aSplitNode,
78 int32_t* aSplitOffset) {
79 if (NS_WARN_IF(!aSplitNode) || NS_WARN_IF(!*aSplitNode) ||
80 NS_WARN_IF(!aSplitOffset)) {
81 return NS_ERROR_INVALID_ARG;
84 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), aSplitNode,
85 aSplitOffset);
87 nsresult rv = WhiteSpaceVisibilityKeeper::
88 MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
89 aHTMLEditor, EditorDOMPoint(*aSplitNode, *aSplitOffset));
90 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
91 "WhiteSpaceVisibilityKeeper::"
92 "MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit() "
93 "failed");
94 return rv;
97 // static
98 EditActionResult WhiteSpaceVisibilityKeeper::
99 MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
100 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
101 Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild,
102 const Maybe<nsAtom*>& aListElementTagName,
103 const HTMLBRElement* aPrecedingInvisibleBRElement) {
104 MOZ_ASSERT(
105 EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement));
106 MOZ_ASSERT(&aRightBlockElement == aAtRightBlockChild.GetContainer());
108 // NOTE: This method may extend deletion range:
109 // - to delete invisible white-spaces at end of aLeftBlockElement
110 // - to delete invisible white-spaces at start of
111 // afterRightBlockChild.GetChild()
112 // - to delete invisible white-spaces before afterRightBlockChild.GetChild()
113 // - to delete invisible `<br>` element at end of aLeftBlockElement
115 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
117 EditorDOMPoint afterRightBlockChild = aAtRightBlockChild.NextPoint();
118 MOZ_ASSERT(afterRightBlockChild.IsSetAndValid());
119 nsresult rv = WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
120 aHTMLEditor, EditorDOMPoint::AtEndOf(aLeftBlockElement));
121 if (NS_FAILED(rv)) {
122 NS_WARNING(
123 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
124 "failed at left block");
125 return EditActionResult(rv);
127 if (!afterRightBlockChild.IsSetAndValid()) {
128 NS_WARNING(
129 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
130 "running script and the point to be modified was changed");
131 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
134 OwningNonNull<Element> rightBlockElement = aRightBlockElement;
136 // We can't just track rightBlockElement because it's an Element.
137 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(),
138 &afterRightBlockChild);
139 nsresult rv = WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
140 aHTMLEditor, afterRightBlockChild);
141 if (NS_FAILED(rv)) {
142 NS_WARNING(
143 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
144 "failed at right block child");
145 return EditActionResult(rv);
148 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
149 // Do we really need to do update rightBlockElement here??
150 // XXX And afterRightBlockChild.GetContainerAsElement() always returns
151 // an element pointer so that probably here should not use
152 // accessors of EditorDOMPoint, should use DOM API directly instead.
153 if (afterRightBlockChild.GetContainerAsElement()) {
154 rightBlockElement = *afterRightBlockChild.GetContainerAsElement();
155 } else if (NS_WARN_IF(
156 !afterRightBlockChild.GetContainerParentAsElement())) {
157 return EditActionResult(NS_ERROR_UNEXPECTED);
158 } else {
159 rightBlockElement = *afterRightBlockChild.GetContainerParentAsElement();
163 // Do br adjustment.
164 RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
165 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
166 aHTMLEditor.GetActiveEditingHost(),
167 EditorDOMPoint::AtEndOf(aLeftBlockElement));
168 NS_ASSERTION(
169 aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement,
170 "The preceding invisible BR element computation was different");
171 EditActionResult ret(NS_OK);
172 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
173 // AutoInclusiveAncestorBlockElementsJoiner.
174 if (NS_WARN_IF(aListElementTagName.isSome())) {
175 // Since 2002, here was the following comment:
176 // > The idea here is to take all children in rightListElement that are
177 // > past offset, and pull them into leftlistElement.
178 // However, this has never been performed because we are here only when
179 // neither left list nor right list is a descendant of the other but
180 // in such case, getting a list item in the right list node almost
181 // always failed since a variable for offset of
182 // rightListElement->GetChildAt() was not initialized. So, it might be
183 // a bug, but we should keep this traditional behavior for now. If you
184 // find when we get here, please remove this comment if we don't need to
185 // do it. Otherwise, please move children of the right list node to the
186 // end of the left list node.
188 // XXX Although, we do nothing here, but for keeping traditional
189 // behavior, we should mark as handled.
190 ret.MarkAsHandled();
191 } else {
192 // XXX Why do we ignore the result of MoveOneHardLineContents()?
193 NS_ASSERTION(rightBlockElement == afterRightBlockChild.GetContainer(),
194 "The relation is not guaranteed but assumed");
195 #ifdef DEBUG
196 Result<bool, nsresult> firstLineHasContent =
197 aHTMLEditor.CanMoveOrDeleteSomethingInHardLine(EditorRawDOMPoint(
198 rightBlockElement, afterRightBlockChild.Offset()));
199 #endif // #ifdef DEBUG
200 MoveNodeResult moveNodeResult = aHTMLEditor.MoveOneHardLineContents(
201 EditorDOMPoint(rightBlockElement, afterRightBlockChild.Offset()),
202 EditorDOMPoint(&aLeftBlockElement, 0),
203 HTMLEditor::MoveToEndOfContainer::Yes);
204 if (NS_WARN_IF(moveNodeResult.EditorDestroyed())) {
205 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
207 NS_WARNING_ASSERTION(moveNodeResult.Succeeded(),
208 "HTMLEditor::MoveOneHardLineContents("
209 "MoveToEndOfContainer::Yes) failed, but ignored");
210 if (moveNodeResult.Succeeded()) {
211 #ifdef DEBUG
212 MOZ_ASSERT(!firstLineHasContent.isErr());
213 if (firstLineHasContent.inspect()) {
214 NS_ASSERTION(moveNodeResult.Handled(),
215 "Failed to consider whether moving or not something");
216 } else {
217 NS_ASSERTION(moveNodeResult.Ignored(),
218 "Failed to consider whether moving or not something");
220 #endif // #ifdef DEBUG
221 ret |= moveNodeResult;
223 // Now, all children of rightBlockElement were moved to leftBlockElement.
224 // So, afterRightBlockChild is now invalid.
225 afterRightBlockChild.Clear();
228 if (!invisibleBRElementAtEndOfLeftBlockElement) {
229 return ret;
232 rv = aHTMLEditor.DeleteNodeWithTransaction(
233 *invisibleBRElementAtEndOfLeftBlockElement);
234 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
235 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
237 if (NS_FAILED(rv)) {
238 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
239 return EditActionResult(rv);
241 return EditActionHandled();
244 // static
245 EditActionResult WhiteSpaceVisibilityKeeper::
246 MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
247 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
248 Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild,
249 nsIContent& aLeftContentInBlock,
250 const Maybe<nsAtom*>& aListElementTagName,
251 const HTMLBRElement* aPrecedingInvisibleBRElement) {
252 MOZ_ASSERT(
253 EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement));
254 MOZ_ASSERT(
255 &aLeftBlockElement == &aLeftContentInBlock ||
256 EditorUtils::IsDescendantOf(aLeftContentInBlock, aLeftBlockElement));
257 MOZ_ASSERT(&aLeftBlockElement == aAtLeftBlockChild.GetContainer());
259 // NOTE: This method may extend deletion range:
260 // - to delete invisible white-spaces at start of aRightBlockElement
261 // - to delete invisible white-spaces before aRightBlockElement
262 // - to delete invisible white-spaces at start of aAtLeftBlockChild.GetChild()
263 // - to delete invisible white-spaces before aAtLeftBlockChild.GetChild()
264 // - to delete invisible `<br>` element before aAtLeftBlockChild.GetChild()
266 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
268 nsresult rv = WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
269 aHTMLEditor, EditorDOMPoint(&aRightBlockElement, 0));
270 if (NS_FAILED(rv)) {
271 NS_WARNING(
272 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() failed "
273 "at right block");
274 return EditActionResult(rv);
276 if (!aAtLeftBlockChild.IsSetAndValid()) {
277 NS_WARNING(
278 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() caused "
279 "running script and the point to be modified was changed");
280 return EditActionResult(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
283 OwningNonNull<Element> originalLeftBlockElement = aLeftBlockElement;
284 OwningNonNull<Element> leftBlockElement = aLeftBlockElement;
285 EditorDOMPoint atLeftBlockChild(aAtLeftBlockChild);
287 // We can't just track leftBlockElement because it's an Element, so track
288 // something else.
289 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &atLeftBlockChild);
290 nsresult rv = WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
291 aHTMLEditor, EditorDOMPoint(atLeftBlockChild.GetContainer(),
292 atLeftBlockChild.Offset()));
293 if (NS_FAILED(rv)) {
294 NS_WARNING(
295 "WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces() "
296 "failed at left block child");
297 return EditActionResult(rv);
299 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
300 // Do we really need to do update aRightBlockElement here??
301 // XXX And atLeftBlockChild.GetContainerAsElement() always returns
302 // an element pointer so that probably here should not use
303 // accessors of EditorDOMPoint, should use DOM API directly instead.
304 if (atLeftBlockChild.GetContainerAsElement()) {
305 leftBlockElement = *atLeftBlockChild.GetContainerAsElement();
306 } else if (NS_WARN_IF(!atLeftBlockChild.GetContainerParentAsElement())) {
307 return EditActionResult(NS_ERROR_UNEXPECTED);
308 } else {
309 leftBlockElement = *atLeftBlockChild.GetContainerParentAsElement();
313 // Do br adjustment.
314 RefPtr<HTMLBRElement> invisibleBRElementBeforeLeftBlockElement =
315 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
316 aHTMLEditor.GetActiveEditingHost(), atLeftBlockChild);
317 NS_ASSERTION(
318 aPrecedingInvisibleBRElement == invisibleBRElementBeforeLeftBlockElement,
319 "The preceding invisible BR element computation was different");
320 EditActionResult ret(NS_OK);
321 // NOTE: Keep syncing with CanMergeLeftAndRightBlockElements() of
322 // AutoInclusiveAncestorBlockElementsJoiner.
323 if (aListElementTagName.isSome()) {
324 // XXX Why do we ignore the error from MoveChildrenWithTransaction()?
325 MOZ_ASSERT(originalLeftBlockElement == atLeftBlockChild.GetContainer(),
326 "This is not guaranteed, but assumed");
327 #ifdef DEBUG
328 Result<bool, nsresult> rightBlockHasContent =
329 aHTMLEditor.CanMoveChildren(aRightBlockElement, aLeftBlockElement);
330 #endif // #ifdef DEBUG
331 MoveNodeResult moveNodeResult = aHTMLEditor.MoveChildrenWithTransaction(
332 aRightBlockElement, EditorDOMPoint(atLeftBlockChild.GetContainer(),
333 atLeftBlockChild.Offset()));
334 if (NS_WARN_IF(moveNodeResult.EditorDestroyed())) {
335 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
337 NS_WARNING_ASSERTION(
338 moveNodeResult.Succeeded(),
339 "HTMLEditor::MoveChildrenWithTransaction() failed, but ignored");
340 if (moveNodeResult.Succeeded()) {
341 ret |= moveNodeResult;
342 #ifdef DEBUG
343 MOZ_ASSERT(!rightBlockHasContent.isErr());
344 if (rightBlockHasContent.inspect()) {
345 NS_ASSERTION(moveNodeResult.Handled(),
346 "Failed to consider whether moving or not children");
347 } else {
348 NS_ASSERTION(moveNodeResult.Ignored(),
349 "Failed to consider whether moving or not children");
351 #endif // #ifdef DEBUG
353 // atLeftBlockChild was moved to rightListElement. So, it's invalid now.
354 atLeftBlockChild.Clear();
355 } else {
356 // Left block is a parent of right block, and the parent of the previous
357 // visible content. Right block is a child and contains the contents we
358 // want to move.
360 EditorDOMPoint atPreviousContent;
361 if (&aLeftContentInBlock == leftBlockElement) {
362 // We are working with valid HTML, aLeftContentInBlock is a block node,
363 // and is therefore allowed to contain aRightBlockElement. This is the
364 // simple case, we will simply move the content in aRightBlockElement
365 // out of its block.
366 atPreviousContent = atLeftBlockChild;
367 } else {
368 // We try to work as well as possible with HTML that's already invalid.
369 // Although "right block" is a block, and a block must not be contained
370 // in inline elements, reality is that broken documents do exist. The
371 // DIRECT parent of "left NODE" might be an inline element. Previous
372 // versions of this code skipped inline parents until the first block
373 // parent was found (and used "left block" as the destination).
374 // However, in some situations this strategy moves the content to an
375 // unexpected position. (see bug 200416) The new idea is to make the
376 // moving content a sibling, next to the previous visible content.
377 atPreviousContent.Set(&aLeftContentInBlock);
379 // We want to move our content just after the previous visible node.
380 atPreviousContent.AdvanceOffset();
383 MOZ_ASSERT(atPreviousContent.IsSet());
385 // Because we don't want the moving content to receive the style of the
386 // previous content, we split the previous content's style.
388 #ifdef DEBUG
389 Result<bool, nsresult> firstLineHasContent =
390 aHTMLEditor.CanMoveOrDeleteSomethingInHardLine(
391 EditorRawDOMPoint(&aRightBlockElement, 0));
392 #endif // #ifdef DEBUG
394 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
395 // XXX It's odd to continue handling this edit action if there is no
396 // editing host.
397 if (!editingHost || &aLeftContentInBlock != editingHost) {
398 SplitNodeResult splitResult =
399 aHTMLEditor.SplitAncestorStyledInlineElementsAt(atPreviousContent,
400 nullptr, nullptr);
401 if (splitResult.Failed()) {
402 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
403 return EditActionResult(splitResult.Rv());
406 if (splitResult.Handled()) {
407 if (splitResult.GetNextNode()) {
408 atPreviousContent.Set(splitResult.GetNextNode());
409 if (!atPreviousContent.IsSet()) {
410 NS_WARNING("Next node of split point was orphaned");
411 return EditActionResult(NS_ERROR_NULL_POINTER);
413 } else {
414 atPreviousContent = splitResult.SplitPoint();
415 if (!atPreviousContent.IsSet()) {
416 NS_WARNING("Split node was orphaned");
417 return EditActionResult(NS_ERROR_NULL_POINTER);
423 MoveNodeResult moveNodeResult = aHTMLEditor.MoveOneHardLineContents(
424 EditorDOMPoint(&aRightBlockElement, 0), atPreviousContent);
425 if (moveNodeResult.Failed()) {
426 NS_WARNING("HTMLEditor::MoveOneHardLineContents() failed");
427 return EditActionResult(moveNodeResult.Rv());
430 #ifdef DEBUG
431 MOZ_ASSERT(!firstLineHasContent.isErr());
432 if (firstLineHasContent.inspect()) {
433 NS_ASSERTION(moveNodeResult.Handled(),
434 "Failed to consider whether moving or not something");
435 } else {
436 NS_ASSERTION(moveNodeResult.Ignored(),
437 "Failed to consider whether moving or not something");
439 #endif // #ifdef DEBUG
441 ret |= moveNodeResult;
444 if (!invisibleBRElementBeforeLeftBlockElement) {
445 return ret;
448 rv = aHTMLEditor.DeleteNodeWithTransaction(
449 *invisibleBRElementBeforeLeftBlockElement);
450 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
451 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
453 if (NS_FAILED(rv)) {
454 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed, but ignored");
455 return EditActionResult(rv);
457 return EditActionHandled();
460 // static
461 EditActionResult WhiteSpaceVisibilityKeeper::
462 MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
463 HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
464 Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName,
465 const HTMLBRElement* aPrecedingInvisibleBRElement) {
466 MOZ_ASSERT(
467 !EditorUtils::IsDescendantOf(aLeftBlockElement, aRightBlockElement));
468 MOZ_ASSERT(
469 !EditorUtils::IsDescendantOf(aRightBlockElement, aLeftBlockElement));
471 // NOTE: This method may extend deletion range:
472 // - to delete invisible white-spaces at end of aLeftBlockElement
473 // - to delete invisible white-spaces at start of aRightBlockElement
474 // - to delete invisible `<br>` element at end of aLeftBlockElement
476 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
478 // Adjust white-space at block boundaries
479 nsresult rv = WhiteSpaceVisibilityKeeper::
480 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
481 aHTMLEditor,
482 EditorDOMRange(EditorDOMPoint::AtEndOf(aLeftBlockElement),
483 EditorDOMPoint(&aRightBlockElement, 0)));
484 if (NS_FAILED(rv)) {
485 NS_WARNING(
486 "WhiteSpaceVisibilityKeeper::"
487 "MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange() failed");
488 return EditActionResult(rv);
490 // Do br adjustment.
491 RefPtr<HTMLBRElement> invisibleBRElementAtEndOfLeftBlockElement =
492 WSRunScanner::GetPrecedingBRElementUnlessVisibleContentFound(
493 aHTMLEditor.GetActiveEditingHost(),
494 EditorDOMPoint::AtEndOf(aLeftBlockElement));
495 NS_ASSERTION(
496 aPrecedingInvisibleBRElement == invisibleBRElementAtEndOfLeftBlockElement,
497 "The preceding invisible BR element computation was different");
498 EditActionResult ret(NS_OK);
499 if (aListElementTagName.isSome() ||
500 aLeftBlockElement.NodeInfo()->NameAtom() ==
501 aRightBlockElement.NodeInfo()->NameAtom()) {
502 // Nodes are same type. merge them.
503 EditorDOMPoint atFirstChildOfRightNode;
504 nsresult rv = aHTMLEditor.JoinNearestEditableNodesWithTransaction(
505 aLeftBlockElement, aRightBlockElement, &atFirstChildOfRightNode);
506 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
507 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
509 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
510 "HTMLEditor::JoinNearestEditableNodesWithTransaction()"
511 " failed, but ignored");
512 if (aListElementTagName.isSome() && atFirstChildOfRightNode.IsSet()) {
513 CreateElementResult convertListTypeResult =
514 aHTMLEditor.ChangeListElementType(
515 aRightBlockElement, MOZ_KnownLive(*aListElementTagName.ref()),
516 *nsGkAtoms::li);
517 if (NS_WARN_IF(convertListTypeResult.EditorDestroyed())) {
518 return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
520 NS_WARNING_ASSERTION(
521 convertListTypeResult.Succeeded(),
522 "HTMLEditor::ChangeListElementType() failed, but ignored");
524 ret.MarkAsHandled();
525 } else {
526 #ifdef DEBUG
527 Result<bool, nsresult> firstLineHasContent =
528 aHTMLEditor.CanMoveOrDeleteSomethingInHardLine(
529 EditorRawDOMPoint(&aRightBlockElement, 0));
530 #endif // #ifdef DEBUG
532 // Nodes are dissimilar types.
533 MoveNodeResult moveNodeResult = aHTMLEditor.MoveOneHardLineContents(
534 EditorDOMPoint(&aRightBlockElement, 0),
535 EditorDOMPoint(&aLeftBlockElement, 0),
536 HTMLEditor::MoveToEndOfContainer::Yes);
537 if (moveNodeResult.Failed()) {
538 NS_WARNING(
539 "HTMLEditor::MoveOneHardLineContents(MoveToEndOfContainer::Yes) "
540 "failed");
541 return EditActionResult(moveNodeResult.Rv());
544 #ifdef DEBUG
545 MOZ_ASSERT(!firstLineHasContent.isErr());
546 if (firstLineHasContent.inspect()) {
547 NS_ASSERTION(moveNodeResult.Handled(),
548 "Failed to consider whether moving or not something");
549 } else {
550 NS_ASSERTION(moveNodeResult.Ignored(),
551 "Failed to consider whether moving or not something");
553 #endif // #ifdef DEBUG
554 ret |= moveNodeResult;
557 if (!invisibleBRElementAtEndOfLeftBlockElement) {
558 return ret.MarkAsHandled();
561 rv = aHTMLEditor.DeleteNodeWithTransaction(
562 *invisibleBRElementAtEndOfLeftBlockElement);
563 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
564 return ret.SetResult(NS_ERROR_EDITOR_DESTROYED);
566 // XXX In other top level if blocks, the result of
567 // DeleteNodeWithTransaction() is ignored. Why does only this result
568 // is respected?
569 if (NS_FAILED(rv)) {
570 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
571 return EditActionResult(rv);
573 return EditActionHandled();
576 // static
577 Result<RefPtr<Element>, nsresult> WhiteSpaceVisibilityKeeper::InsertBRElement(
578 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert) {
579 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
580 return Err(NS_ERROR_INVALID_ARG);
583 // MOOSE: for now, we always assume non-PRE formatting. Fix this later.
584 // meanwhile, the pre case is handled in HandleInsertText() in
585 // HTMLEditSubActionHandler.cpp
587 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
588 TextFragmentData textFragmentDataAtInsertionPoint(aPointToInsert,
589 editingHost);
590 if (NS_WARN_IF(!textFragmentDataAtInsertionPoint.IsInitialized())) {
591 return Err(NS_ERROR_FAILURE);
593 const EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine =
594 textFragmentDataAtInsertionPoint
595 .GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
596 const EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine =
597 textFragmentDataAtInsertionPoint
598 .GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
599 const Maybe<const VisibleWhiteSpacesData> visibleWhiteSpaces =
600 !invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() ||
601 !invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()
602 ? Some(textFragmentDataAtInsertionPoint.VisibleWhiteSpacesDataRef())
603 : Nothing();
604 const PointPosition pointPositionWithVisibleWhiteSpaces =
605 visibleWhiteSpaces.isSome() && visibleWhiteSpaces.ref().IsInitialized()
606 ? visibleWhiteSpaces.ref().ComparePoint(aPointToInsert)
607 : PointPosition::NotInSameDOMTree;
609 EditorDOMPoint pointToInsert(aPointToInsert);
611 // Some scoping for AutoTrackDOMPoint. This will track our insertion
612 // point while we tweak any surrounding white-space
613 AutoTrackDOMPoint tracker(aHTMLEditor.RangeUpdaterRef(), &pointToInsert);
615 if (invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()) {
616 if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine.Collapsed()) {
617 // XXX Why don't we remove all of the invisible white-spaces?
618 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef() ==
619 pointToInsert);
620 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
621 invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef(),
622 invisibleTrailingWhiteSpaceRangeOfCurrentLine.EndRef(),
623 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
624 if (NS_FAILED(rv)) {
625 NS_WARNING(
626 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
627 return Err(rv);
631 // If new line will start with visible white-spaces, it needs to be start
632 // with an NBSP.
633 else if (pointPositionWithVisibleWhiteSpaces ==
634 PointPosition::StartOfFragment ||
635 pointPositionWithVisibleWhiteSpaces ==
636 PointPosition::MiddleOfFragment) {
637 EditorRawDOMPointInText atNextCharOfInsertionPoint =
638 textFragmentDataAtInsertionPoint.GetInclusiveNextEditableCharPoint(
639 pointToInsert);
640 if (atNextCharOfInsertionPoint.IsSet() &&
641 !atNextCharOfInsertionPoint.IsEndOfContainer() &&
642 atNextCharOfInsertionPoint.IsCharASCIISpace() &&
643 !EditorUtils::IsContentPreformatted(
644 *atNextCharOfInsertionPoint.ContainerAsText())) {
645 EditorRawDOMPointInText atPreviousCharOfNextCharOfInsertionPoint =
646 textFragmentDataAtInsertionPoint.GetPreviousEditableCharPoint(
647 atNextCharOfInsertionPoint);
648 if (!atPreviousCharOfNextCharOfInsertionPoint.IsSet() ||
649 atPreviousCharOfNextCharOfInsertionPoint.IsEndOfContainer() ||
650 !atPreviousCharOfNextCharOfInsertionPoint.IsCharASCIISpace()) {
651 // We are at start of non-nbsps. Convert to a single nbsp.
652 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
653 textFragmentDataAtInsertionPoint
654 .GetEndOfCollapsibleASCIIWhiteSpaces(
655 atNextCharOfInsertionPoint);
656 nsresult rv =
657 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
658 aHTMLEditor,
659 EditorDOMRangeInTexts(atNextCharOfInsertionPoint,
660 endOfCollapsibleASCIIWhiteSpaces),
661 nsDependentSubstring(&kNBSP, 1));
662 if (NS_FAILED(rv)) {
663 NS_WARNING(
664 "WhiteSpaceVisibilityKeeper::"
665 "ReplaceTextAndRemoveEmptyTextNodes() failed");
666 return Err(rv);
672 if (invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned()) {
673 if (!invisibleLeadingWhiteSpaceRangeOfNewLine.Collapsed()) {
674 // XXX Why don't we remove all of the invisible white-spaces?
675 MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef() ==
676 pointToInsert);
677 // XXX If the DOM tree has been changed above,
678 // invisibleLeadingWhiteSpaceRangeOfNewLine may be invalid now.
679 // So, we may do something wrong here.
680 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
681 invisibleLeadingWhiteSpaceRangeOfNewLine.StartRef(),
682 invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef(),
683 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
684 if (NS_FAILED(rv)) {
685 NS_WARNING(
686 "WhiteSpaceVisibilityKeeper::"
687 "DeleteTextAndTextNodesWithTransaction() failed");
688 return Err(rv);
692 // If the `<br>` element is put immediately after an NBSP, it should be
693 // replaced with an ASCII white-space.
694 else if (pointPositionWithVisibleWhiteSpaces ==
695 PointPosition::MiddleOfFragment ||
696 pointPositionWithVisibleWhiteSpaces ==
697 PointPosition::EndOfFragment) {
698 // XXX If the DOM tree has been changed above, pointToInsert` and/or
699 // `visibleWhiteSpaces` may be invalid. So, we may do
700 // something wrong here.
701 EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace =
702 textFragmentDataAtInsertionPoint
703 .GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
704 pointToInsert);
705 if (atNBSPReplacedWithASCIIWhiteSpace.IsSet()) {
706 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
707 nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
708 MOZ_KnownLive(*atNBSPReplacedWithASCIIWhiteSpace.ContainerAsText()),
709 atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
710 if (NS_FAILED(rv)) {
711 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
712 return Err(rv);
718 Result<RefPtr<Element>, nsresult> resultOfInsertingBRElement =
719 aHTMLEditor.InsertBRElementWithTransaction(pointToInsert,
720 nsIEditor::eNone);
721 NS_WARNING_ASSERTION(
722 resultOfInsertingBRElement.isOk(),
723 "HTMLEditor::InsertBRElementWithTransaction(eNone) failed");
724 MOZ_ASSERT_IF(resultOfInsertingBRElement.isOk(),
725 resultOfInsertingBRElement.inspect());
726 return resultOfInsertingBRElement;
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 HTMLEditUtils::GetPreviousSibling(
1206 aContentToDelete, {WalkTreeOption::IgnoreNonEditableNode});
1207 // Delete the node, and join like nodes if appropriate
1208 rv = aHTMLEditor.DeleteNodeWithTransaction(aContentToDelete);
1209 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
1210 return NS_ERROR_EDITOR_DESTROYED;
1212 if (NS_FAILED(rv)) {
1213 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1214 return rv;
1216 // Are they both text nodes? If so, join them!
1217 // XXX This may cause odd behavior if there is non-editable nodes
1218 // around the atomic content.
1219 if (!aCaretPoint.IsInTextNode() || !previousEditableSibling ||
1220 !previousEditableSibling->IsText()) {
1221 return NS_OK;
1224 nsIContent* nextEditableSibling = HTMLEditUtils::GetNextSibling(
1225 *previousEditableSibling, {WalkTreeOption::IgnoreNonEditableNode});
1226 if (aCaretPoint.GetContainer() != nextEditableSibling) {
1227 return NS_OK;
1229 EditorDOMPoint atFirstChildOfRightNode;
1230 rv = aHTMLEditor.JoinNearestEditableNodesWithTransaction(
1231 *previousEditableSibling,
1232 MOZ_KnownLive(*aCaretPoint.GetContainerAsText()),
1233 &atFirstChildOfRightNode);
1234 if (NS_FAILED(rv)) {
1235 NS_WARNING("HTMLEditor::JoinNearestEditableNodesWithTransaction() failed");
1236 return rv;
1238 if (!atFirstChildOfRightNode.IsSet()) {
1239 NS_WARNING(
1240 "HTMLEditor::JoinNearestEditableNodesWithTransaction() didn't return "
1241 "right node position");
1242 return NS_ERROR_FAILURE;
1244 // Fix up selection
1245 rv = aHTMLEditor.CollapseSelectionTo(atFirstChildOfRightNode);
1246 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1247 "HTMLEditor::CollapseSelectionTo() failed");
1248 return rv;
1251 template <typename PT, typename CT>
1252 WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1253 const EditorDOMPointBase<PT, CT>& aPoint) const {
1254 MOZ_ASSERT(aPoint.IsSet());
1256 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1257 return WSScanResult(nullptr, WSType::UnexpectedError);
1260 // If the range has visible text and start of the visible text is before
1261 // aPoint, return previous character in the text.
1262 const VisibleWhiteSpacesData& visibleWhiteSpaces =
1263 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1264 if (visibleWhiteSpaces.IsInitialized() &&
1265 visibleWhiteSpaces.StartRef().IsBefore(aPoint)) {
1266 // If the visible things are not editable, we shouldn't scan "editable"
1267 // things now. Whether keep scanning editable things or not should be
1268 // considered by the caller.
1269 if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
1270 return WSScanResult(aPoint.GetChild(), WSType::SpecialContent);
1272 EditorDOMPointInText atPreviousChar = GetPreviousEditableCharPoint(aPoint);
1273 // When it's a non-empty text node, return it.
1274 if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
1275 MOZ_ASSERT(!atPreviousChar.IsEndOfContainer());
1276 return WSScanResult(atPreviousChar.NextPoint(),
1277 atPreviousChar.IsCharASCIISpaceOrNBSP()
1278 ? WSType::NormalWhiteSpaces
1279 : WSType::NormalText);
1283 // Otherwise, return the start of the range.
1284 if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
1285 TextFragmentDataAtStartRef().StartRef().GetContainer()) {
1286 // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not
1287 // meaningful.
1288 return WSScanResult(TextFragmentDataAtStartRef().GetStartReasonContent(),
1289 TextFragmentDataAtStartRef().StartRawReason());
1291 return WSScanResult(TextFragmentDataAtStartRef().StartRef(),
1292 TextFragmentDataAtStartRef().StartRawReason());
1295 template <typename PT, typename CT>
1296 WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
1297 const EditorDOMPointBase<PT, CT>& aPoint) const {
1298 MOZ_ASSERT(aPoint.IsSet());
1300 if (!TextFragmentDataAtStartRef().IsInitialized()) {
1301 return WSScanResult(nullptr, WSType::UnexpectedError);
1304 // If the range has visible text and aPoint equals or is before the end of the
1305 // visible text, return inclusive next character in the text.
1306 const VisibleWhiteSpacesData& visibleWhiteSpaces =
1307 TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef();
1308 if (visibleWhiteSpaces.IsInitialized() &&
1309 aPoint.EqualsOrIsBefore(visibleWhiteSpaces.EndRef())) {
1310 // If the visible things are not editable, we shouldn't scan "editable"
1311 // things now. Whether keep scanning editable things or not should be
1312 // considered by the caller.
1313 if (aPoint.GetChild() && !aPoint.GetChild()->IsEditable()) {
1314 return WSScanResult(aPoint.GetChild(), WSType::SpecialContent);
1316 EditorDOMPointInText atNextChar = GetInclusiveNextEditableCharPoint(aPoint);
1317 // When it's a non-empty text node, return it.
1318 if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) {
1319 return WSScanResult(
1320 atNextChar,
1321 !atNextChar.IsEndOfContainer() && atNextChar.IsCharASCIISpaceOrNBSP()
1322 ? WSType::NormalWhiteSpaces
1323 : WSType::NormalText);
1327 // Otherwise, return the end of the range.
1328 if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
1329 TextFragmentDataAtStartRef().EndRef().GetContainer()) {
1330 // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not
1331 // meaningful.
1332 return WSScanResult(TextFragmentDataAtStartRef().GetEndReasonContent(),
1333 TextFragmentDataAtStartRef().EndRawReason());
1335 return WSScanResult(TextFragmentDataAtStartRef().EndRef(),
1336 TextFragmentDataAtStartRef().EndRawReason());
1339 template <typename EditorDOMPointType>
1340 WSRunScanner::TextFragmentData::TextFragmentData(
1341 const EditorDOMPointType& aPoint, const Element* aEditingHost)
1342 : mEditingHost(aEditingHost), mIsPreformatted(false) {
1343 if (!aPoint.IsSetAndValid()) {
1344 NS_WARNING("aPoint was invalid");
1345 return;
1347 if (!aPoint.IsInContentNode()) {
1348 NS_WARNING("aPoint was in Document or DocumentFragment");
1349 // I.e., we're try to modify outside of root element. We don't need to
1350 // support such odd case because web apps cannot append text nodes as
1351 // direct child of Document node.
1352 return;
1355 mScanStartPoint = aPoint;
1356 NS_ASSERTION(EditorUtils::IsEditableContent(
1357 *mScanStartPoint.ContainerAsContent(), EditorType::HTML),
1358 "Given content is not editable");
1359 NS_ASSERTION(
1360 mScanStartPoint.ContainerAsContent()->GetAsElementOrParentElement(),
1361 "Given content is not an element and an orphan node");
1362 if (NS_WARN_IF(!EditorUtils::IsEditableContent(
1363 *mScanStartPoint.ContainerAsContent(), EditorType::HTML))) {
1364 return;
1366 Element* editableBlockParentOrTopmostEditableInlineElement = HTMLEditUtils::
1367 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
1368 *mScanStartPoint.ContainerAsContent());
1369 if (!editableBlockParentOrTopmostEditableInlineElement) {
1370 NS_WARNING(
1371 "HTMLEditUtils::"
1372 "GetInclusiveAncestorEditableBlockElementOrInlineEditingHost() "
1373 "couldn't find editing host");
1374 return;
1377 mStart = BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1378 mScanStartPoint, *editableBlockParentOrTopmostEditableInlineElement,
1379 mEditingHost, &mNBSPData);
1380 mEnd = BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1381 mScanStartPoint, *editableBlockParentOrTopmostEditableInlineElement,
1382 mEditingHost, &mNBSPData);
1383 // If scan start point is start/end of preformatted text node, only
1384 // mEnd/mStart crosses a preformatted character so that when one of
1385 // them crosses a preformatted character, this fragment's range is
1386 // preformatted.
1387 // Additionally, if the scan start point is preformatted, and there is
1388 // no text node around it, the range is also preformatted.
1389 mIsPreformatted = mStart.AcrossPreformattedCharacter() ||
1390 mEnd.AcrossPreformattedCharacter() ||
1391 (EditorUtils::IsContentPreformatted(
1392 *mScanStartPoint.ContainerAsContent()) &&
1393 !mStart.IsNormalText() && !mEnd.IsNormalText());
1396 // static
1397 template <typename EditorDOMPointType>
1398 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner::
1399 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1400 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) {
1401 MOZ_ASSERT(aPoint.IsSetAndValid());
1402 MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode());
1403 MOZ_DIAGNOSTIC_ASSERT(
1404 !EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText()));
1406 const nsTextFragment& textFragment = aPoint.ContainerAsText()->TextFragment();
1407 for (uint32_t i = std::min(aPoint.Offset(), textFragment.GetLength()); i;
1408 i--) {
1409 char16_t ch = textFragment.CharAt(i - 1);
1410 if (nsCRT::IsAsciiSpace(ch)) {
1411 continue;
1414 if (ch == HTMLEditUtils::kNBSP) {
1415 if (aNBSPData) {
1416 aNBSPData->NotifyNBSP(
1417 EditorDOMPointInText(aPoint.ContainerAsText(), i - 1),
1418 NoBreakingSpaceData::Scanning::Backward);
1420 continue;
1423 return Some(BoundaryData(EditorDOMPoint(aPoint.ContainerAsText(), i),
1424 *aPoint.ContainerAsText(), WSType::NormalText,
1425 Preformatted::No));
1428 return Nothing();
1431 // static
1432 template <typename EditorDOMPointType>
1433 WSRunScanner::TextFragmentData::BoundaryData WSRunScanner::TextFragmentData::
1434 BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1435 const EditorDOMPointType& aPoint,
1436 const Element& aEditableBlockParentOrTopmostEditableInlineContent,
1437 const Element* aEditingHost, NoBreakingSpaceData* aNBSPData) {
1438 MOZ_ASSERT(aPoint.IsSetAndValid());
1440 if (aPoint.IsInTextNode() && !aPoint.IsStartOfContainer()) {
1441 // If the point is in a text node which is preformatted, we should return
1442 // the point as a visible character point.
1443 if (EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText())) {
1444 return BoundaryData(aPoint, *aPoint.ContainerAsText(), WSType::NormalText,
1445 Preformatted::Yes);
1447 // If the text node is not preformatted, we should look for its preceding
1448 // characters.
1449 Maybe<BoundaryData> startInTextNode =
1450 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(aPoint,
1451 aNBSPData);
1452 if (startInTextNode.isSome()) {
1453 return startInTextNode.ref();
1455 // The text node does not have visible character, let's keep scanning
1456 // preceding nodes.
1457 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1458 EditorDOMPoint(aPoint.ContainerAsText(), 0),
1459 aEditableBlockParentOrTopmostEditableInlineContent, aEditingHost,
1460 aNBSPData);
1463 // Then, we need to check previous leaf node.
1464 nsIContent* previousLeafContentOrBlock =
1465 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
1466 aPoint, aEditableBlockParentOrTopmostEditableInlineContent,
1467 {LeafNodeType::LeafNodeOrNonEditableNode}, aEditingHost);
1468 if (!previousLeafContentOrBlock) {
1469 // no prior node means we exhausted
1470 // aEditableBlockParentOrTopmostEditableInlineContent
1471 // mReasonContent can be either a block element or any non-editable
1472 // content in this case.
1473 return BoundaryData(aPoint,
1474 const_cast<Element&>(
1475 aEditableBlockParentOrTopmostEditableInlineContent),
1476 WSType::CurrentBlockBoundary, Preformatted::No);
1479 if (HTMLEditUtils::IsBlockElement(*previousLeafContentOrBlock)) {
1480 return BoundaryData(aPoint, *previousLeafContentOrBlock,
1481 WSType::OtherBlockBoundary, Preformatted::No);
1484 if (!previousLeafContentOrBlock->IsText() ||
1485 !previousLeafContentOrBlock->IsEditable()) {
1486 // it's a break or a special node, like <img>, that is not a block and
1487 // not a break but still serves as a terminator to ws runs.
1488 return BoundaryData(aPoint, *previousLeafContentOrBlock,
1489 previousLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
1490 ? WSType::BRElement
1491 : WSType::SpecialContent,
1492 Preformatted::No);
1495 if (!previousLeafContentOrBlock->AsText()->TextLength()) {
1496 // If it's an empty text node, keep looking for its previous leaf content.
1497 // Note that even if the empty text node is preformatted, we should keep
1498 // looking for the previous one.
1499 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1500 EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0),
1501 aEditableBlockParentOrTopmostEditableInlineContent, aEditingHost,
1502 aNBSPData);
1505 if (EditorUtils::IsContentPreformatted(*previousLeafContentOrBlock)) {
1506 // If the previous text node is preformatted and not empty, we should return
1507 // its end as found a visible character. Note that we stop scanning
1508 // collapsible white-spaces due to reaching preformatted non-empty text
1509 // node. I.e., the following text node might be not preformatted.
1510 return BoundaryData(EditorDOMPoint::AtEndOf(*previousLeafContentOrBlock),
1511 *previousLeafContentOrBlock, WSType::NormalText,
1512 Preformatted::No);
1515 Maybe<BoundaryData> startInTextNode =
1516 BoundaryData::ScanCollapsibleWhiteSpaceStartInTextNode(
1517 EditorDOMPointInText::AtEndOf(*previousLeafContentOrBlock->AsText()),
1518 aNBSPData);
1519 if (startInTextNode.isSome()) {
1520 return startInTextNode.ref();
1523 // The text node does not have visible character, let's keep scanning
1524 // preceding nodes.
1525 return BoundaryData::ScanCollapsibleWhiteSpaceStartFrom(
1526 EditorDOMPointInText(previousLeafContentOrBlock->AsText(), 0),
1527 aEditableBlockParentOrTopmostEditableInlineContent, aEditingHost,
1528 aNBSPData);
1531 // static
1532 template <typename EditorDOMPointType>
1533 Maybe<WSRunScanner::TextFragmentData::BoundaryData> WSRunScanner::
1534 TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1535 const EditorDOMPointType& aPoint, NoBreakingSpaceData* aNBSPData) {
1536 MOZ_ASSERT(aPoint.IsSetAndValid());
1537 MOZ_DIAGNOSTIC_ASSERT(aPoint.IsInTextNode());
1538 MOZ_DIAGNOSTIC_ASSERT(
1539 !EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText()));
1541 const nsTextFragment& textFragment = aPoint.ContainerAsText()->TextFragment();
1542 for (uint32_t i = aPoint.Offset(); i < textFragment.GetLength(); i++) {
1543 char16_t ch = textFragment.CharAt(i);
1544 if (nsCRT::IsAsciiSpace(ch)) {
1545 continue;
1548 if (ch == HTMLEditUtils::kNBSP) {
1549 if (aNBSPData) {
1550 aNBSPData->NotifyNBSP(EditorDOMPointInText(aPoint.ContainerAsText(), i),
1551 NoBreakingSpaceData::Scanning::Forward);
1553 continue;
1556 return Some(BoundaryData(EditorDOMPoint(aPoint.ContainerAsText(), i),
1557 *aPoint.ContainerAsText(), WSType::NormalText,
1558 Preformatted::No));
1561 return Nothing();
1564 // static
1565 template <typename EditorDOMPointType>
1566 WSRunScanner::TextFragmentData::BoundaryData
1567 WSRunScanner::TextFragmentData::BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1568 const EditorDOMPointType& aPoint,
1569 const Element& aEditableBlockParentOrTopmostEditableInlineElement,
1570 const Element* aEditingHost, NoBreakingSpaceData* aNBSPData) {
1571 MOZ_ASSERT(aPoint.IsSetAndValid());
1573 if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer()) {
1574 // If the point is in a text node which is preformatted, we should return
1575 // the point as a visible character point.
1576 if (EditorUtils::IsContentPreformatted(*aPoint.ContainerAsText())) {
1577 return BoundaryData(aPoint, *aPoint.ContainerAsText(), WSType::NormalText,
1578 Preformatted::Yes);
1580 // If the text node is not preformatted, we should look for inclusive
1581 // next characters.
1582 Maybe<BoundaryData> endInTextNode =
1583 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(aPoint, aNBSPData);
1584 if (endInTextNode.isSome()) {
1585 return endInTextNode.ref();
1587 // The text node does not have visible character, let's keep scanning
1588 // following nodes.
1589 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1590 EditorDOMPointInText::AtEndOf(*aPoint.ContainerAsText()),
1591 aEditableBlockParentOrTopmostEditableInlineElement, aEditingHost,
1592 aNBSPData);
1595 // Then, we need to check next leaf node.
1596 nsIContent* nextLeafContentOrBlock =
1597 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
1598 aPoint, aEditableBlockParentOrTopmostEditableInlineElement,
1599 {LeafNodeType::LeafNodeOrNonEditableNode}, aEditingHost);
1600 if (!nextLeafContentOrBlock) {
1601 // no next node means we exhausted
1602 // aEditableBlockParentOrTopmostEditableInlineElement
1603 // mReasonContent can be either a block element or any non-editable
1604 // content in this case.
1605 return BoundaryData(aPoint,
1606 const_cast<Element&>(
1607 aEditableBlockParentOrTopmostEditableInlineElement),
1608 WSType::CurrentBlockBoundary, Preformatted::No);
1611 if (HTMLEditUtils::IsBlockElement(*nextLeafContentOrBlock)) {
1612 // we encountered a new block. therefore no more ws.
1613 return BoundaryData(aPoint, *nextLeafContentOrBlock,
1614 WSType::OtherBlockBoundary, Preformatted::No);
1617 if (!nextLeafContentOrBlock->IsText() ||
1618 !nextLeafContentOrBlock->IsEditable()) {
1619 // we encountered a break or a special node, like <img>,
1620 // that is not a block and not a break but still
1621 // serves as a terminator to ws runs.
1622 return BoundaryData(aPoint, *nextLeafContentOrBlock,
1623 nextLeafContentOrBlock->IsHTMLElement(nsGkAtoms::br)
1624 ? WSType::BRElement
1625 : WSType::SpecialContent,
1626 Preformatted::No);
1629 if (!nextLeafContentOrBlock->AsText()->TextFragment().GetLength()) {
1630 // If it's an empty text node, keep looking for its next leaf content.
1631 // Note that even if the empty text node is preformatted, we should keep
1632 // looking for the next one.
1633 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1634 EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0),
1635 aEditableBlockParentOrTopmostEditableInlineElement, aEditingHost,
1636 aNBSPData);
1639 if (EditorUtils::IsContentPreformatted(*nextLeafContentOrBlock)) {
1640 // If the next text node is preformatted and not empty, we should return
1641 // its start as found a visible character. Note that we stop scanning
1642 // collapsible white-spaces due to reaching preformatted non-empty text
1643 // node. I.e., the following text node might be not preformatted.
1644 return BoundaryData(EditorDOMPoint(nextLeafContentOrBlock, 0),
1645 *nextLeafContentOrBlock, WSType::NormalText,
1646 Preformatted::No);
1649 Maybe<BoundaryData> endInTextNode =
1650 BoundaryData::ScanCollapsibleWhiteSpaceEndInTextNode(
1651 EditorDOMPointInText(nextLeafContentOrBlock->AsText(), 0), aNBSPData);
1652 if (endInTextNode.isSome()) {
1653 return endInTextNode.ref();
1656 // The text node does not have visible character, let's keep scanning
1657 // following nodes.
1658 return BoundaryData::ScanCollapsibleWhiteSpaceEndFrom(
1659 EditorDOMPointInText::AtEndOf(*nextLeafContentOrBlock->AsText()),
1660 aEditableBlockParentOrTopmostEditableInlineElement, aEditingHost,
1661 aNBSPData);
1664 const EditorDOMRange&
1665 WSRunScanner::TextFragmentData::InvisibleLeadingWhiteSpaceRangeRef() const {
1666 if (mLeadingWhiteSpaceRange.isSome()) {
1667 return mLeadingWhiteSpaceRange.ref();
1670 // If it's preformatted or not start of line, the range is not invisible
1671 // leading white-spaces.
1672 if (!StartsFromHardLineBreak()) {
1673 mLeadingWhiteSpaceRange.emplace();
1674 return mLeadingWhiteSpaceRange.ref();
1677 // If there is no NBSP, all of the given range is leading white-spaces.
1678 // Note that this result may be collapsed if there is no leading white-spaces.
1679 if (!mNBSPData.FoundNBSP()) {
1680 MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
1681 mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
1682 return mLeadingWhiteSpaceRange.ref();
1685 MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
1687 // Even if the first NBSP is the start, i.e., there is no invisible leading
1688 // white-space, return collapsed range.
1689 mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mNBSPData.FirstPointRef());
1690 return mLeadingWhiteSpaceRange.ref();
1693 const EditorDOMRange&
1694 WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const {
1695 if (mTrailingWhiteSpaceRange.isSome()) {
1696 return mTrailingWhiteSpaceRange.ref();
1699 // If it's preformatted or not immediately before block boundary, the range is
1700 // not invisible trailing white-spaces. Note that collapsible white-spaces
1701 // before a `<br>` element is visible.
1702 if (!EndsByBlockBoundary()) {
1703 mTrailingWhiteSpaceRange.emplace();
1704 return mTrailingWhiteSpaceRange.ref();
1707 // If there is no NBSP, all of the given range is trailing white-spaces.
1708 // Note that this result may be collapsed if there is no trailing white-
1709 // spaces.
1710 if (!mNBSPData.FoundNBSP()) {
1711 MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
1712 mTrailingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
1713 return mTrailingWhiteSpaceRange.ref();
1716 MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
1718 // If last NBSP is immediately before the end, there is no trailing white-
1719 // spaces.
1720 if (mEnd.PointRef().IsSet() &&
1721 mNBSPData.LastPointRef().GetContainer() ==
1722 mEnd.PointRef().GetContainer() &&
1723 mNBSPData.LastPointRef().Offset() == mEnd.PointRef().Offset() - 1) {
1724 mTrailingWhiteSpaceRange.emplace();
1725 return mTrailingWhiteSpaceRange.ref();
1728 // Otherwise, the may be some trailing white-spaces.
1729 MOZ_ASSERT(!mNBSPData.LastPointRef().IsEndOfContainer());
1730 mTrailingWhiteSpaceRange.emplace(mNBSPData.LastPointRef().NextPoint(),
1731 mEnd.PointRef());
1732 return mTrailingWhiteSpaceRange.ref();
1735 EditorDOMRangeInTexts
1736 WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts(
1737 const EditorDOMRange& aRange) const {
1738 if (!aRange.IsPositioned()) {
1739 return EditorDOMRangeInTexts();
1741 if (aRange.Collapsed()) {
1742 // If collapsed, we can do nothing.
1743 return EditorDOMRangeInTexts();
1745 if (aRange.IsInTextNodes()) {
1746 // Note that this may return a range which don't include any invisible
1747 // white-spaces due to empty text nodes.
1748 return aRange.GetAsInTexts();
1751 EditorDOMPointInText firstPoint =
1752 aRange.StartRef().IsInTextNode()
1753 ? aRange.StartRef().AsInText()
1754 : GetInclusiveNextEditableCharPoint(aRange.StartRef());
1755 if (!firstPoint.IsSet()) {
1756 return EditorDOMRangeInTexts();
1758 EditorDOMPointInText endPoint;
1759 if (aRange.EndRef().IsInTextNode()) {
1760 endPoint = aRange.EndRef().AsInText();
1761 } else {
1762 // FYI: GetPreviousEditableCharPoint() returns last character's point
1763 // of preceding text node if it's not empty, but we need end of
1764 // the text node here.
1765 endPoint = GetPreviousEditableCharPoint(aRange.EndRef());
1766 if (endPoint.IsSet() && endPoint.IsAtLastContent()) {
1767 MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset());
1770 if (!endPoint.IsSet() || firstPoint == endPoint) {
1771 return EditorDOMRangeInTexts();
1773 return EditorDOMRangeInTexts(firstPoint, endPoint);
1776 const WSRunScanner::VisibleWhiteSpacesData&
1777 WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const {
1778 if (mVisibleWhiteSpacesData.isSome()) {
1779 return mVisibleWhiteSpacesData.ref();
1782 if (IsPreformattedOrSurrondedByVisibleContent()) {
1783 VisibleWhiteSpacesData visibleWhiteSpaces;
1784 if (mStart.PointRef().IsSet()) {
1785 visibleWhiteSpaces.SetStartPoint(mStart.PointRef());
1787 visibleWhiteSpaces.SetStartFrom(mStart.RawReason());
1788 if (mEnd.PointRef().IsSet()) {
1789 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
1791 visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
1792 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
1793 return mVisibleWhiteSpacesData.ref();
1796 // If all of the range is invisible leading or trailing white-spaces,
1797 // there is no visible content.
1798 const EditorDOMRange& leadingWhiteSpaceRange =
1799 InvisibleLeadingWhiteSpaceRangeRef();
1800 const bool maybeHaveLeadingWhiteSpaces =
1801 leadingWhiteSpaceRange.StartRef().IsSet() ||
1802 leadingWhiteSpaceRange.EndRef().IsSet();
1803 if (maybeHaveLeadingWhiteSpaces &&
1804 leadingWhiteSpaceRange.StartRef() == mStart.PointRef() &&
1805 leadingWhiteSpaceRange.EndRef() == mEnd.PointRef()) {
1806 mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData());
1807 return mVisibleWhiteSpacesData.ref();
1809 const EditorDOMRange& trailingWhiteSpaceRange =
1810 InvisibleTrailingWhiteSpaceRangeRef();
1811 const bool maybeHaveTrailingWhiteSpaces =
1812 trailingWhiteSpaceRange.StartRef().IsSet() ||
1813 trailingWhiteSpaceRange.EndRef().IsSet();
1814 if (maybeHaveTrailingWhiteSpaces &&
1815 trailingWhiteSpaceRange.StartRef() == mStart.PointRef() &&
1816 trailingWhiteSpaceRange.EndRef() == mEnd.PointRef()) {
1817 mVisibleWhiteSpacesData.emplace(VisibleWhiteSpacesData());
1818 return mVisibleWhiteSpacesData.ref();
1821 if (!StartsFromHardLineBreak()) {
1822 VisibleWhiteSpacesData visibleWhiteSpaces;
1823 if (mStart.PointRef().IsSet()) {
1824 visibleWhiteSpaces.SetStartPoint(mStart.PointRef());
1826 visibleWhiteSpaces.SetStartFrom(mStart.RawReason());
1827 if (!maybeHaveTrailingWhiteSpaces) {
1828 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
1829 visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
1830 mVisibleWhiteSpacesData = Some(visibleWhiteSpaces);
1831 return mVisibleWhiteSpacesData.ref();
1833 if (trailingWhiteSpaceRange.StartRef().IsSet()) {
1834 visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef());
1836 visibleWhiteSpaces.SetEndByTrailingWhiteSpaces();
1837 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
1838 return mVisibleWhiteSpacesData.ref();
1841 MOZ_ASSERT(StartsFromHardLineBreak());
1842 MOZ_ASSERT(maybeHaveLeadingWhiteSpaces);
1844 VisibleWhiteSpacesData visibleWhiteSpaces;
1845 if (leadingWhiteSpaceRange.EndRef().IsSet()) {
1846 visibleWhiteSpaces.SetStartPoint(leadingWhiteSpaceRange.EndRef());
1848 visibleWhiteSpaces.SetStartFromLeadingWhiteSpaces();
1849 if (!EndsByBlockBoundary()) {
1850 // then no trailing ws. this normal run ends the overall ws run.
1851 if (mEnd.PointRef().IsSet()) {
1852 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
1854 visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
1855 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
1856 return mVisibleWhiteSpacesData.ref();
1859 MOZ_ASSERT(EndsByBlockBoundary());
1861 if (!maybeHaveTrailingWhiteSpaces) {
1862 // normal ws runs right up to adjacent block (nbsp next to block)
1863 visibleWhiteSpaces.SetEndPoint(mEnd.PointRef());
1864 visibleWhiteSpaces.SetEndBy(mEnd.RawReason());
1865 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
1866 return mVisibleWhiteSpacesData.ref();
1869 if (trailingWhiteSpaceRange.StartRef().IsSet()) {
1870 visibleWhiteSpaces.SetEndPoint(trailingWhiteSpaceRange.StartRef());
1872 visibleWhiteSpaces.SetEndByTrailingWhiteSpaces();
1873 mVisibleWhiteSpacesData.emplace(visibleWhiteSpaces);
1874 return mVisibleWhiteSpacesData.ref();
1877 // static
1878 nsresult WhiteSpaceVisibilityKeeper::
1879 MakeSureToKeepVisibleStateOfWhiteSpacesAroundDeletingRange(
1880 HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete) {
1881 if (NS_WARN_IF(!aRangeToDelete.IsPositionedAndValid()) ||
1882 NS_WARN_IF(!aRangeToDelete.IsInContentNodes())) {
1883 return NS_ERROR_INVALID_ARG;
1886 EditorDOMRange rangeToDelete(aRangeToDelete);
1887 bool mayBecomeUnexpectedDOMTree = aHTMLEditor.MayHaveMutationEventListeners(
1888 NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED |
1889 NS_EVENT_BITS_MUTATION_NODEREMOVED |
1890 NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT |
1891 NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED);
1893 RefPtr<Element> editingHost = aHTMLEditor.GetActiveEditingHost();
1894 TextFragmentData textFragmentDataAtStart(rangeToDelete.StartRef(),
1895 editingHost);
1896 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
1897 return NS_ERROR_FAILURE;
1899 TextFragmentData textFragmentDataAtEnd(rangeToDelete.EndRef(), editingHost);
1900 if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
1901 return NS_ERROR_FAILURE;
1903 ReplaceRangeData replaceRangeDataAtEnd =
1904 textFragmentDataAtEnd.GetReplaceRangeDataAtEndOfDeletionRange(
1905 textFragmentDataAtStart);
1906 if (replaceRangeDataAtEnd.IsSet() && !replaceRangeDataAtEnd.Collapsed()) {
1907 MOZ_ASSERT(rangeToDelete.EndRef().EqualsOrIsBefore(
1908 replaceRangeDataAtEnd.EndRef()));
1909 // If there is some text after deleting range, replacing range start must
1910 // equal or be before end of the deleting range.
1911 MOZ_ASSERT_IF(rangeToDelete.EndRef().IsInTextNode() &&
1912 !rangeToDelete.EndRef().IsEndOfContainer(),
1913 replaceRangeDataAtEnd.StartRef().EqualsOrIsBefore(
1914 rangeToDelete.EndRef()));
1915 // If the deleting range end is end of a text node, the replacing range
1916 // starts with another node if the following text node starts with white-
1917 // spaces.
1918 MOZ_ASSERT_IF(rangeToDelete.EndRef().IsInTextNode() &&
1919 rangeToDelete.EndRef().IsEndOfContainer(),
1920 rangeToDelete.EndRef() == replaceRangeDataAtEnd.StartRef() ||
1921 replaceRangeDataAtEnd.StartRef().IsStartOfContainer());
1922 MOZ_ASSERT(rangeToDelete.StartRef().EqualsOrIsBefore(
1923 replaceRangeDataAtEnd.StartRef()));
1924 if (!replaceRangeDataAtEnd.HasReplaceString()) {
1925 EditorDOMPoint startToDelete(aRangeToDelete.StartRef());
1926 EditorDOMPoint endToDelete(replaceRangeDataAtEnd.StartRef());
1928 AutoEditorDOMPointChildInvalidator lockOffsetOfStart(startToDelete);
1929 AutoEditorDOMPointChildInvalidator lockOffsetOfEnd(endToDelete);
1930 AutoTrackDOMPoint trackStartToDelete(aHTMLEditor.RangeUpdaterRef(),
1931 &startToDelete);
1932 AutoTrackDOMPoint trackEndToDelete(aHTMLEditor.RangeUpdaterRef(),
1933 &endToDelete);
1934 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
1935 replaceRangeDataAtEnd.StartRef(), replaceRangeDataAtEnd.EndRef(),
1936 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
1937 if (NS_FAILED(rv)) {
1938 NS_WARNING(
1939 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
1940 return rv;
1943 if (mayBecomeUnexpectedDOMTree &&
1944 (NS_WARN_IF(!startToDelete.IsSetAndValid()) ||
1945 NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
1946 NS_WARN_IF(!startToDelete.EqualsOrIsBefore(endToDelete)))) {
1947 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1949 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
1950 rangeToDelete.SetStartAndEnd(startToDelete, endToDelete);
1951 } else {
1952 MOZ_ASSERT(replaceRangeDataAtEnd.RangeRef().IsInTextNodes());
1953 EditorDOMPoint startToDelete(aRangeToDelete.StartRef());
1954 EditorDOMPoint endToDelete(replaceRangeDataAtEnd.StartRef());
1956 AutoTrackDOMPoint trackStartToDelete(aHTMLEditor.RangeUpdaterRef(),
1957 &startToDelete);
1958 AutoTrackDOMPoint trackEndToDelete(aHTMLEditor.RangeUpdaterRef(),
1959 &endToDelete);
1960 nsresult rv =
1961 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
1962 aHTMLEditor, replaceRangeDataAtEnd.RangeRef().AsInTexts(),
1963 replaceRangeDataAtEnd.ReplaceStringRef());
1964 if (NS_FAILED(rv)) {
1965 NS_WARNING(
1966 "WhiteSpaceVisibilityKeeper::"
1967 "MakeSureToKeepVisibleStateOfWhiteSpacesAtEndOfDeletingRange() "
1968 "failed");
1969 return rv;
1972 if (mayBecomeUnexpectedDOMTree &&
1973 (NS_WARN_IF(!startToDelete.IsSetAndValid()) ||
1974 NS_WARN_IF(!endToDelete.IsSetAndValid()) ||
1975 NS_WARN_IF(!startToDelete.EqualsOrIsBefore(endToDelete)))) {
1976 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1978 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
1979 rangeToDelete.SetStartAndEnd(startToDelete, endToDelete);
1982 if (mayBecomeUnexpectedDOMTree) {
1983 // If focus is changed by mutation event listeners, we should stop
1984 // handling this edit action.
1985 if (editingHost != aHTMLEditor.GetActiveEditingHost()) {
1986 NS_WARNING("Active editing host was changed");
1987 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1989 if (!rangeToDelete.IsInContentNodes()) {
1990 NS_WARNING("The modified range was not in content");
1991 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
1993 // If the DOM tree might be changed by mutation event listeners, we
1994 // should retrieve the latest data for avoiding to delete/replace
1995 // unexpected range.
1996 textFragmentDataAtStart =
1997 TextFragmentData(rangeToDelete.StartRef(), editingHost);
1998 textFragmentDataAtEnd =
1999 TextFragmentData(rangeToDelete.EndRef(), editingHost);
2002 ReplaceRangeData replaceRangeDataAtStart =
2003 textFragmentDataAtStart.GetReplaceRangeDataAtStartOfDeletionRange(
2004 textFragmentDataAtEnd);
2005 if (!replaceRangeDataAtStart.IsSet() || replaceRangeDataAtStart.Collapsed()) {
2006 return NS_OK;
2008 if (!replaceRangeDataAtStart.HasReplaceString()) {
2009 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
2010 replaceRangeDataAtStart.StartRef(), replaceRangeDataAtStart.EndRef(),
2011 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
2012 // XXX Should we validate the range for making this return
2013 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2014 NS_WARNING_ASSERTION(
2015 NS_SUCCEEDED(rv),
2016 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2017 return rv;
2019 MOZ_ASSERT(replaceRangeDataAtStart.RangeRef().IsInTextNodes());
2020 nsresult rv = WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2021 aHTMLEditor, replaceRangeDataAtStart.RangeRef().AsInTexts(),
2022 replaceRangeDataAtStart.ReplaceStringRef());
2023 // XXX Should we validate the range for making this return
2024 // NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE in this case?
2025 NS_WARNING_ASSERTION(
2026 NS_SUCCEEDED(rv),
2027 "WhiteSpaceVisibilityKeeper::"
2028 "MakeSureToKeepVisibleStateOfWhiteSpacesAtStartOfDeletingRange() failed");
2029 return rv;
2032 ReplaceRangeData
2033 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange(
2034 const TextFragmentData& aTextFragmentDataAtStartToDelete) const {
2035 const EditorDOMPoint& startToDelete =
2036 aTextFragmentDataAtStartToDelete.ScanStartRef();
2037 const EditorDOMPoint& endToDelete = mScanStartPoint;
2039 MOZ_ASSERT(startToDelete.IsSetAndValid());
2040 MOZ_ASSERT(endToDelete.IsSetAndValid());
2041 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
2043 if (EndRef().EqualsOrIsBefore(endToDelete)) {
2044 return ReplaceRangeData();
2047 // If deleting range is followed by invisible trailing white-spaces, we need
2048 // to remove it for making them not visible.
2049 const EditorDOMRange invisibleTrailingWhiteSpaceRangeAtEnd =
2050 GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(endToDelete);
2051 if (invisibleTrailingWhiteSpaceRangeAtEnd.IsPositioned()) {
2052 if (invisibleTrailingWhiteSpaceRangeAtEnd.Collapsed()) {
2053 return ReplaceRangeData();
2055 // XXX Why don't we remove all invisible white-spaces?
2056 MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeAtEnd.StartRef() == endToDelete);
2057 return ReplaceRangeData(invisibleTrailingWhiteSpaceRangeAtEnd, u""_ns);
2060 if (IsPreformatted()) {
2061 return ReplaceRangeData();
2064 // If end of the deleting range is followed by visible white-spaces which
2065 // is not preformatted, we might need to replace the following ASCII
2066 // white-spaces with an NBSP.
2067 const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtEnd =
2068 VisibleWhiteSpacesDataRef();
2069 if (!nonPreformattedVisibleWhiteSpacesAtEnd.IsInitialized()) {
2070 return ReplaceRangeData();
2072 const PointPosition pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd =
2073 nonPreformattedVisibleWhiteSpacesAtEnd.ComparePoint(endToDelete);
2074 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd !=
2075 PointPosition::StartOfFragment &&
2076 pointPositionWithNonPreformattedVisibleWhiteSpacesAtEnd !=
2077 PointPosition::MiddleOfFragment) {
2078 return ReplaceRangeData();
2080 // If start of deleting range follows white-spaces or end of delete
2081 // will be start of a line, the following text cannot start with an
2082 // ASCII white-space for keeping it visible.
2083 if (!aTextFragmentDataAtStartToDelete
2084 .FollowingContentMayBecomeFirstVisibleContent(startToDelete)) {
2085 return ReplaceRangeData();
2087 EditorRawDOMPointInText nextCharOfStartOfEnd =
2088 GetInclusiveNextEditableCharPoint(endToDelete);
2089 if (!nextCharOfStartOfEnd.IsSet() ||
2090 nextCharOfStartOfEnd.IsEndOfContainer() ||
2091 !nextCharOfStartOfEnd.IsCharASCIISpace() ||
2092 EditorUtils::IsContentPreformatted(
2093 *nextCharOfStartOfEnd.ContainerAsText())) {
2094 return ReplaceRangeData();
2096 if (nextCharOfStartOfEnd.IsStartOfContainer() ||
2097 nextCharOfStartOfEnd.IsPreviousCharASCIISpace()) {
2098 nextCharOfStartOfEnd =
2099 aTextFragmentDataAtStartToDelete
2100 .GetFirstASCIIWhiteSpacePointCollapsedTo(nextCharOfStartOfEnd);
2102 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
2103 aTextFragmentDataAtStartToDelete.GetEndOfCollapsibleASCIIWhiteSpaces(
2104 nextCharOfStartOfEnd);
2105 return ReplaceRangeData(nextCharOfStartOfEnd,
2106 endOfCollapsibleASCIIWhiteSpaces,
2107 nsDependentSubstring(&kNBSP, 1));
2110 ReplaceRangeData
2111 WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
2112 const TextFragmentData& aTextFragmentDataAtEndToDelete) const {
2113 const EditorDOMPoint& startToDelete = mScanStartPoint;
2114 const EditorDOMPoint& endToDelete =
2115 aTextFragmentDataAtEndToDelete.ScanStartRef();
2117 MOZ_ASSERT(startToDelete.IsSetAndValid());
2118 MOZ_ASSERT(endToDelete.IsSetAndValid());
2119 MOZ_ASSERT(startToDelete.EqualsOrIsBefore(endToDelete));
2121 if (startToDelete.EqualsOrIsBefore(StartRef())) {
2122 return ReplaceRangeData();
2125 const EditorDOMRange invisibleLeadingWhiteSpaceRangeAtStart =
2126 GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(startToDelete);
2128 // If deleting range follows invisible leading white-spaces, we need to
2129 // remove them for making them not visible.
2130 if (invisibleLeadingWhiteSpaceRangeAtStart.IsPositioned()) {
2131 if (invisibleLeadingWhiteSpaceRangeAtStart.Collapsed()) {
2132 return ReplaceRangeData();
2135 // XXX Why don't we remove all leading white-spaces?
2136 return ReplaceRangeData(invisibleLeadingWhiteSpaceRangeAtStart, u""_ns);
2139 if (IsPreformatted()) {
2140 return ReplaceRangeData();
2143 // If start of the deleting range follows visible white-spaces which is not
2144 // preformatted, we might need to replace previous ASCII white-spaces with
2145 // an NBSP.
2146 const VisibleWhiteSpacesData& nonPreformattedVisibleWhiteSpacesAtStart =
2147 VisibleWhiteSpacesDataRef();
2148 if (!nonPreformattedVisibleWhiteSpacesAtStart.IsInitialized()) {
2149 return ReplaceRangeData();
2151 const PointPosition
2152 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart =
2153 nonPreformattedVisibleWhiteSpacesAtStart.ComparePoint(startToDelete);
2154 if (pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart !=
2155 PointPosition::MiddleOfFragment &&
2156 pointPositionWithNonPreformattedVisibleWhiteSpacesAtStart !=
2157 PointPosition::EndOfFragment) {
2158 return ReplaceRangeData();
2160 // If end of the deleting range is (was) followed by white-spaces or
2161 // previous character of start of deleting range will be immediately
2162 // before a block boundary, the text cannot ends with an ASCII white-space
2163 // for keeping it visible.
2164 if (!aTextFragmentDataAtEndToDelete.PrecedingContentMayBecomeInvisible(
2165 endToDelete)) {
2166 return ReplaceRangeData();
2168 EditorRawDOMPointInText atPreviousCharOfStart =
2169 GetPreviousEditableCharPoint(startToDelete);
2170 if (!atPreviousCharOfStart.IsSet() ||
2171 atPreviousCharOfStart.IsEndOfContainer() ||
2172 !atPreviousCharOfStart.IsCharASCIISpace() ||
2173 EditorUtils::IsContentPreformatted(
2174 *atPreviousCharOfStart.ContainerAsText())) {
2175 return ReplaceRangeData();
2177 if (atPreviousCharOfStart.IsStartOfContainer() ||
2178 atPreviousCharOfStart.IsPreviousCharASCIISpace()) {
2179 atPreviousCharOfStart =
2180 GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart);
2182 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
2183 GetEndOfCollapsibleASCIIWhiteSpaces(atPreviousCharOfStart);
2184 return ReplaceRangeData(atPreviousCharOfStart,
2185 endOfCollapsibleASCIIWhiteSpaces,
2186 nsDependentSubstring(&kNBSP, 1));
2189 // static
2190 nsresult
2191 WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
2192 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit) {
2193 TextFragmentData textFragmentDataAtSplitPoint(
2194 aPointToSplit, aHTMLEditor.GetActiveEditingHost());
2195 if (NS_WARN_IF(!textFragmentDataAtSplitPoint.IsInitialized())) {
2196 return NS_ERROR_FAILURE;
2199 // used to prepare white-space sequence to be split across two blocks.
2200 // The main issue here is make sure white-spaces around the split point
2201 // doesn't end up becoming non-significant leading or trailing ws after
2202 // the split.
2203 const VisibleWhiteSpacesData& visibleWhiteSpaces =
2204 textFragmentDataAtSplitPoint.VisibleWhiteSpacesDataRef();
2205 if (!visibleWhiteSpaces.IsInitialized()) {
2206 return NS_OK; // No visible white-space sequence.
2209 PointPosition pointPositionWithVisibleWhiteSpaces =
2210 visibleWhiteSpaces.ComparePoint(aPointToSplit);
2212 // XXX If we split white-space sequence, the following code modify the DOM
2213 // tree twice. This is not reasonable and the latter change may touch
2214 // wrong position. We should do this once.
2216 // If we insert block boundary to start or middle of the white-space sequence,
2217 // the character at the insertion point needs to be an NBSP.
2218 EditorDOMPoint pointToSplit(aPointToSplit);
2219 if (pointPositionWithVisibleWhiteSpaces == PointPosition::StartOfFragment ||
2220 pointPositionWithVisibleWhiteSpaces == PointPosition::MiddleOfFragment) {
2221 EditorRawDOMPointInText atNextCharOfStart =
2222 textFragmentDataAtSplitPoint.GetInclusiveNextEditableCharPoint(
2223 pointToSplit);
2224 if (atNextCharOfStart.IsSet() && !atNextCharOfStart.IsEndOfContainer() &&
2225 atNextCharOfStart.IsCharASCIISpace() &&
2226 !EditorUtils::IsContentPreformatted(
2227 *atNextCharOfStart.ContainerAsText())) {
2228 // pointToSplit will be referred bellow so that we need to keep
2229 // it a valid point.
2230 AutoEditorDOMPointChildInvalidator forgetChild(pointToSplit);
2231 if (atNextCharOfStart.IsStartOfContainer() ||
2232 atNextCharOfStart.IsPreviousCharASCIISpace()) {
2233 atNextCharOfStart =
2234 textFragmentDataAtSplitPoint
2235 .GetFirstASCIIWhiteSpacePointCollapsedTo(atNextCharOfStart);
2237 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
2238 textFragmentDataAtSplitPoint.GetEndOfCollapsibleASCIIWhiteSpaces(
2239 atNextCharOfStart);
2240 nsresult rv =
2241 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2242 aHTMLEditor,
2243 EditorDOMRangeInTexts(atNextCharOfStart,
2244 endOfCollapsibleASCIIWhiteSpaces),
2245 nsDependentSubstring(&kNBSP, 1));
2246 if (NS_FAILED(rv)) {
2247 NS_WARNING(
2248 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2249 "failed");
2250 return rv;
2255 // If we insert block boundary to middle of or end of the white-space
2256 // sequence, the previous character at the insertion point needs to be an
2257 // NBSP.
2258 if (pointPositionWithVisibleWhiteSpaces == PointPosition::MiddleOfFragment ||
2259 pointPositionWithVisibleWhiteSpaces == PointPosition::EndOfFragment) {
2260 EditorRawDOMPointInText atPreviousCharOfStart =
2261 textFragmentDataAtSplitPoint.GetPreviousEditableCharPoint(pointToSplit);
2262 if (atPreviousCharOfStart.IsSet() &&
2263 !atPreviousCharOfStart.IsEndOfContainer() &&
2264 atPreviousCharOfStart.IsCharASCIISpace() &&
2265 !EditorUtils::IsContentPreformatted(
2266 *atPreviousCharOfStart.ContainerAsText())) {
2267 if (atPreviousCharOfStart.IsStartOfContainer() ||
2268 atPreviousCharOfStart.IsPreviousCharASCIISpace()) {
2269 atPreviousCharOfStart =
2270 textFragmentDataAtSplitPoint
2271 .GetFirstASCIIWhiteSpacePointCollapsedTo(atPreviousCharOfStart);
2273 EditorRawDOMPointInText endOfCollapsibleASCIIWhiteSpaces =
2274 textFragmentDataAtSplitPoint.GetEndOfCollapsibleASCIIWhiteSpaces(
2275 atPreviousCharOfStart);
2276 nsresult rv =
2277 WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2278 aHTMLEditor,
2279 EditorDOMRangeInTexts(atPreviousCharOfStart,
2280 endOfCollapsibleASCIIWhiteSpaces),
2281 nsDependentSubstring(&kNBSP, 1));
2282 if (NS_FAILED(rv)) {
2283 NS_WARNING(
2284 "WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes() "
2285 "failed");
2286 return rv;
2290 return NS_OK;
2293 template <typename PT, typename CT>
2294 EditorDOMPointInText
2295 WSRunScanner::TextFragmentData::GetInclusiveNextEditableCharPoint(
2296 const EditorDOMPointBase<PT, CT>& aPoint) const {
2297 MOZ_ASSERT(aPoint.IsSetAndValid());
2299 if (NS_WARN_IF(!aPoint.IsInContentNode()) ||
2300 NS_WARN_IF(!mScanStartPoint.IsInContentNode())) {
2301 return EditorDOMPointInText();
2304 EditorRawDOMPoint point;
2305 if (nsIContent* child =
2306 aPoint.CanContainerHaveChildren() ? aPoint.GetChild() : nullptr) {
2307 nsIContent* leafContent = child->HasChildren()
2308 ? HTMLEditUtils::GetFirstLeafContent(
2309 *child, {LeafNodeType::OnlyLeafNode})
2310 : child;
2311 if (NS_WARN_IF(!leafContent)) {
2312 return EditorDOMPointInText();
2314 point.Set(leafContent, 0);
2315 } else {
2316 point = aPoint;
2319 // If it points a character in a text node, return it.
2320 // XXX For the performance, this does not check whether the container
2321 // is outside of our range.
2322 if (point.IsInTextNode() && point.GetContainer()->IsEditable() &&
2323 !point.IsEndOfContainer()) {
2324 return EditorDOMPointInText(point.ContainerAsText(), point.Offset());
2327 if (point.GetContainer() == GetEndReasonContent()) {
2328 return EditorDOMPointInText();
2331 NS_ASSERTION(EditorUtils::IsEditableContent(
2332 *mScanStartPoint.ContainerAsContent(), EditorType::HTML),
2333 "Given content is not editable");
2334 NS_ASSERTION(
2335 mScanStartPoint.ContainerAsContent()->GetAsElementOrParentElement(),
2336 "Given content is not an element and an orphan node");
2337 nsIContent* editableBlockParentOrTopmostEditableInlineContent =
2338 mScanStartPoint.ContainerAsContent() &&
2339 EditorUtils::IsEditableContent(
2340 *mScanStartPoint.ContainerAsContent(), EditorType::HTML)
2341 ? HTMLEditUtils::
2342 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2343 *mScanStartPoint.ContainerAsContent())
2344 : nullptr;
2345 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent)) {
2346 // Meaning that the container of `mScanStartPoint` is not editable.
2347 editableBlockParentOrTopmostEditableInlineContent =
2348 mScanStartPoint.ContainerAsContent();
2351 for (nsIContent* nextContent =
2352 HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2353 *point.ContainerAsContent(),
2354 *editableBlockParentOrTopmostEditableInlineContent,
2355 {LeafNodeType::LeafNodeOrNonEditableNode}, mEditingHost);
2356 nextContent;
2357 nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
2358 *nextContent, *editableBlockParentOrTopmostEditableInlineContent,
2359 {LeafNodeType::LeafNodeOrNonEditableNode}, mEditingHost)) {
2360 if (!nextContent->IsText() || !nextContent->IsEditable()) {
2361 if (nextContent == GetEndReasonContent()) {
2362 break; // Reached end of current runs.
2364 continue;
2366 return EditorDOMPointInText(nextContent->AsText(), 0);
2368 return EditorDOMPointInText();
2371 template <typename PT, typename CT>
2372 EditorDOMPointInText
2373 WSRunScanner::TextFragmentData::GetPreviousEditableCharPoint(
2374 const EditorDOMPointBase<PT, CT>& aPoint) const {
2375 MOZ_ASSERT(aPoint.IsSetAndValid());
2377 if (NS_WARN_IF(!aPoint.IsInContentNode()) ||
2378 NS_WARN_IF(!mScanStartPoint.IsInContentNode())) {
2379 return EditorDOMPointInText();
2382 EditorRawDOMPoint point;
2383 if (nsIContent* previousChild = aPoint.CanContainerHaveChildren()
2384 ? aPoint.GetPreviousSiblingOfChild()
2385 : nullptr) {
2386 nsIContent* leafContent =
2387 previousChild->HasChildren()
2388 ? HTMLEditUtils::GetLastLeafContent(*previousChild,
2389 {LeafNodeType::OnlyLeafNode})
2390 : previousChild;
2391 if (NS_WARN_IF(!leafContent)) {
2392 return EditorDOMPointInText();
2394 point.SetToEndOf(leafContent);
2395 } else {
2396 point = aPoint;
2399 // If it points a character in a text node and it's not first character
2400 // in it, return its previous point.
2401 // XXX For the performance, this does not check whether the container
2402 // is outside of our range.
2403 if (point.IsInTextNode() && point.GetContainer()->IsEditable() &&
2404 !point.IsStartOfContainer()) {
2405 return EditorDOMPointInText(point.ContainerAsText(), point.Offset() - 1);
2408 if (point.GetContainer() == GetStartReasonContent()) {
2409 return EditorDOMPointInText();
2412 NS_ASSERTION(EditorUtils::IsEditableContent(
2413 *mScanStartPoint.ContainerAsContent(), EditorType::HTML),
2414 "Given content is not editable");
2415 NS_ASSERTION(
2416 mScanStartPoint.ContainerAsContent()->GetAsElementOrParentElement(),
2417 "Given content is not an element and an orphan node");
2418 nsIContent* editableBlockParentOrTopmostEditableInlineContent =
2419 mScanStartPoint.ContainerAsContent() &&
2420 EditorUtils::IsEditableContent(
2421 *mScanStartPoint.ContainerAsContent(), EditorType::HTML)
2422 ? HTMLEditUtils::
2423 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2424 *mScanStartPoint.ContainerAsContent())
2425 : nullptr;
2426 if (NS_WARN_IF(!editableBlockParentOrTopmostEditableInlineContent)) {
2427 // Meaning that the container of `mScanStartPoint` is not editable.
2428 editableBlockParentOrTopmostEditableInlineContent =
2429 mScanStartPoint.ContainerAsContent();
2432 for (nsIContent* previousContent =
2433 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2434 *point.ContainerAsContent(),
2435 *editableBlockParentOrTopmostEditableInlineContent,
2436 {LeafNodeType::LeafNodeOrNonEditableNode}, mEditingHost);
2437 previousContent;
2438 previousContent =
2439 HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
2440 *previousContent,
2441 *editableBlockParentOrTopmostEditableInlineContent,
2442 {LeafNodeType::LeafNodeOrNonEditableNode}, mEditingHost)) {
2443 if (!previousContent->IsText() || !previousContent->IsEditable()) {
2444 if (previousContent == GetStartReasonContent()) {
2445 break; // Reached start of current runs.
2447 continue;
2449 return EditorDOMPointInText(
2450 previousContent->AsText(),
2451 previousContent->AsText()->TextLength()
2452 ? previousContent->AsText()->TextLength() - 1
2453 : 0);
2455 return EditorDOMPointInText();
2458 // static
2459 template <typename EditorDOMPointType>
2460 EditorDOMPointType WSRunScanner::GetAfterLastVisiblePoint(
2461 Text& aTextNode, const Element* aAncestorLimiter) {
2462 if (EditorUtils::IsContentPreformatted(aTextNode)) {
2463 return EditorDOMPointType::AtEndOf(aTextNode);
2465 TextFragmentData textFragmentData(
2466 EditorDOMPoint(&aTextNode,
2467 aTextNode.Length() ? aTextNode.Length() - 1 : 0),
2468 aAncestorLimiter);
2469 if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
2470 return EditorDOMPointType(); // TODO: Make here return error with Err.
2472 const EditorDOMRange& invisibleWhiteSpaceRange =
2473 textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
2474 if (!invisibleWhiteSpaceRange.IsPositioned() ||
2475 invisibleWhiteSpaceRange.Collapsed()) {
2476 return EditorDOMPointType::AtEndOf(aTextNode);
2478 return EditorDOMPointType(invisibleWhiteSpaceRange.StartRef());
2481 // static
2482 template <typename EditorDOMPointType>
2483 EditorDOMPointType WSRunScanner::GetFirstVisiblePoint(
2484 Text& aTextNode, const Element* aAncestorLimiter) {
2485 if (EditorUtils::IsContentPreformatted(aTextNode)) {
2486 return EditorDOMPointType(&aTextNode, 0);
2488 TextFragmentData textFragmentData(EditorDOMPoint(&aTextNode, 0),
2489 aAncestorLimiter);
2490 if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
2491 return EditorDOMPointType(); // TODO: Make here return error with Err.
2493 const EditorDOMRange& invisibleWhiteSpaceRange =
2494 textFragmentData.InvisibleLeadingWhiteSpaceRangeRef();
2495 if (!invisibleWhiteSpaceRange.IsPositioned() ||
2496 invisibleWhiteSpaceRange.Collapsed()) {
2497 return EditorDOMPointType(&aTextNode, 0);
2499 return EditorDOMPointType(invisibleWhiteSpaceRange.EndRef());
2502 EditorDOMPointInText
2503 WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
2504 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const {
2505 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
2506 MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
2507 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsCharASCIISpace());
2508 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2509 *aPointAtASCIIWhiteSpace.ContainerAsText()),
2510 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2512 // If it's not the last character in the text node, let's scan following
2513 // characters in it.
2514 if (!aPointAtASCIIWhiteSpace.IsAtLastContent()) {
2515 Maybe<uint32_t> nextVisibleCharOffset =
2516 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2517 aPointAtASCIIWhiteSpace);
2518 if (nextVisibleCharOffset.isSome()) {
2519 // There is non-white-space character in it.
2520 return EditorDOMPointInText(aPointAtASCIIWhiteSpace.ContainerAsText(),
2521 nextVisibleCharOffset.value());
2525 // Otherwise, i.e., the text node ends with ASCII white-space, keep scanning
2526 // the following text nodes.
2527 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2528 // content.
2529 EditorDOMPointInText afterLastWhiteSpace =
2530 EditorDOMPointInText::AtEndOf(*aPointAtASCIIWhiteSpace.ContainerAsText());
2531 for (EditorDOMPointInText atEndOfPreviousTextNode = afterLastWhiteSpace;;) {
2532 EditorDOMPointInText atStartOfNextTextNode =
2533 GetInclusiveNextEditableCharPoint(atEndOfPreviousTextNode);
2534 if (!atStartOfNextTextNode.IsSet()) {
2535 // There is no more text nodes. Return end of the previous text node.
2536 return afterLastWhiteSpace;
2539 // We can ignore empty text nodes (even if it's preformatted).
2540 if (atStartOfNextTextNode.IsContainerEmpty()) {
2541 atEndOfPreviousTextNode = atStartOfNextTextNode;
2542 continue;
2545 // If next node starts with non-white-space character or next node is
2546 // preformatted, return end of previous text node.
2547 if (!atStartOfNextTextNode.IsCharASCIISpace() ||
2548 EditorUtils::IsContentPreformatted(
2549 *atStartOfNextTextNode.ContainerAsText())) {
2550 return afterLastWhiteSpace;
2553 // Otherwise, scan the text node.
2554 Maybe<uint32_t> nextVisibleCharOffset =
2555 HTMLEditUtils::GetNextCharOffsetExceptASCIIWhiteSpaces(
2556 atStartOfNextTextNode);
2557 if (nextVisibleCharOffset.isSome()) {
2558 return EditorDOMPointInText(atStartOfNextTextNode.ContainerAsText(),
2559 nextVisibleCharOffset.value());
2562 // The next text nodes ends with white-space too. Try next one.
2563 afterLastWhiteSpace = atEndOfPreviousTextNode =
2564 EditorDOMPointInText::AtEndOf(*atStartOfNextTextNode.ContainerAsText());
2568 EditorDOMPointInText
2569 WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
2570 const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const {
2571 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
2572 MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
2573 MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsCharASCIISpace());
2574 NS_ASSERTION(!EditorUtils::IsContentPreformatted(
2575 *aPointAtASCIIWhiteSpace.ContainerAsText()),
2576 "aPointAtASCIIWhiteSpace should be in a formatted text node");
2578 // If there is some characters before it, scan it in the text node first.
2579 if (!aPointAtASCIIWhiteSpace.IsStartOfContainer()) {
2580 uint32_t firstASCIIWhiteSpaceOffset =
2581 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2582 aPointAtASCIIWhiteSpace);
2583 if (firstASCIIWhiteSpaceOffset) {
2584 // There is a non-white-space character in it.
2585 return EditorDOMPointInText(aPointAtASCIIWhiteSpace.ContainerAsText(),
2586 firstASCIIWhiteSpaceOffset);
2590 // Otherwise, i.e., the text node starts with ASCII white-space, keep scanning
2591 // the preceding text nodes.
2592 // XXX Perhaps, we should stop scanning if there is non-editable and visible
2593 // content.
2594 EditorDOMPointInText atLastWhiteSpace =
2595 EditorDOMPointInText(aPointAtASCIIWhiteSpace.ContainerAsText(), 0);
2596 for (EditorDOMPointInText atStartOfPreviousTextNode = atLastWhiteSpace;;) {
2597 EditorDOMPointInText atLastCharOfNextTextNode =
2598 GetPreviousEditableCharPoint(atStartOfPreviousTextNode);
2599 if (!atLastCharOfNextTextNode.IsSet()) {
2600 // There is no more text nodes. Return end of last text node.
2601 return atLastWhiteSpace;
2604 // We can ignore empty text nodes (even if it's preformatted).
2605 if (atLastCharOfNextTextNode.IsContainerEmpty()) {
2606 atStartOfPreviousTextNode = atLastCharOfNextTextNode;
2607 continue;
2610 // If next node ends with non-white-space character or next node is
2611 // preformatted, return start of previous text node.
2612 if (!atLastCharOfNextTextNode.IsCharASCIISpace() ||
2613 EditorUtils::IsContentPreformatted(
2614 *atLastCharOfNextTextNode.ContainerAsText())) {
2615 return atLastWhiteSpace;
2618 // Otherwise, scan the text node.
2619 uint32_t firstASCIIWhiteSpaceOffset =
2620 HTMLEditUtils::GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
2621 atLastCharOfNextTextNode);
2622 if (firstASCIIWhiteSpaceOffset) {
2623 return EditorDOMPointInText(atLastCharOfNextTextNode.ContainerAsText(),
2624 firstASCIIWhiteSpaceOffset);
2627 // The next text nodes starts with white-space too. Try next one.
2628 atLastWhiteSpace = atStartOfPreviousTextNode =
2629 EditorDOMPointInText(atLastCharOfNextTextNode.ContainerAsText(), 0);
2633 // static
2634 nsresult WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
2635 HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
2636 const nsAString& aReplaceString) {
2637 MOZ_ASSERT(aRangeToReplace.IsPositioned());
2638 MOZ_ASSERT(aRangeToReplace.StartRef().IsSetAndValid());
2639 MOZ_ASSERT(aRangeToReplace.EndRef().IsSetAndValid());
2640 MOZ_ASSERT(aRangeToReplace.StartRef().IsBefore(aRangeToReplace.EndRef()));
2642 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
2643 nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
2644 MOZ_KnownLive(*aRangeToReplace.StartRef().ContainerAsText()),
2645 aRangeToReplace.StartRef().Offset(),
2646 aRangeToReplace.InSameContainer()
2647 ? aRangeToReplace.EndRef().Offset() -
2648 aRangeToReplace.StartRef().Offset()
2649 : aRangeToReplace.StartRef().ContainerAsText()->TextLength() -
2650 aRangeToReplace.StartRef().Offset(),
2651 aReplaceString);
2652 if (NS_FAILED(rv)) {
2653 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2654 return rv;
2657 if (aRangeToReplace.InSameContainer()) {
2658 return NS_OK;
2661 rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
2662 EditorDOMPointInText::AtEndOf(
2663 *aRangeToReplace.StartRef().ContainerAsText()),
2664 aRangeToReplace.EndRef(),
2665 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
2666 NS_WARNING_ASSERTION(
2667 NS_SUCCEEDED(rv),
2668 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2669 return rv;
2672 char16_t WSRunScanner::GetCharAt(Text* aTextNode, int32_t aOffset) const {
2673 // return 0 if we can't get a char, for whatever reason
2674 if (NS_WARN_IF(!aTextNode) || NS_WARN_IF(aOffset < 0) ||
2675 NS_WARN_IF(aOffset >=
2676 static_cast<int32_t>(aTextNode->TextDataLength()))) {
2677 return 0;
2679 return aTextNode->TextFragment().CharAt(aOffset);
2682 // static
2683 template <typename EditorDOMPointType>
2684 nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
2685 HTMLEditor& aHTMLEditor, const EditorDOMPointType& aPoint) {
2686 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
2687 TextFragmentData textFragmentData(aPoint, editingHost);
2688 if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
2689 return NS_ERROR_FAILURE;
2692 // this routine examines a run of ws and tries to get rid of some unneeded
2693 // nbsp's, replacing them with regular ascii space if possible. Keeping
2694 // things simple for now and just trying to fix up the trailing ws in the run.
2695 if (!textFragmentData.FoundNoBreakingWhiteSpaces()) {
2696 // nothing to do!
2697 return NS_OK;
2699 const VisibleWhiteSpacesData& visibleWhiteSpaces =
2700 textFragmentData.VisibleWhiteSpacesDataRef();
2701 if (!visibleWhiteSpaces.IsInitialized()) {
2702 return NS_OK;
2705 // Remove this block if we ship Blink-compat white-space normalization.
2706 if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
2707 // now check that what is to the left of it is compatible with replacing
2708 // nbsp with space
2709 const EditorDOMPoint& atEndOfVisibleWhiteSpaces =
2710 visibleWhiteSpaces.EndRef();
2711 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces =
2712 textFragmentData.GetPreviousEditableCharPoint(
2713 atEndOfVisibleWhiteSpaces);
2714 if (!atPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() ||
2715 atPreviousCharOfEndOfVisibleWhiteSpaces.IsEndOfContainer() ||
2716 !atPreviousCharOfEndOfVisibleWhiteSpaces.IsCharNBSP()) {
2717 return NS_OK;
2720 // now check that what is to the left of it is compatible with replacing
2721 // nbsp with space
2722 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces =
2723 textFragmentData.GetPreviousEditableCharPoint(
2724 atPreviousCharOfEndOfVisibleWhiteSpaces);
2725 bool isPreviousCharASCIIWhiteSpace =
2726 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() &&
2727 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2728 .IsEndOfContainer() &&
2729 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2730 .IsCharASCIISpace();
2731 bool maybeNBSPFollowingVisibleContent =
2732 (atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() &&
2733 !isPreviousCharASCIIWhiteSpace) ||
2734 (!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() &&
2735 (visibleWhiteSpaces.StartsFromNormalText() ||
2736 visibleWhiteSpaces.StartsFromSpecialContent()));
2737 bool followedByVisibleContentOrBRElement = false;
2739 // If the NBSP follows a visible content or an ASCII white-space, i.e.,
2740 // unless NBSP is first character and start of a block, we may need to
2741 // insert <br> element and restore the NBSP to an ASCII white-space.
2742 if (maybeNBSPFollowingVisibleContent || isPreviousCharASCIIWhiteSpace) {
2743 followedByVisibleContentOrBRElement =
2744 visibleWhiteSpaces.EndsByNormalText() ||
2745 visibleWhiteSpaces.EndsBySpecialContent() ||
2746 visibleWhiteSpaces.EndsByBRElement();
2747 // First, try to insert <br> element if NBSP is at end of a block.
2748 // XXX We should stop this if there is a visible content.
2749 if (visibleWhiteSpaces.EndsByBlockBoundary() &&
2750 aPoint.IsInContentNode()) {
2751 bool insertBRElement =
2752 HTMLEditUtils::IsBlockElement(*aPoint.ContainerAsContent());
2753 if (!insertBRElement) {
2754 NS_ASSERTION(EditorUtils::IsEditableContent(
2755 *aPoint.ContainerAsContent(), EditorType::HTML),
2756 "Given content is not editable");
2757 NS_ASSERTION(
2758 aPoint.ContainerAsContent()->GetAsElementOrParentElement(),
2759 "Given content is not an element and an orphan node");
2760 nsIContent* blockParentOrTopmostEditableInlineContent =
2761 EditorUtils::IsEditableContent(*aPoint.ContainerAsContent(),
2762 EditorType::HTML)
2763 ? HTMLEditUtils::
2764 GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
2765 *aPoint.ContainerAsContent())
2766 : nullptr;
2767 insertBRElement = blockParentOrTopmostEditableInlineContent &&
2768 HTMLEditUtils::IsBlockElement(
2769 *blockParentOrTopmostEditableInlineContent);
2771 if (insertBRElement) {
2772 // We are at a block boundary. Insert a <br>. Why? Well, first note
2773 // that the br will have no visible effect since it is up against a
2774 // block boundary. |foo<br><p>bar| renders like |foo<p>bar| and
2775 // similarly |<p>foo<br></p>bar| renders like |<p>foo</p>bar|. What
2776 // this <br> addition gets us is the ability to convert a trailing
2777 // nbsp to a space. Consider: |<body>foo. '</body>|, where '
2778 // represents selection. User types space attempting to put 2 spaces
2779 // after the end of their sentence. We used to do this as:
2780 // |<body>foo. &nbsp</body>| This caused problems with soft wrapping:
2781 // the nbsp would wrap to the next line, which looked attrocious. If
2782 // you try to do: |<body>foo.&nbsp </body>| instead, the trailing
2783 // space is invisible because it is against a block boundary. If you
2784 // do:
2785 // |<body>foo.&nbsp&nbsp</body>| then you get an even uglier soft
2786 // wrapping problem, where foo is on one line until you type the final
2787 // space, and then "foo " jumps down to the next line. Ugh. The
2788 // best way I can find out of this is to throw in a harmless <br>
2789 // here, which allows us to do: |<body>foo.&nbsp <br></body>|, which
2790 // doesn't cause foo to jump lines, doesn't cause spaces to show up at
2791 // the beginning of soft wrapped lines, and lets the user see 2 spaces
2792 // when they type 2 spaces.
2794 Result<RefPtr<Element>, nsresult> resultOfInsertingBRElement =
2795 aHTMLEditor.InsertBRElementWithTransaction(
2796 atEndOfVisibleWhiteSpaces);
2797 if (resultOfInsertingBRElement.isErr()) {
2798 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
2799 return resultOfInsertingBRElement.unwrapErr();
2801 MOZ_ASSERT(resultOfInsertingBRElement.inspect());
2803 atPreviousCharOfEndOfVisibleWhiteSpaces =
2804 textFragmentData.GetPreviousEditableCharPoint(
2805 atEndOfVisibleWhiteSpaces);
2806 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces =
2807 textFragmentData.GetPreviousEditableCharPoint(
2808 atPreviousCharOfEndOfVisibleWhiteSpaces);
2809 isPreviousCharASCIIWhiteSpace =
2810 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() &&
2811 !atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2812 .IsEndOfContainer() &&
2813 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2814 .IsCharASCIISpace();
2815 followedByVisibleContentOrBRElement = true;
2819 // Next, replace the NBSP with an ASCII white-space if it's surrounded
2820 // by visible contents (or immediately before a <br> element).
2821 if (maybeNBSPFollowingVisibleContent &&
2822 followedByVisibleContentOrBRElement) {
2823 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
2824 nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
2825 MOZ_KnownLive(
2826 *atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAsText()),
2827 atPreviousCharOfEndOfVisibleWhiteSpaces.Offset(), 1, u" "_ns);
2828 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2829 "HTMLEditor::ReplaceTextWithTransaction() failed");
2830 return rv;
2833 // If the text node is not preformatted, and the NBSP is followed by a <br>
2834 // element and following (maybe multiple) ASCII spaces, remove the NBSP,
2835 // but inserts a NBSP before the spaces. This makes a line break
2836 // opportunity to wrap the line.
2837 // XXX This is different behavior from Blink. Blink generates pairs of
2838 // an NBSP and an ASCII white-space, but put NBSP at the end of the
2839 // sequence. We should follow the behavior for web-compat.
2840 if (maybeNBSPFollowingVisibleContent || !isPreviousCharASCIIWhiteSpace ||
2841 !followedByVisibleContentOrBRElement ||
2842 EditorUtils::IsContentPreformatted(
2843 *atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2844 .GetContainerAsText())) {
2845 return NS_OK;
2848 // Currently, we're at an NBSP following an ASCII space, and we need to
2849 // replace them with `"&nbsp; "` for avoiding collapsing white-spaces.
2850 MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2851 .IsEndOfContainer());
2852 EditorDOMPointInText atFirstASCIIWhiteSpace =
2853 textFragmentData.GetFirstASCIIWhiteSpacePointCollapsedTo(
2854 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces);
2855 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
2856 uint32_t numberOfASCIIWhiteSpacesInStartNode =
2857 atFirstASCIIWhiteSpace.ContainerAsText() ==
2858 atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAsText()
2859 ? atPreviousCharOfEndOfVisibleWhiteSpaces.Offset() -
2860 atFirstASCIIWhiteSpace.Offset()
2861 : atFirstASCIIWhiteSpace.ContainerAsText()->Length() -
2862 atFirstASCIIWhiteSpace.Offset();
2863 // Replace all preceding ASCII white-spaces **and** the NBSP.
2864 uint32_t replaceLengthInStartNode =
2865 numberOfASCIIWhiteSpacesInStartNode +
2866 (atFirstASCIIWhiteSpace.ContainerAsText() ==
2867 atPreviousCharOfEndOfVisibleWhiteSpaces.ContainerAsText()
2869 : 0);
2870 nsresult rv = aHTMLEditor.ReplaceTextWithTransaction(
2871 MOZ_KnownLive(*atFirstASCIIWhiteSpace.ContainerAsText()),
2872 atFirstASCIIWhiteSpace.Offset(), replaceLengthInStartNode,
2873 u"\x00A0 "_ns);
2874 if (NS_FAILED(rv)) {
2875 NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
2876 return rv;
2879 if (atFirstASCIIWhiteSpace.GetContainer() ==
2880 atPreviousCharOfEndOfVisibleWhiteSpaces.GetContainer()) {
2881 return NS_OK;
2884 // We need to remove the following unnecessary ASCII white-spaces and
2885 // NBSP at atPreviousCharOfEndOfVisibleWhiteSpaces because we collapsed them
2886 // into the start node.
2887 rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
2888 EditorDOMPointInText::AtEndOf(
2889 *atFirstASCIIWhiteSpace.ContainerAsText()),
2890 atPreviousCharOfEndOfVisibleWhiteSpaces.NextPoint(),
2891 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
2892 NS_WARNING_ASSERTION(
2893 NS_SUCCEEDED(rv),
2894 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
2895 return rv;
2898 // XXX This is called when top-level edit sub-action handling ends for
2899 // 3 points at most. However, this is not compatible with Blink.
2900 // Blink touches white-space sequence which includes new character
2901 // or following white-space sequence of new <br> element or, if and
2902 // only if deleting range is followed by white-space sequence (i.e.,
2903 // not touched previous white-space sequence of deleting range).
2904 // This should be done when we change to make each edit action
2905 // handler directly normalize white-space sequence rather than
2906 // OnEndHandlingTopLevelEditSucAction().
2908 // First, check if the last character is an NBSP. Otherwise, we don't need
2909 // to do nothing here.
2910 const EditorDOMPoint& atEndOfVisibleWhiteSpaces = visibleWhiteSpaces.EndRef();
2911 EditorDOMPointInText atPreviousCharOfEndOfVisibleWhiteSpaces =
2912 textFragmentData.GetPreviousEditableCharPoint(atEndOfVisibleWhiteSpaces);
2913 if (!atPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() ||
2914 atPreviousCharOfEndOfVisibleWhiteSpaces.IsEndOfContainer() ||
2915 !atPreviousCharOfEndOfVisibleWhiteSpaces.IsCharNBSP()) {
2916 return NS_OK;
2919 // Next, consider the range to collapse ASCII white-spaces before there.
2920 EditorDOMPointInText startToDelete, endToDelete;
2922 EditorDOMPointInText atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces =
2923 textFragmentData.GetPreviousEditableCharPoint(
2924 atPreviousCharOfEndOfVisibleWhiteSpaces);
2925 // If there are some preceding ASCII white-spaces, we need to treat them
2926 // as one white-space. I.e., we need to collapse them.
2927 if (atPreviousCharOfEndOfVisibleWhiteSpaces.IsCharNBSP() &&
2928 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces.IsSet() &&
2929 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces
2930 .IsCharASCIISpace()) {
2931 startToDelete = textFragmentData.GetFirstASCIIWhiteSpacePointCollapsedTo(
2932 atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces);
2933 endToDelete = atPreviousCharOfPreviousCharOfEndOfVisibleWhiteSpaces;
2935 // Otherwise, we don't need to remove any white-spaces, but we may need
2936 // to normalize the white-space sequence containing the previous NBSP.
2937 else {
2938 startToDelete = endToDelete =
2939 atPreviousCharOfEndOfVisibleWhiteSpaces.NextPoint();
2942 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
2943 Result<EditorDOMPoint, nsresult> result =
2944 aHTMLEditor.DeleteTextAndNormalizeSurroundingWhiteSpaces(
2945 startToDelete, endToDelete,
2946 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries,
2947 HTMLEditor::DeleteDirection::Forward);
2948 NS_WARNING_ASSERTION(
2949 !result.isOk(),
2950 "HTMLEditor::DeleteTextAndNormalizeSurroundingWhiteSpaces() failed");
2951 return result.isErr() ? result.unwrapErr() : NS_OK;
2954 EditorDOMPointInText WSRunScanner::TextFragmentData::
2955 GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
2956 const EditorDOMPoint& aPointToInsert) const {
2957 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
2958 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
2959 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
2960 PointPosition::MiddleOfFragment ||
2961 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
2962 PointPosition::EndOfFragment,
2963 "Previous char of aPoint should be in the visible white-spaces");
2965 // Try to change an NBSP to a space, if possible, just to prevent NBSP
2966 // proliferation. This routine is called when we are about to make this
2967 // point in the ws abut an inserted break or text, so we don't have to worry
2968 // about what is after it. What is after it now will end up after the
2969 // inserted object.
2970 EditorDOMPointInText atPreviousChar =
2971 GetPreviousEditableCharPoint(aPointToInsert);
2972 if (!atPreviousChar.IsSet() || atPreviousChar.IsEndOfContainer() ||
2973 !atPreviousChar.IsCharNBSP() ||
2974 EditorUtils::IsContentPreformatted(*atPreviousChar.ContainerAsText())) {
2975 return EditorDOMPointInText();
2978 EditorDOMPointInText atPreviousCharOfPreviousChar =
2979 GetPreviousEditableCharPoint(atPreviousChar);
2980 if (atPreviousCharOfPreviousChar.IsSet()) {
2981 // If the previous char is in different text node and it's preformatted,
2982 // we shouldn't touch it.
2983 if (atPreviousChar.ContainerAsText() !=
2984 atPreviousCharOfPreviousChar.ContainerAsText() &&
2985 EditorUtils::IsContentPreformatted(
2986 *atPreviousCharOfPreviousChar.ContainerAsText())) {
2987 return EditorDOMPointInText();
2989 // If the previous char of the NBSP at previous position of aPointToInsert
2990 // is an ASCII white-space, we don't need to replace it with same character.
2991 if (!atPreviousCharOfPreviousChar.IsEndOfContainer() &&
2992 atPreviousCharOfPreviousChar.IsCharASCIISpace()) {
2993 return EditorDOMPointInText();
2995 return atPreviousChar;
2998 // If previous content of the NBSP is block boundary, we cannot replace the
2999 // NBSP with an ASCII white-space to keep it rendered.
3000 const VisibleWhiteSpacesData& visibleWhiteSpaces =
3001 VisibleWhiteSpacesDataRef();
3002 if (!visibleWhiteSpaces.StartsFromNormalText() &&
3003 !visibleWhiteSpaces.StartsFromSpecialContent()) {
3004 return EditorDOMPointInText();
3006 return atPreviousChar;
3009 EditorDOMPointInText WSRunScanner::TextFragmentData::
3010 GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
3011 const EditorDOMPoint& aPointToInsert) const {
3012 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
3013 MOZ_ASSERT(VisibleWhiteSpacesDataRef().IsInitialized());
3014 NS_ASSERTION(VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
3015 PointPosition::StartOfFragment ||
3016 VisibleWhiteSpacesDataRef().ComparePoint(aPointToInsert) ==
3017 PointPosition::MiddleOfFragment,
3018 "Inclusive next char of aPointToInsert should be in the visible "
3019 "white-spaces");
3021 // Try to change an nbsp to a space, if possible, just to prevent nbsp
3022 // proliferation This routine is called when we are about to make this point
3023 // in the ws abut an inserted text, so we don't have to worry about what is
3024 // before it. What is before it now will end up before the inserted text.
3025 EditorDOMPointInText atNextChar =
3026 GetInclusiveNextEditableCharPoint(aPointToInsert);
3027 if (!atNextChar.IsSet() || NS_WARN_IF(atNextChar.IsEndOfContainer()) ||
3028 !atNextChar.IsCharNBSP() ||
3029 EditorUtils::IsContentPreformatted(*atNextChar.ContainerAsText())) {
3030 return EditorDOMPointInText();
3033 EditorDOMPointInText atNextCharOfNextCharOfNBSP =
3034 GetInclusiveNextEditableCharPoint(atNextChar.NextPoint());
3035 if (atNextCharOfNextCharOfNBSP.IsSet()) {
3036 // If the next char is in different text node and it's preformatted,
3037 // we shouldn't touch it.
3038 if (atNextChar.ContainerAsText() !=
3039 atNextCharOfNextCharOfNBSP.ContainerAsText() &&
3040 EditorUtils::IsContentPreformatted(
3041 *atNextCharOfNextCharOfNBSP.ContainerAsText())) {
3042 return EditorDOMPointInText();
3044 // If following character of an NBSP is an ASCII white-space, we don't
3045 // need to replace it with same character.
3046 if (!atNextCharOfNextCharOfNBSP.IsEndOfContainer() &&
3047 atNextCharOfNextCharOfNBSP.IsCharASCIISpace()) {
3048 return EditorDOMPointInText();
3050 return atNextChar;
3053 // If the NBSP is last character in the hard line, we don't need to
3054 // replace it because it's required to render multiple white-spaces.
3055 const VisibleWhiteSpacesData& visibleWhiteSpaces =
3056 VisibleWhiteSpacesDataRef();
3057 if (!visibleWhiteSpaces.EndsByNormalText() &&
3058 !visibleWhiteSpaces.EndsBySpecialContent() &&
3059 !visibleWhiteSpaces.EndsByBRElement()) {
3060 return EditorDOMPointInText();
3063 return atNextChar;
3066 // static
3067 nsresult WhiteSpaceVisibilityKeeper::DeleteInvisibleASCIIWhiteSpaces(
3068 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint) {
3069 MOZ_ASSERT(aPoint.IsSet());
3070 Element* editingHost = aHTMLEditor.GetActiveEditingHost();
3071 TextFragmentData textFragmentData(aPoint, editingHost);
3072 if (NS_WARN_IF(!textFragmentData.IsInitialized())) {
3073 return NS_ERROR_FAILURE;
3075 const EditorDOMRange& leadingWhiteSpaceRange =
3076 textFragmentData.InvisibleLeadingWhiteSpaceRangeRef();
3077 // XXX Getting trailing white-space range now must be wrong because
3078 // mutation event listener may invalidate it.
3079 const EditorDOMRange& trailingWhiteSpaceRange =
3080 textFragmentData.InvisibleTrailingWhiteSpaceRangeRef();
3081 DebugOnly<bool> leadingWhiteSpacesDeleted = false;
3082 if (leadingWhiteSpaceRange.IsPositioned() &&
3083 !leadingWhiteSpaceRange.Collapsed()) {
3084 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
3085 leadingWhiteSpaceRange.StartRef(), leadingWhiteSpaceRange.EndRef(),
3086 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
3087 if (NS_FAILED(rv)) {
3088 NS_WARNING(
3089 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3090 "delete leading white-spaces");
3091 return rv;
3093 leadingWhiteSpacesDeleted = true;
3095 if (trailingWhiteSpaceRange.IsPositioned() &&
3096 !trailingWhiteSpaceRange.Collapsed() &&
3097 leadingWhiteSpaceRange != trailingWhiteSpaceRange) {
3098 NS_ASSERTION(!leadingWhiteSpacesDeleted,
3099 "We're trying to remove trailing white-spaces with maybe "
3100 "outdated range");
3101 nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
3102 trailingWhiteSpaceRange.StartRef(), trailingWhiteSpaceRange.EndRef(),
3103 HTMLEditor::TreatEmptyTextNodes::KeepIfContainerOfRangeBoundaries);
3104 if (NS_FAILED(rv)) {
3105 NS_WARNING(
3106 "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed to "
3107 "delete trailing white-spaces");
3108 return rv;
3111 return NS_OK;
3114 /*****************************************************************************
3115 * Implementation for new white-space normalizer
3116 *****************************************************************************/
3118 // static
3119 EditorDOMRangeInTexts
3120 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3121 const TextFragmentData& aStart, const TextFragmentData& aEnd) {
3122 // Corresponding to handling invisible white-spaces part of
3123 // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and
3124 // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
3126 MOZ_ASSERT(aStart.ScanStartRef().IsSetAndValid());
3127 MOZ_ASSERT(aEnd.ScanStartRef().IsSetAndValid());
3128 MOZ_ASSERT(aStart.ScanStartRef().EqualsOrIsBefore(aEnd.ScanStartRef()));
3129 MOZ_ASSERT(aStart.ScanStartRef().IsInTextNode());
3130 MOZ_ASSERT(aEnd.ScanStartRef().IsInTextNode());
3132 // XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and
3133 // `GetReplaceRangeDataAtStartOfDeletionRange()` use
3134 // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and
3135 // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`.
3136 // However, they are really odd as mentioned with "XXX" comments
3137 // in them. For the new white-space normalizer, we need to treat
3138 // invisible white-spaces stricter because the legacy path handles
3139 // white-spaces multiple times (e.g., calling `HTMLEditor::
3140 // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides
3141 // the bug, but in the new path, we should stop doing same things
3142 // multiple times for both performance and footprint. Therefore,
3143 // even though the result might be different in some edge cases,
3144 // we should use clean path for now. Perhaps, we should fix the odd
3145 // cases before shipping `beforeinput` event in release channel.
3147 const EditorDOMRange& invisibleLeadingWhiteSpaceRange =
3148 aStart.InvisibleLeadingWhiteSpaceRangeRef();
3149 const EditorDOMRange& invisibleTrailingWhiteSpaceRange =
3150 aEnd.InvisibleTrailingWhiteSpaceRangeRef();
3151 const bool hasInvisibleLeadingWhiteSpaces =
3152 invisibleLeadingWhiteSpaceRange.IsPositioned() &&
3153 !invisibleLeadingWhiteSpaceRange.Collapsed();
3154 const bool hasInvisibleTrailingWhiteSpaces =
3155 invisibleLeadingWhiteSpaceRange != invisibleTrailingWhiteSpaceRange &&
3156 invisibleTrailingWhiteSpaceRange.IsPositioned() &&
3157 !invisibleTrailingWhiteSpaceRange.Collapsed();
3159 EditorDOMRangeInTexts result(aStart.ScanStartRef().AsInText(),
3160 aEnd.ScanStartRef().AsInText());
3161 MOZ_ASSERT(result.IsPositionedAndValid());
3162 if (!hasInvisibleLeadingWhiteSpaces && !hasInvisibleTrailingWhiteSpaces) {
3163 return result;
3166 MOZ_ASSERT_IF(
3167 hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
3168 invisibleLeadingWhiteSpaceRange.StartRef().IsBefore(
3169 invisibleTrailingWhiteSpaceRange.StartRef()));
3170 const EditorDOMPoint& aroundFirstInvisibleWhiteSpace =
3171 hasInvisibleLeadingWhiteSpaces
3172 ? invisibleLeadingWhiteSpaceRange.StartRef()
3173 : invisibleTrailingWhiteSpaceRange.StartRef();
3174 if (aroundFirstInvisibleWhiteSpace.IsBefore(result.StartRef())) {
3175 if (aroundFirstInvisibleWhiteSpace.IsInTextNode()) {
3176 result.SetStart(aroundFirstInvisibleWhiteSpace.AsInText());
3177 MOZ_ASSERT(result.IsPositionedAndValid());
3178 } else {
3179 const EditorDOMPointInText atFirstInvisibleWhiteSpace =
3180 hasInvisibleLeadingWhiteSpaces
3181 ? aStart.GetInclusiveNextEditableCharPoint(
3182 aroundFirstInvisibleWhiteSpace)
3183 : aEnd.GetInclusiveNextEditableCharPoint(
3184 aroundFirstInvisibleWhiteSpace);
3185 MOZ_ASSERT(atFirstInvisibleWhiteSpace.IsSet());
3186 MOZ_ASSERT(
3187 atFirstInvisibleWhiteSpace.EqualsOrIsBefore(result.StartRef()));
3188 result.SetStart(atFirstInvisibleWhiteSpace);
3189 MOZ_ASSERT(result.IsPositionedAndValid());
3192 MOZ_ASSERT_IF(
3193 hasInvisibleLeadingWhiteSpaces && hasInvisibleTrailingWhiteSpaces,
3194 invisibleLeadingWhiteSpaceRange.EndRef().IsBefore(
3195 invisibleTrailingWhiteSpaceRange.EndRef()));
3196 const EditorDOMPoint& afterLastInvisibleWhiteSpace =
3197 hasInvisibleTrailingWhiteSpaces
3198 ? invisibleTrailingWhiteSpaceRange.EndRef()
3199 : invisibleLeadingWhiteSpaceRange.EndRef();
3200 if (afterLastInvisibleWhiteSpace.EqualsOrIsBefore(result.EndRef())) {
3201 MOZ_ASSERT(result.IsPositionedAndValid());
3202 return result;
3204 if (afterLastInvisibleWhiteSpace.IsInTextNode()) {
3205 result.SetEnd(afterLastInvisibleWhiteSpace.AsInText());
3206 MOZ_ASSERT(result.IsPositionedAndValid());
3207 return result;
3209 const EditorDOMPointInText atLastInvisibleWhiteSpace =
3210 hasInvisibleTrailingWhiteSpaces
3211 ? aEnd.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace)
3212 : aStart.GetPreviousEditableCharPoint(afterLastInvisibleWhiteSpace);
3213 MOZ_ASSERT(atLastInvisibleWhiteSpace.IsSet());
3214 MOZ_ASSERT(atLastInvisibleWhiteSpace.IsContainerEmpty() ||
3215 atLastInvisibleWhiteSpace.IsAtLastContent());
3216 MOZ_ASSERT(result.EndRef().EqualsOrIsBefore(atLastInvisibleWhiteSpace));
3217 result.SetEnd(atLastInvisibleWhiteSpace.IsEndOfContainer()
3218 ? atLastInvisibleWhiteSpace
3219 : atLastInvisibleWhiteSpace.NextPoint());
3220 MOZ_ASSERT(result.IsPositionedAndValid());
3221 return result;
3224 // static
3225 Result<EditorDOMRangeInTexts, nsresult>
3226 WSRunScanner::GetRangeInTextNodesToBackspaceFrom(Element* aEditingHost,
3227 const EditorDOMPoint& aPoint) {
3228 // Corresponding to computing delete range part of
3229 // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
3230 MOZ_ASSERT(aPoint.IsSetAndValid());
3232 TextFragmentData textFragmentDataAtCaret(aPoint, aEditingHost);
3233 if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
3234 return Err(NS_ERROR_FAILURE);
3236 EditorDOMPointInText atPreviousChar =
3237 textFragmentDataAtCaret.GetPreviousEditableCharPoint(aPoint);
3238 if (!atPreviousChar.IsSet()) {
3239 return EditorDOMRangeInTexts(); // There is no content in the block.
3242 // XXX When previous char point is in an empty text node, we do nothing,
3243 // but this must look odd from point of user view. We should delete
3244 // something before aPoint.
3245 if (atPreviousChar.IsEndOfContainer()) {
3246 return EditorDOMRangeInTexts();
3249 // Extend delete range if previous char is a low surrogate following
3250 // a high surrogate.
3251 EditorDOMPointInText atNextChar = atPreviousChar.NextPoint();
3252 if (!atPreviousChar.IsStartOfContainer()) {
3253 if (atPreviousChar.IsCharLowSurrogateFollowingHighSurrogate()) {
3254 atPreviousChar = atPreviousChar.PreviousPoint();
3256 // If caret is in middle of a surrogate pair, delete the surrogate pair
3257 // (blink-compat).
3258 else if (atPreviousChar.IsCharHighSurrogateFollowedByLowSurrogate()) {
3259 atNextChar = atNextChar.NextPoint();
3263 // If the text node is preformatted, just remove the previous character.
3264 if (textFragmentDataAtCaret.IsPreformatted()) {
3265 return EditorDOMRangeInTexts(atPreviousChar, atNextChar);
3268 // If previous char is an ASCII white-spaces, delete all adjcent ASCII
3269 // whitespaces.
3270 EditorDOMRangeInTexts rangeToDelete;
3271 if (atPreviousChar.IsCharASCIISpace()) {
3272 EditorDOMPointInText startToDelete =
3273 textFragmentDataAtCaret.GetFirstASCIIWhiteSpacePointCollapsedTo(
3274 atPreviousChar);
3275 if (!startToDelete.IsSet()) {
3276 NS_WARNING(
3277 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3278 return Err(NS_ERROR_FAILURE);
3280 EditorDOMPointInText endToDelete =
3281 textFragmentDataAtCaret.GetEndOfCollapsibleASCIIWhiteSpaces(
3282 atPreviousChar);
3283 if (!endToDelete.IsSet()) {
3284 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3285 return Err(NS_ERROR_FAILURE);
3287 rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
3289 // if previous char is not an ASCII white-space, remove it.
3290 else {
3291 rangeToDelete = EditorDOMRangeInTexts(atPreviousChar, atNextChar);
3294 // If there is no removable and visible content, we should do nothing.
3295 if (rangeToDelete.Collapsed()) {
3296 return EditorDOMRangeInTexts();
3299 // And also delete invisible white-spaces if they become visible.
3300 TextFragmentData textFragmentDataAtStart =
3301 rangeToDelete.StartRef() != aPoint
3302 ? TextFragmentData(rangeToDelete.StartRef(), aEditingHost)
3303 : textFragmentDataAtCaret;
3304 TextFragmentData textFragmentDataAtEnd =
3305 rangeToDelete.EndRef() != aPoint
3306 ? TextFragmentData(rangeToDelete.EndRef(), aEditingHost)
3307 : textFragmentDataAtCaret;
3308 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
3309 NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
3310 return Err(NS_ERROR_FAILURE);
3312 EditorDOMRangeInTexts extendedRangeToDelete =
3313 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3314 textFragmentDataAtStart, textFragmentDataAtEnd);
3315 MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
3316 return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
3317 : rangeToDelete;
3320 // static
3321 Result<EditorDOMRangeInTexts, nsresult>
3322 WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
3323 Element* aEditingHost, const EditorDOMPoint& aPoint) {
3324 // Corresponding to computing delete range part of
3325 // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
3326 MOZ_ASSERT(aPoint.IsSetAndValid());
3328 TextFragmentData textFragmentDataAtCaret(aPoint, aEditingHost);
3329 if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) {
3330 return Err(NS_ERROR_FAILURE);
3332 EditorDOMPointInText atCaret =
3333 textFragmentDataAtCaret.GetInclusiveNextEditableCharPoint(aPoint);
3334 if (!atCaret.IsSet()) {
3335 return EditorDOMRangeInTexts(); // There is no content in the block.
3337 // If caret is in middle of a surrogate pair, we should remove next
3338 // character (blink-compat).
3339 if (!atCaret.IsEndOfContainer() &&
3340 atCaret.IsCharLowSurrogateFollowingHighSurrogate()) {
3341 atCaret = atCaret.NextPoint();
3344 // XXX When next char point is in an empty text node, we do nothing,
3345 // but this must look odd from point of user view. We should delete
3346 // something after aPoint.
3347 if (atCaret.IsEndOfContainer()) {
3348 return EditorDOMRangeInTexts();
3351 // Extend delete range if previous char is a low surrogate following
3352 // a high surrogate.
3353 EditorDOMPointInText atNextChar = atCaret.NextPoint();
3354 if (atCaret.IsCharHighSurrogateFollowedByLowSurrogate()) {
3355 atNextChar = atNextChar.NextPoint();
3358 // If the text node is preformatted, just remove the previous character.
3359 if (textFragmentDataAtCaret.IsPreformatted()) {
3360 return EditorDOMRangeInTexts(atCaret, atNextChar);
3363 // If next char is an ASCII whitespaces, delete all adjcent ASCII
3364 // whitespaces.
3365 EditorDOMRangeInTexts rangeToDelete;
3366 if (atCaret.IsCharASCIISpace()) {
3367 EditorDOMPointInText startToDelete =
3368 textFragmentDataAtCaret.GetFirstASCIIWhiteSpacePointCollapsedTo(
3369 atCaret);
3370 if (!startToDelete.IsSet()) {
3371 NS_WARNING(
3372 "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed");
3373 return Err(NS_ERROR_FAILURE);
3375 EditorDOMPointInText endToDelete =
3376 textFragmentDataAtCaret.GetEndOfCollapsibleASCIIWhiteSpaces(atCaret);
3377 if (!endToDelete.IsSet()) {
3378 NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed");
3379 return Err(NS_ERROR_FAILURE);
3381 rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
3383 // if next char is not an ASCII white-space, remove it.
3384 else {
3385 rangeToDelete = EditorDOMRangeInTexts(atCaret, atNextChar);
3388 // If there is no removable and visible content, we should do nothing.
3389 if (rangeToDelete.Collapsed()) {
3390 return EditorDOMRangeInTexts();
3393 // And also delete invisible white-spaces if they become visible.
3394 TextFragmentData textFragmentDataAtStart =
3395 rangeToDelete.StartRef() != aPoint
3396 ? TextFragmentData(rangeToDelete.StartRef(), aEditingHost)
3397 : textFragmentDataAtCaret;
3398 TextFragmentData textFragmentDataAtEnd =
3399 rangeToDelete.EndRef() != aPoint
3400 ? TextFragmentData(rangeToDelete.EndRef(), aEditingHost)
3401 : textFragmentDataAtCaret;
3402 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
3403 NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
3404 return Err(NS_ERROR_FAILURE);
3406 EditorDOMRangeInTexts extendedRangeToDelete =
3407 WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
3408 textFragmentDataAtStart, textFragmentDataAtEnd);
3409 MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid());
3410 return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
3411 : rangeToDelete;
3414 // static
3415 EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent(
3416 Element* aEditingHost, const nsIContent& aAtomicContent) {
3417 if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) {
3418 // Preceding white-spaces should be preserved, but the following
3419 // white-spaces should be invisible around `<br>` element.
3420 TextFragmentData textFragmentDataAfterBRElement(
3421 EditorDOMPoint::After(aAtomicContent), aEditingHost);
3422 if (NS_WARN_IF(!textFragmentDataAfterBRElement.IsInitialized())) {
3423 return EditorDOMRange(); // TODO: Make here return error with Err.
3425 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
3426 textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts(
3427 textFragmentDataAfterBRElement
3428 .InvisibleLeadingWhiteSpaceRangeRef());
3429 return followingInvisibleWhiteSpaces.IsPositioned() &&
3430 !followingInvisibleWhiteSpaces.Collapsed()
3431 ? EditorDOMRange(
3432 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
3433 followingInvisibleWhiteSpaces.EndRef())
3434 : EditorDOMRange(
3435 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
3436 EditorDOMPoint::After(aAtomicContent));
3439 if (!HTMLEditUtils::IsBlockElement(aAtomicContent)) {
3440 // Both preceding and following white-spaces around it should be preserved
3441 // around inline elements like `<img>`.
3442 return EditorDOMRange(
3443 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
3444 EditorDOMPoint::After(aAtomicContent));
3447 // Both preceding and following white-spaces can be invisible around a
3448 // block element.
3449 TextFragmentData textFragmentDataBeforeAtomicContent(
3450 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)), aEditingHost);
3451 if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent.IsInitialized())) {
3452 return EditorDOMRange(); // TODO: Make here return error with Err.
3454 const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces =
3455 textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts(
3456 textFragmentDataBeforeAtomicContent
3457 .InvisibleTrailingWhiteSpaceRangeRef());
3458 TextFragmentData textFragmentDataAfterAtomicContent(
3459 EditorDOMPoint::After(aAtomicContent), aEditingHost);
3460 if (NS_WARN_IF(!textFragmentDataAfterAtomicContent.IsInitialized())) {
3461 return EditorDOMRange(); // TODO: Make here return error with Err.
3463 const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
3464 textFragmentDataAfterAtomicContent.GetNonCollapsedRangeInTexts(
3465 textFragmentDataAfterAtomicContent
3466 .InvisibleLeadingWhiteSpaceRangeRef());
3467 if (precedingInvisibleWhiteSpaces.StartRef().IsSet() &&
3468 followingInvisibleWhiteSpaces.EndRef().IsSet()) {
3469 return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
3470 followingInvisibleWhiteSpaces.EndRef());
3472 if (precedingInvisibleWhiteSpaces.StartRef().IsSet()) {
3473 return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(),
3474 EditorDOMPoint::After(aAtomicContent));
3476 if (followingInvisibleWhiteSpaces.EndRef().IsSet()) {
3477 return EditorDOMRange(
3478 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
3479 followingInvisibleWhiteSpaces.EndRef());
3481 return EditorDOMRange(
3482 EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
3483 EditorDOMPoint::After(aAtomicContent));
3486 // static
3487 EditorDOMRange WSRunScanner::GetRangeForDeletingBlockElementBoundaries(
3488 const HTMLEditor& aHTMLEditor, const Element& aLeftBlockElement,
3489 const Element& aRightBlockElement,
3490 const EditorDOMPoint& aPointContainingTheOtherBlock) {
3491 MOZ_ASSERT(&aLeftBlockElement != &aRightBlockElement);
3492 MOZ_ASSERT_IF(
3493 aPointContainingTheOtherBlock.IsSet(),
3494 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement ||
3495 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement);
3496 MOZ_ASSERT_IF(
3497 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement,
3498 aRightBlockElement.IsInclusiveDescendantOf(
3499 aPointContainingTheOtherBlock.GetChild()));
3500 MOZ_ASSERT_IF(
3501 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement,
3502 aLeftBlockElement.IsInclusiveDescendantOf(
3503 aPointContainingTheOtherBlock.GetChild()));
3504 MOZ_ASSERT_IF(
3505 !aPointContainingTheOtherBlock.IsSet(),
3506 !aRightBlockElement.IsInclusiveDescendantOf(&aLeftBlockElement));
3507 MOZ_ASSERT_IF(
3508 !aPointContainingTheOtherBlock.IsSet(),
3509 !aLeftBlockElement.IsInclusiveDescendantOf(&aRightBlockElement));
3510 MOZ_ASSERT_IF(!aPointContainingTheOtherBlock.IsSet(),
3511 EditorRawDOMPoint(const_cast<Element*>(&aLeftBlockElement))
3512 .IsBefore(EditorRawDOMPoint(
3513 const_cast<Element*>(&aRightBlockElement))));
3515 const Element* editingHost = aHTMLEditor.GetActiveEditingHost();
3517 EditorDOMRange range;
3518 // Include trailing invisible white-spaces in aLeftBlockElement.
3519 TextFragmentData textFragmentDataAtEndOfLeftBlockElement(
3520 aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement
3521 ? aPointContainingTheOtherBlock
3522 : EditorDOMPoint::AtEndOf(const_cast<Element&>(aLeftBlockElement)),
3523 editingHost);
3524 if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement.IsInitialized())) {
3525 return EditorDOMRange(); // TODO: Make here return error with Err.
3527 if (textFragmentDataAtEndOfLeftBlockElement.StartsFromInvisibleBRElement()) {
3528 // If the left block element ends with an invisible `<br>` element,
3529 // it'll be deleted (and it means there is no invisible trailing
3530 // white-spaces). Therefore, the range should start from the invisible
3531 // `<br>` element.
3532 range.SetStart(EditorDOMPoint(
3533 textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr()));
3534 } else {
3535 const EditorDOMRange& trailingWhiteSpaceRange =
3536 textFragmentDataAtEndOfLeftBlockElement
3537 .InvisibleTrailingWhiteSpaceRangeRef();
3538 if (trailingWhiteSpaceRange.StartRef().IsSet()) {
3539 range.SetStart(trailingWhiteSpaceRange.StartRef());
3540 } else {
3541 range.SetStart(textFragmentDataAtEndOfLeftBlockElement.ScanStartRef());
3544 // Include leading invisible white-spaces in aRightBlockElement.
3545 TextFragmentData textFragmentDataAtStartOfRightBlockElement(
3546 aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement &&
3547 !aPointContainingTheOtherBlock.IsEndOfContainer()
3548 ? aPointContainingTheOtherBlock.NextPoint()
3549 : EditorDOMPoint(const_cast<Element*>(&aRightBlockElement), 0),
3550 editingHost);
3551 if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement.IsInitialized())) {
3552 return EditorDOMRange(); // TODO: Make here return error with Err.
3554 const EditorDOMRange& leadingWhiteSpaceRange =
3555 textFragmentDataAtStartOfRightBlockElement
3556 .InvisibleLeadingWhiteSpaceRangeRef();
3557 if (leadingWhiteSpaceRange.EndRef().IsSet()) {
3558 range.SetEnd(leadingWhiteSpaceRange.EndRef());
3559 } else {
3560 range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef());
3562 return range;
3565 // static
3566 EditorDOMRange
3567 WSRunScanner::GetRangeContainingInvisibleWhiteSpacesAtRangeBoundaries(
3568 Element* aEditingHost, const EditorDOMRange& aRange) {
3569 MOZ_ASSERT(aRange.IsPositionedAndValid());
3570 MOZ_ASSERT(aRange.EndRef().IsSetAndValid());
3571 MOZ_ASSERT(aRange.StartRef().IsSetAndValid());
3573 EditorDOMRange result;
3574 TextFragmentData textFragmentDataAtStart(aRange.StartRef(), aEditingHost);
3575 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
3576 return EditorDOMRange(); // TODO: Make here return error with Err.
3578 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart =
3579 textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
3580 textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef());
3581 if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() &&
3582 !invisibleLeadingWhiteSpacesAtStart.Collapsed()) {
3583 result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef());
3584 } else {
3585 const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
3586 textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
3587 textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef());
3588 if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() &&
3589 !invisibleTrailingWhiteSpacesAtStart.Collapsed()) {
3590 MOZ_ASSERT(
3591 invisibleTrailingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore(
3592 aRange.StartRef()));
3593 result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef());
3595 // If there is no invisible white-space and the line starts with a
3596 // text node, shrink the range to start of the text node.
3597 else if (!aRange.StartRef().IsInTextNode() &&
3598 textFragmentDataAtStart.StartsFromBlockBoundary() &&
3599 textFragmentDataAtStart.EndRef().IsInTextNode()) {
3600 result.SetStart(textFragmentDataAtStart.EndRef());
3603 if (!result.StartRef().IsSet()) {
3604 result.SetStart(aRange.StartRef());
3607 TextFragmentData textFragmentDataAtEnd(aRange.EndRef(), aEditingHost);
3608 if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
3609 return EditorDOMRange(); // TODO: Make here return error with Err.
3611 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
3612 textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
3613 textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef());
3614 if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
3615 !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
3616 result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
3617 } else {
3618 const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
3619 textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
3620 textFragmentDataAtEnd.InvisibleLeadingWhiteSpaceRangeRef());
3621 if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
3622 !invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
3623 MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore(
3624 invisibleLeadingWhiteSpacesAtEnd.EndRef()));
3625 result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
3627 // If there is no invisible white-space and the line ends with a text
3628 // node, shrink the range to end of the text node.
3629 else if (!aRange.EndRef().IsInTextNode() &&
3630 textFragmentDataAtEnd.EndsByBlockBoundary() &&
3631 textFragmentDataAtEnd.StartRef().IsInTextNode()) {
3632 result.SetEnd(EditorDOMPoint::AtEndOf(
3633 *textFragmentDataAtEnd.StartRef().ContainerAsText()));
3636 if (!result.EndRef().IsSet()) {
3637 result.SetEnd(aRange.EndRef());
3639 MOZ_ASSERT(result.IsPositionedAndValid());
3640 return result;
3643 /******************************************************************************
3644 * Utilities for other things.
3645 ******************************************************************************/
3647 // static
3648 Result<bool, nsresult>
3649 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
3650 const HTMLEditor& aHTMLEditor, nsRange& aRange,
3651 const Element* aEditingHost) {
3652 MOZ_ASSERT(aRange.IsPositioned());
3653 MOZ_ASSERT(!aRange.IsInSelection(),
3654 "Changing range in selection may cause running script");
3656 if (NS_WARN_IF(!aRange.GetStartContainer()) ||
3657 NS_WARN_IF(!aRange.GetEndContainer())) {
3658 return Err(NS_ERROR_FAILURE);
3661 if (!aRange.GetStartContainer()->IsContent() ||
3662 !aRange.GetEndContainer()->IsContent()) {
3663 return false;
3666 // If the range crosses a block boundary, we should do nothing for now
3667 // because it hits a bug of inserting a padding `<br>` element after
3668 // joining the blocks.
3669 if (HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3670 *aRange.GetStartContainer()->AsContent(), aEditingHost) !=
3671 HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement(
3672 *aRange.GetEndContainer()->AsContent(), aEditingHost)) {
3673 return false;
3676 nsIContent* startContent = nullptr;
3677 if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() &&
3678 aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) {
3679 // If next content is a visible `<br>` element, special inline content
3680 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3681 // element like `<hr>`, the range should start with it.
3682 TextFragmentData textFragmentDataAtStart(
3683 EditorRawDOMPoint(aRange.StartRef()), aEditingHost);
3684 if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) {
3685 return Err(NS_ERROR_FAILURE);
3687 if (textFragmentDataAtStart.EndsByVisibleBRElement()) {
3688 startContent = textFragmentDataAtStart.EndReasonBRElementPtr();
3689 } else if (textFragmentDataAtStart.EndsBySpecialContent() ||
3690 (textFragmentDataAtStart.EndsByOtherBlockElement() &&
3691 !HTMLEditUtils::IsContainerNode(
3692 *textFragmentDataAtStart
3693 .EndReasonOtherBlockElementPtr()))) {
3694 startContent = textFragmentDataAtStart.GetEndReasonContent();
3698 nsIContent* endContent = nullptr;
3699 if (aRange.GetEndContainer() && aRange.GetEndContainer()->IsText() &&
3700 !aRange.EndOffset()) {
3701 // If previous content is a visible `<br>` element, special inline content
3702 // (e.g., `<img>`, non-editable text node, etc) or a block level void
3703 // element like `<hr>`, the range should end after it.
3704 TextFragmentData textFragmentDataAtEnd(EditorRawDOMPoint(aRange.EndRef()),
3705 aEditingHost);
3706 if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) {
3707 return Err(NS_ERROR_FAILURE);
3709 if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) {
3710 endContent = textFragmentDataAtEnd.StartReasonBRElementPtr();
3711 } else if (textFragmentDataAtEnd.StartsFromSpecialContent() ||
3712 (textFragmentDataAtEnd.StartsFromOtherBlockElement() &&
3713 !HTMLEditUtils::IsContainerNode(
3714 *textFragmentDataAtEnd
3715 .StartReasonOtherBlockElementPtr()))) {
3716 endContent = textFragmentDataAtEnd.GetStartReasonContent();
3720 if (!startContent && !endContent) {
3721 return false;
3724 nsresult rv = aRange.SetStartAndEnd(
3725 startContent ? RangeBoundary(
3726 startContent->GetParentNode(),
3727 startContent->GetPreviousSibling()) // at startContent
3728 : aRange.StartRef(),
3729 endContent ? RangeBoundary(endContent->GetParentNode(),
3730 endContent) // after endContent
3731 : aRange.EndRef());
3732 if (NS_FAILED(rv)) {
3733 NS_WARNING("nsRange::SetStartAndEnd() failed");
3734 return Err(rv);
3736 return true;
3739 } // namespace mozilla