diff --git a/editor/libeditor/EditorUtils.cpp b/editor/libeditor/EditorUtils.cpp index 90d4ff225b78..c306d0079cf0 100644 --- a/editor/libeditor/EditorUtils.cpp +++ b/editor/libeditor/EditorUtils.cpp @@ -5,9 +5,11 @@ #include "EditorUtils.h" +#include "WSRunObject.h" #include "mozilla/ComputedStyle.h" #include "mozilla/ContentIterator.h" #include "mozilla/EditorDOMPoint.h" +#include "mozilla/HTMLEditor.h" #include "mozilla/OwningNonNull.h" #include "mozilla/TextEditor.h" #include "mozilla/dom/Document.h" @@ -239,6 +241,7 @@ AutoRangeArray::ExtendAnchorFocusRangeFor( NS_WARNING("Failed to extend the range, but ignored"); return directionAndAmountResult; } + if (NS_WARN_IF(!frameSelection->IsValidSelectionPoint( extendedRange->GetStartContainer())) || NS_WARN_IF(!frameSelection->IsValidSelectionPoint( @@ -261,6 +264,58 @@ AutoRangeArray::ExtendAnchorFocusRangeFor( return directionAndAmountResult; } +Result +AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent( + const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, + IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent, + const Element* aEditingHost) { + if (IsCollapsed()) { + return false; + } + + switch (aDirectionAndAmount) { + case nsIEditor::eNext: + case nsIEditor::eNextWord: + case nsIEditor::ePrevious: + case nsIEditor::ePreviousWord: + break; + default: + return false; + } + + bool changed = false; + for (auto& range : mRanges) { + MOZ_ASSERT(!range->IsInSelection(), + "Changing range in selection may cause running script"); + Result result = + WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( + aHTMLEditor, range, aEditingHost); + if (result.isErr()) { + NS_WARNING( + "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() " + "failed"); + return Err(result.inspectErr()); + } + changed |= result.inspect(); + } + + if (mRanges.Length() == 1 && aIfSelectingOnlyOneAtomicContent == + IfSelectingOnlyOneAtomicContent::Collapse) { + MOZ_ASSERT(mRanges[0].get() == mAnchorFocusRange.get()); + if (mAnchorFocusRange->GetStartContainer() == + mAnchorFocusRange->GetEndContainer() && + mAnchorFocusRange->GetChildAtStartOffset() && + mAnchorFocusRange->StartRef().GetNextSiblingOfChildAtOffset() == + mAnchorFocusRange->GetChildAtEndOffset()) { + mAnchorFocusRange->Collapse(aDirectionAndAmount == nsIEditor::eNext || + aDirectionAndAmount == nsIEditor::eNextWord); + changed = true; + } + } + + return changed; +} + /****************************************************************************** * some helper classes for iterating the dom tree *****************************************************************************/ diff --git a/editor/libeditor/EditorUtils.h b/editor/libeditor/EditorUtils.h index c844500e8cc6..ced7c29871d8 100644 --- a/editor/libeditor/EditorUtils.h +++ b/editor/libeditor/EditorUtils.h @@ -812,6 +812,24 @@ class MOZ_STACK_CLASS AutoRangeArray final { ExtendAnchorFocusRangeFor(const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount); + /** + * For compatiblity with the other browsers, we should shrink ranges to + * start from an atomic content and/or end after one instead of start + * from end of a preceding text node and end by start of a follwing text + * node. Returns true if this modifies a range. + */ + enum class IfSelectingOnlyOneAtomicContent { + Collapse, // Collapse to the range selecting only one atomic content to + // start or after of it. Whether to collapse start or after + // it depends on aDirectionAndAmount. This is ignored if + // there are multiple ranges. + KeepSelecting, // Won't collapse the range. + }; + Result ShrinkRangesIfStartFromOrEndAfterAtomicContent( + const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, + IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent, + const dom::Element* aEditingHost); + /** * The following methods are same as `Selection`'s methods. */ diff --git a/editor/libeditor/HTMLEditSubActionHandler.cpp b/editor/libeditor/HTMLEditSubActionHandler.cpp index 3d386ddcfba4..5eb6ed9102ce 100644 --- a/editor/libeditor/HTMLEditSubActionHandler.cpp +++ b/editor/libeditor/HTMLEditSubActionHandler.cpp @@ -2487,12 +2487,11 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { const EditorDOMPoint& aPointToDelete); /** - * HandleDeleteCollapsedSelectionAtAtomicContent() handles deletion of - * atomic elements like `
`, `
`, ``, ``, etc and - * data nodes except text node (e.g., comment node). - * Note that don't call this directly with `
` element. Instead, call - * `HandleDeleteCollapsedSelectionAtHRElement()`. - * Note that don't call this for invisible `
` element. + * HandleDeleteAtomicContent() handles deletion of atomic elements like + * `
`, `
`, ``, ``, etc and data nodes except text node + * (e.g., comment node). Note that don't call this directly with `
` + * element. Instead, call `HandleDeleteCollapsedSelectionAtHRElement()`. Note + * that don't call this for invisible `
` element. * * @param aAtomicContent The atomic content to be deleted. * @param aCaretPoint The caret point (i.e., selection start or @@ -2501,10 +2500,12 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { * with the caret point. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult - HandleDeleteCollapsedSelectionAtAtomicContent( - HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, - const EditorDOMPoint& aCaretPoint, - const WSRunScanner& aWSRunScannerAtCaret); + HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, + const EditorDOMPoint& aCaretPoint, + const WSRunScanner& aWSRunScannerAtCaret); + nsresult ComputeRangesToDeleteAtomicContent( + const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent, + AutoRangeArray& aRangesToDelete) const; /** * HandleDeleteCollapsedSelectionAtHRElement() handles deletion around @@ -3151,11 +3152,11 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete( if (NS_WARN_IF(!startPoint.IsSet())) { return NS_ERROR_FAILURE; } + RefPtr editingHost = aHTMLEditor.GetActiveEditingHost(); + if (NS_WARN_IF(!editingHost)) { + return NS_ERROR_FAILURE; + } if (startPoint.GetContainerAsContent()) { - RefPtr editingHost = aHTMLEditor.GetActiveEditingHost(); - if (NS_WARN_IF(!editingHost)) { - return NS_ERROR_FAILURE; - } AutoEmptyBlockAncestorDeleter deleter; if (deleter.ScanEmptyBlockInclusiveAncestor( aHTMLEditor, *startPoint.GetContainerAsContent(), *editingHost)) { @@ -3196,7 +3197,25 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete( NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed"); return extendResult.unwrapErr(); } - aDirectionAndAmount = extendResult.unwrap(); + + // For compatibility with other browsers, we should set target ranges + // to start from and/or end after an atomic content rather than start + // from preceding text node end nor end at following text node start. + Result shrunkenResult = + aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent( + aHTMLEditor, aDirectionAndAmount, + AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse, + editingHost); + if (shrunkenResult.isErr()) { + NS_WARNING( + "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() " + "failed"); + return shrunkenResult.unwrapErr(); + } + + if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) { + aDirectionAndAmount = extendResult.unwrap(); + } if (aDirectionAndAmount == nsIEditor::eNone) { MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1); @@ -3296,8 +3315,8 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( } // If we are inside an empty block, delete it. + RefPtr editingHost = aHTMLEditor.GetActiveEditingHost(); if (startPoint.GetContainerAsContent()) { - RefPtr editingHost = aHTMLEditor.GetActiveEditingHost(); if (NS_WARN_IF(!editingHost)) { return EditActionResult(NS_ERROR_FAILURE); } @@ -3346,6 +3365,20 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( aHTMLEditor, *aHTMLEditor.SelectionRefPtr(), *startPoint.GetContainer(), &aRangesToDelete); + // Calling `ExtendAnchorFocusRangeFor()` and + // `ShrinkRangesIfStartFromOrEndAfterAtomicContent()` may move caret to + // the container of deleting atomic content. However, it may be different + // from the original caret's container. The original caret container may + // be important to put caret after deletion so that let's cache the + // original position. + Maybe caretPoint; + if (aRangesToDelete.IsCollapsed() && !aRangesToDelete.Ranges().IsEmpty()) { + caretPoint = Some(aRangesToDelete.GetStartPointOfFirstRange()); + if (NS_WARN_IF(!caretPoint.ref().IsInContentNode())) { + return EditActionResult(NS_ERROR_FAILURE); + } + } + Result extendResult = aRangesToDelete.ExtendAnchorFocusRangeFor(aHTMLEditor, aDirectionAndAmount); @@ -3353,7 +3386,30 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( NS_WARNING("AutoRangeArray::ExtendAnchorFocusRangeFor() failed"); return EditActionResult(extendResult.unwrapErr()); } - aDirectionAndAmount = extendResult.unwrap(); + if (caretPoint.isSome() && !caretPoint.ref().IsSetAndValid()) { + NS_WARNING("The caret position became invalid"); + return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); + } + + // If there is only one range and it selects an atomic content, we should + // delete it with collapsed range path for making consistent behavior + // between both cases, the content is selected case and caret is at it or + // after it case. + Result shrunkenResult = + aRangesToDelete.ShrinkRangesIfStartFromOrEndAfterAtomicContent( + aHTMLEditor, aDirectionAndAmount, + AutoRangeArray::IfSelectingOnlyOneAtomicContent::Collapse, + editingHost); + if (shrunkenResult.isErr()) { + NS_WARNING( + "AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent() " + "failed"); + return EditActionResult(shrunkenResult.unwrapErr()); + } + + if (!shrunkenResult.inspect() || !aRangesToDelete.IsCollapsed()) { + aDirectionAndAmount = extendResult.unwrap(); + } if (aDirectionAndAmount == nsIEditor::eNone) { MOZ_ASSERT(aRangesToDelete.Ranges().Length() == 1); @@ -3369,21 +3425,20 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( } if (aRangesToDelete.IsCollapsed()) { - EditorDOMPoint caretPoint(aRangesToDelete.GetStartPointOfFirstRange()); - if (NS_WARN_IF(!caretPoint.IsInContentNode())) { - return EditActionResult(NS_ERROR_FAILURE); - } - if (!EditorUtils::IsEditableContent(*caretPoint.ContainerAsContent(), - EditorType::HTML)) { + // Use the original caret position for handling the deletion around + // collapsed range because the container may be different from the + // new collapsed position's container. + if (!EditorUtils::IsEditableContent( + *caretPoint.ref().ContainerAsContent(), EditorType::HTML)) { return EditActionCanceled(); } - WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint); + WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint.ref()); WSScanResult scanFromCaretPointResult = aDirectionAndAmount == nsIEditor::eNext ? wsRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom( - caretPoint) + caretPoint.ref()) : wsRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom( - caretPoint); + caretPoint.ref()); if (!scanFromCaretPointResult.GetContent()) { return EditActionCanceled(); } @@ -3407,7 +3462,7 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( DeleteContentNodeAndJoinTextNodesAroundIt( aHTMLEditor, MOZ_KnownLive(*scanFromCaretPointResult.BRElementPtr()), - caretPoint); + caretPoint.ref()); if (NS_FAILED(rv)) { NS_WARNING( "WhiteSpaceVisibilityKeeper::" @@ -3421,8 +3476,8 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); } AutoRangeArray rangesToDelete(*aHTMLEditor.SelectionRefPtr()); - caretPoint = aRangesToDelete.GetStartPointOfFirstRange(); - if (!caretPoint.IsSet()) { + caretPoint = Some(aRangesToDelete.GetStartPointOfFirstRange()); + if (!caretPoint.ref().IsSet()) { NS_WARNING( "New selection after deleting invisible `
` element was " "invalid"); @@ -3434,14 +3489,15 @@ EditActionResult HTMLEditor::AutoDeleteRangesHandler::Run( NS_EVENT_BITS_MUTATION_NODEREMOVEDFROMDOCUMENT)) { // Let's check whether there is new invisible `
` element // for avoiding infinit recursive calls. - WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint); + WSRunScanner wsRunScannerAtCaret(aHTMLEditor, caretPoint.ref()); WSScanResult scanFromCaretPointResult = aDirectionAndAmount == nsIEditor::eNext ? wsRunScannerAtCaret - .ScanNextVisibleNodeOrBlockBoundaryFrom(caretPoint) + .ScanNextVisibleNodeOrBlockBoundaryFrom( + caretPoint.ref()) : wsRunScannerAtCaret .ScanPreviousVisibleNodeOrBlockBoundaryFrom( - caretPoint); + caretPoint.ref()); if (scanFromCaretPointResult.ReachedBRElement() && !aHTMLEditor.IsVisibleBRElement( scanFromCaretPointResult.BRElementPtr())) { @@ -3499,6 +3555,20 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAroundCollapsedRanges( return rv; } + if (aScanFromCaretPointResult.ReachedSpecialContent() || + aScanFromCaretPointResult.ReachedBRElement()) { + if (aScanFromCaretPointResult.GetContent() == + aWSRunScannerAtCaret.GetEditingHost()) { + return NS_OK; + } + nsresult rv = ComputeRangesToDeleteAtomicContent( + aHTMLEditor, *aScanFromCaretPointResult.GetContent(), aRangesToDelete); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent() failed"); + return rv; + } + return NS_OK; } @@ -3562,13 +3632,12 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAroundCollapsedRanges( aWSRunScannerAtCaret.GetEditingHost()) { return EditActionHandled(); } - EditActionResult result = HandleDeleteCollapsedSelectionAtAtomicContent( + EditActionResult result = HandleDeleteAtomicContent( aHTMLEditor, MOZ_KnownLive(*aScanFromCaretPointResult.GetContent()), aWSRunScannerAtCaret.ScanStartRef(), aWSRunScannerAtCaret); - NS_WARNING_ASSERTION(result.Succeeded(), - "AutoDeleteRangesHandler::" - "HandleDeleteCollapsedSelectionAtAtomicContent() " - "failed"); + NS_WARNING_ASSERTION( + result.Succeeded(), + "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed"); return result; } @@ -3942,12 +4011,11 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteCollapsedSelectionAtHRElement( return EditActionHandled(canDeleteHRElement.unwrapErr()); } if (canDeleteHRElement.inspect()) { - EditActionResult result = HandleDeleteCollapsedSelectionAtAtomicContent( + EditActionResult result = HandleDeleteAtomicContent( aHTMLEditor, aHRElement, aCaretPoint, aWSRunScannerAtCaret); NS_WARNING_ASSERTION( result.Succeeded(), - "AutoDeleteRangesHandler::" - "HandleDeleteCollapsedSelectionAtAtomicContent() failed"); + "AutoDeleteRangesHandler::HandleDeleteAtomicContent() failed"); return result; } @@ -3997,11 +4065,28 @@ HTMLEditor::AutoDeleteRangesHandler::HandleDeleteCollapsedSelectionAtHRElement( return EditActionHandled(rv); } -EditActionResult HTMLEditor::AutoDeleteRangesHandler:: - HandleDeleteCollapsedSelectionAtAtomicContent( - HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, - const EditorDOMPoint& aCaretPoint, - const WSRunScanner& aWSRunScannerAtCaret) { +nsresult +HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteAtomicContent( + const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent, + AutoRangeArray& aRangesToDelete) const { + EditorDOMRange rangeToDelete = + WSRunScanner::GetRangesForDeletingAtomicContent(aHTMLEditor, + aAtomicContent); + if (!rangeToDelete.IsPositioned()) { + NS_WARNING("WSRunScanner::GetRangeForDeleteAContentNode() failed"); + return NS_ERROR_FAILURE; + } + nsresult rv = aRangesToDelete.SetStartAndEnd(rangeToDelete.StartRef(), + rangeToDelete.EndRef()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "AutoRangeArray::SetStartAndEnd() failed"); + return rv; +} + +EditActionResult HTMLEditor::AutoDeleteRangesHandler::HandleDeleteAtomicContent( + HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, + const EditorDOMPoint& aCaretPoint, + const WSRunScanner& aWSRunScannerAtCaret) { MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); MOZ_ASSERT_IF(aAtomicContent.IsHTMLElement(nsGkAtoms::br), aHTMLEditor.IsVisibleBRElement(&aAtomicContent)); diff --git a/editor/libeditor/WSRunObject.cpp b/editor/libeditor/WSRunObject.cpp index 0302ef9dfd6e..4194ba66703b 100644 --- a/editor/libeditor/WSRunObject.cpp +++ b/editor/libeditor/WSRunObject.cpp @@ -1598,6 +1598,46 @@ WSRunScanner::TextFragmentData::InvisibleTrailingWhiteSpaceRangeRef() const { return mTrailingWhiteSpaceRange.ref(); } +EditorDOMRangeInTexts +WSRunScanner::TextFragmentData::GetNonCollapsedRangeInTexts( + const EditorDOMRange& aRange) const { + if (!aRange.IsPositioned()) { + return EditorDOMRangeInTexts(); + } + if (aRange.IsInTextNodes()) { + if (aRange.Collapsed()) { + return EditorDOMRangeInTexts(); + } + // Note that this may return a range which don't include any invisible + // white-spaces due to empty text nodes. + return aRange.GetAsInTexts(); + } + + EditorDOMPointInText firstPoint = + aRange.StartRef().IsInTextNode() + ? aRange.StartRef().AsInText() + : GetInclusiveNextEditableCharPoint(aRange.StartRef()); + if (!firstPoint.IsSet()) { + return EditorDOMRangeInTexts(); + } + EditorDOMPointInText endPoint; + if (aRange.EndRef().IsInTextNode()) { + endPoint = aRange.EndRef().AsInText(); + } else { + // FYI: GetPreviousEditableCharPoint() returns last character's point + // of preceding text node if it's not empty, but we need end of + // the text node here. + endPoint = GetPreviousEditableCharPoint(aRange.EndRef()); + if (endPoint.IsSet() && endPoint.IsAtLastContent()) { + MOZ_ALWAYS_TRUE(endPoint.AdvanceOffset()); + } + } + if (!endPoint.IsSet() || firstPoint == endPoint) { + return EditorDOMRangeInTexts(); + } + return EditorDOMRangeInTexts(firstPoint, endPoint); +} + const WSRunScanner::VisibleWhiteSpacesData& WSRunScanner::TextFragmentData::VisibleWhiteSpacesDataRef() const { if (mVisibleWhiteSpacesData.isSome()) { @@ -3194,4 +3234,165 @@ WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom( : rangeToDelete; } +// static +EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent( + const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent) { + if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) { + // Preceding white-spaces should be preserved, but the following + // white-spaces should be invisible around `
` element. + Element* editingHost = aHTMLEditor.GetActiveEditingHost(); + TextFragmentData textFragmentDataAfterBRElement( + EditorDOMPoint::After(aAtomicContent), editingHost); + const EditorDOMRangeInTexts followingInvisibleWhiteSpaces = + textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts( + textFragmentDataAfterBRElement + .InvisibleLeadingWhiteSpaceRangeRef()); + return followingInvisibleWhiteSpaces.IsPositioned() && + !followingInvisibleWhiteSpaces.Collapsed() + ? EditorDOMRange( + EditorDOMPoint(const_cast(&aAtomicContent)), + followingInvisibleWhiteSpaces.EndRef()) + : EditorDOMRange( + EditorDOMPoint(const_cast(&aAtomicContent)), + EditorDOMPoint::After(aAtomicContent)); + } + + if (!HTMLEditUtils::IsBlockElement(aAtomicContent)) { + // Both preceding and following white-spaces around it should be preserved + // around inline elements like ``. + return EditorDOMRange( + EditorDOMPoint(const_cast(&aAtomicContent)), + EditorDOMPoint::After(aAtomicContent)); + } + + // Both preceding and following white-spaces can be invisible around a + // block element. + Element* editingHost = aHTMLEditor.GetActiveEditingHost(); + TextFragmentData textFragmentDataBeforeAtomicContent( + EditorDOMPoint(const_cast(&aAtomicContent)), editingHost); + const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces = + textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts( + textFragmentDataBeforeAtomicContent + .InvisibleTrailingWhiteSpaceRangeRef()); + TextFragmentData textFragmentDataAfterAtomicContent( + EditorDOMPoint::After(aAtomicContent), editingHost); + const EditorDOMRangeInTexts followingInvisibleWhiteSpaces = + textFragmentDataAfterAtomicContent.GetNonCollapsedRangeInTexts( + textFragmentDataAfterAtomicContent + .InvisibleLeadingWhiteSpaceRangeRef()); + if (precedingInvisibleWhiteSpaces.StartRef().IsSet() && + followingInvisibleWhiteSpaces.EndRef().IsSet()) { + return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(), + followingInvisibleWhiteSpaces.EndRef()); + } + if (precedingInvisibleWhiteSpaces.StartRef().IsSet()) { + return EditorDOMRange(precedingInvisibleWhiteSpaces.StartRef(), + EditorDOMPoint::After(aAtomicContent)); + } + if (followingInvisibleWhiteSpaces.EndRef().IsSet()) { + return EditorDOMRange( + EditorDOMPoint(const_cast(&aAtomicContent)), + followingInvisibleWhiteSpaces.EndRef()); + } + return EditorDOMRange( + EditorDOMPoint(const_cast(&aAtomicContent)), + EditorDOMPoint::After(aAtomicContent)); +} + +/****************************************************************************** + * Utilities for other things. + ******************************************************************************/ + +// static +Result +WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( + const HTMLEditor& aHTMLEditor, nsRange& aRange, + const Element* aEditingHost) { + MOZ_ASSERT(aRange.IsPositioned()); + MOZ_ASSERT(!aRange.IsInSelection(), + "Changing range in selection may cause running script"); + + if (NS_WARN_IF(!aRange.GetStartContainer()) || + NS_WARN_IF(!aRange.GetEndContainer())) { + return Err(NS_ERROR_FAILURE); + } + + if (!aRange.GetStartContainer()->IsContent() || + !aRange.GetEndContainer()->IsContent()) { + return false; + } + + // If the range crosses a block boundary, we should do nothing for now + // because it hits a bug of inserting a padding `
` element after + // joining the blocks. + if (HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement( + *aRange.GetStartContainer()->AsContent(), aEditingHost) != + HTMLEditUtils::GetInclusiveAncestorBlockElementExceptHRElement( + *aRange.GetEndContainer()->AsContent(), aEditingHost)) { + return false; + } + + nsIContent* startContent = nullptr; + if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() && + aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) { + // If next content is a visible `
` element, special inline content + // (e.g., ``, non-editable text node, etc) or a block level void + // element like `
`, the range should start with it. + TextFragmentData textFragmentDataAtStart( + EditorRawDOMPoint(aRange.StartRef()), aEditingHost); + if (textFragmentDataAtStart.EndsByBRElement()) { + if (aHTMLEditor.IsVisibleBRElement( + textFragmentDataAtStart.EndReasonBRElementPtr())) { + startContent = textFragmentDataAtStart.EndReasonBRElementPtr(); + } + } else if (textFragmentDataAtStart.EndsBySpecialContent() || + (textFragmentDataAtStart.EndsByOtherBlockElement() && + !HTMLEditUtils::IsContainerNode( + *textFragmentDataAtStart + .EndReasonOtherBlockElementPtr()))) { + startContent = textFragmentDataAtStart.GetEndReasonContent(); + } + } + + nsIContent* endContent = nullptr; + if (aRange.GetEndContainer() && aRange.GetEndContainer()->IsText() && + !aRange.EndOffset()) { + // If previous content is a visible `
` element, special inline content + // (e.g., ``, non-editable text node, etc) or a block level void + // element like `
`, the range should end after it. + TextFragmentData textFragmentDataAtEnd(EditorRawDOMPoint(aRange.EndRef()), + aEditingHost); + if (textFragmentDataAtEnd.StartsFromBRElement()) { + if (aHTMLEditor.IsVisibleBRElement( + textFragmentDataAtEnd.StartReasonBRElementPtr())) { + endContent = textFragmentDataAtEnd.StartReasonBRElementPtr(); + } + } else if (textFragmentDataAtEnd.StartsFromSpecialContent() || + (textFragmentDataAtEnd.StartsFromOtherBlockElement() && + !HTMLEditUtils::IsContainerNode( + *textFragmentDataAtEnd + .StartReasonOtherBlockElementPtr()))) { + endContent = textFragmentDataAtEnd.GetStartReasonContent(); + } + } + + if (!startContent && !endContent) { + return false; + } + + nsresult rv = aRange.SetStartAndEnd( + startContent ? RangeBoundary( + startContent->GetParentNode(), + startContent->GetPreviousSibling()) // at startContent + : aRange.StartRef(), + endContent ? RangeBoundary(endContent->GetParentNode(), + endContent) // after endContent + : aRange.EndRef()); + if (NS_FAILED(rv)) { + NS_WARNING("nsRange::SetStartAndEnd() failed"); + return Err(rv); + } + return true; +} + } // namespace mozilla diff --git a/editor/libeditor/WSRunObject.h b/editor/libeditor/WSRunObject.h index 4f7f639bd6ff..cc5d0927d679 100644 --- a/editor/libeditor/WSRunObject.h +++ b/editor/libeditor/WSRunObject.h @@ -360,6 +360,23 @@ class MOZ_STACK_CLASS WSRunScanner final { GetRangeInTextNodesToBackspaceFrom(const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); + /** + * GetRangesForDeletingAtomicContent() returns the range to delete + * aAtomicContent. If it's followed by invisible white-spaces, they will + * be included into the range. + */ + static EditorDOMRange GetRangesForDeletingAtomicContent( + const HTMLEditor& aHTMLEditor, const nsIContent& aAtomicContent); + + /** + * ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() may shrink aRange if it + * starts and/or ends with an atomic content, but the range boundary + * is in adjacent text nodes. Returns true if this modifies the range. + */ + static Result ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( + const HTMLEditor& aHTMLEditor, nsRange& aRange, + const Element* aEditingHost); + /** * GetPrecedingBRElementUnlessVisibleContentFound() scans a `
` element * backward, but stops scanning it if the scanner finds visible character @@ -869,6 +886,13 @@ class MOZ_STACK_CLASS WSRunScanner final { EditorDOMPointInText GetFirstASCIIWhiteSpacePointCollapsedTo( const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const; + /** + * GetNonCollapsedRangeInTexts() returns non-empty range in texts which + * is the largest range in aRange if there is some text nodes. + */ + EditorDOMRangeInTexts GetNonCollapsedRangeInTexts( + const EditorDOMRange& aRange) const; + /** * InvisibleLeadingWhiteSpaceRangeRef() retruns reference to two DOM points, * start of the line and first visible point or end of the hard line. When diff --git a/testing/web-platform/meta/input-events/input-events-get-target-ranges-backspace.tentative.html.ini b/testing/web-platform/meta/input-events/input-events-get-target-ranges-backspace.tentative.html.ini index 4ef4d878d028..7eda58a24b7e 100644 --- a/testing/web-platform/meta/input-events/input-events-get-target-ranges-backspace.tentative.html.ini +++ b/testing/web-platform/meta/input-events/input-events-get-target-ranges-backspace.tentative.html.ini @@ -40,9 +40,6 @@ [Backspace at "
abc   

[\]def

"] expected: FAIL - [Backspace at "

abc
[\]def

"] - expected: FAIL - [Backspace at "

abc

[\] def

"] expected: FAIL @@ -64,3 +61,15 @@ [Backspace at "
abc   

[\]def

"] expected: FAIL + [Backspace at "
abc
[\]def
"] + expected: FAIL + + [Backspace at "
abc
[\]def
"] + expected: FAIL + + [Backspace at "
abc
[\]def
"] + expected: FAIL + + [Backspace at "
abc

[\]def
"] + expected: FAIL + diff --git a/testing/web-platform/meta/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini b/testing/web-platform/meta/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini index 2d34fca9225e..140018f3753e 100644 --- a/testing/web-platform/meta/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini +++ b/testing/web-platform/meta/input-events/input-events-get-target-ranges-forwarddelete.tentative.html.ini @@ -42,9 +42,6 @@ [Delete at "

abc[\]

def

"] expected: FAIL - [Delete at "

abc[\]
def

"] - expected: FAIL - [Delete at "

abc [\]

def

"] expected: FAIL @@ -66,3 +63,15 @@ [Delete at "
{}
cell2
"] expected: FAIL + [Delete at "
abc[\]
def
"] + expected: FAIL + + [Delete at "
abc[\]
def
"] + expected: FAIL + + [Delete at "
abc[\]
def
"] + expected: FAIL + + [Delete at "
abc[\]

def
"] + expected: FAIL + diff --git a/testing/web-platform/tests/input-events/input-events-get-target-ranges-backspace.tentative.html b/testing/web-platform/tests/input-events/input-events-get-target-ranges-backspace.tentative.html index 2a166710f548..11b1830efb9f 100644 --- a/testing/web-platform/tests/input-events/input-events-get-target-ranges-backspace.tentative.html +++ b/testing/web-platform/tests/input-events/input-events-get-target-ranges-backspace.tentative.html @@ -15,6 +15,8 @@ const kMeta = "\uE03d"; const kControl = "\uE009"; const kAlt = "\uE00A"; +const kImgSrc = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEElEQVR42mNgaGD4D8YwBgAw9AX9Y9zBwwAAAABJRU5ErkJggg=="; + let selection = getSelection(); let editor = document.querySelector("div[contenteditable]"); let beforeinput = []; @@ -524,19 +526,247 @@ promise_test(async () => { promise_test(async () => { reset(); editor.innerHTML = "

abc
def

"; + let p = document.querySelector("p"); let def = editor.querySelector("br").nextSibling; selection.collapse(def, 0); await sendBackspaceKey(); assert_equals(editor.innerHTML, "

abcdef

"); checkGetTargetRangesOfBeforeinputOnDeleteSomething({ - startContainer: editor.firstChild, + startContainer: p, startOffset: 1, - endContainer: def, - endOffset: 0, + endContainer: p, + endOffset: 2, }); checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Backspace at "

abc
[]def

"'); +// Deleting visible `
` element following white-space should not include +// the preceding white-space in the range. +promise_test(async () => { + reset(); + editor.innerHTML = "

abc
def

"; + let p = editor.querySelector("p"); + let def = editor.querySelector("br").nextSibling; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc
[]def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + let def = p.lastChild; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abcdef

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc[]def

"'); + +// White-spaces around `` element are visible so that they shouldn't +// be included into the target ranges. +promise_test(async () => { + reset(); + editor.innerHTML = `

abc def

`; + let p = editor.querySelector("p"); + let def = p.lastChild; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc []def

"'); + +// White-spaces around `` element are visible so that they shouldn't +// be included into the target ranges. +promise_test(async () => { + reset(); + editor.innerHTML = `

abc def

`; + let p = editor.querySelector("p"); + let def = p.lastChild; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc[] def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(p, 2); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, `

abcdef

`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc{}def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + let def = p.lastChild; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, `

abcdef

`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 2, + endContainer: p, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc[]def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let hr = editor.querySelector("hr"); + let def = hr.nextSibling; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "
abc
[]def
"'); + +// White-spaces around block element are invisible white-spaces so that +// they should be included into the target ranges to avoid they bcome +// visible. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let abc = div.firstChild; + let hr = editor.querySelector("hr"); + let def = hr.nextSibling; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: abc, + startOffset: 3, + endContainer: div, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "
abc
[]def
"'); + +// White-spaces around block element are invisible white-spaces so that +// they should be included into the target ranges to avoid they bcome +// visible. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let hr = editor.querySelector("hr"); + let def = hr.nextSibling; + selection.collapse(def, 1); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: def, + endOffset: 1, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "
abc
[]def
"'); + +// Invisible `
` element immediately before `
` element should be +// deleted once, and both of them should be included in the target range. +promise_test(async () => { + reset(); + editor.innerHTML = `
abc

def
`; + let div = editor.querySelector("div"); + let def = div.lastChild; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "
abc

[]def
"'); + +// Deleting visible `
` element followed by white-space should include +// the following white-space in the range because it shouldn't become +// visible and should be deleted for avoiding it. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = "

abc
def

"; + let p = editor.querySelector("p"); + let def = editor.querySelector("br").nextSibling; + selection.collapse(def, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abcdef

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: def, + endOffset: 1, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc
[] def

"'); +promise_test(async () => { + reset(); + editor.innerHTML = "

abc
def

"; + let p = editor.querySelector("p"); + let def = editor.querySelector("br").nextSibling; + selection.collapse(def, 1); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abcdef

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: def, + endOffset: 1, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Backspace at "

abc
[]def

"'); + // Deleting visible `
` element should be contained by a range of // `getTargetRanges()`. However, when only the `
` element is selected, // the range shouldn't start from nor end by surrounding text nodes? diff --git a/testing/web-platform/tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html b/testing/web-platform/tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html index 18e10ac660d5..b31afacb62df 100644 --- a/testing/web-platform/tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html +++ b/testing/web-platform/tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html @@ -15,6 +15,8 @@ const kMeta = "\uE03d"; const kControl = "\uE009"; const kAlt = "\uE00A"; +const kImgSrc = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEElEQVR42mNgaGD4D8YwBgAw9AX9Y9zBwwAAAABJRU5ErkJggg=="; + let selection = getSelection(); let editor = document.querySelector("div[contenteditable]"); let beforeinput = []; @@ -521,19 +523,61 @@ promise_test(async () => { promise_test(async () => { reset(); editor.innerHTML = "

abc
def

"; - let abc = editor.firstChild.firstChild; + let p = editor.querySelector("p"); + let abc = p.firstChild; selection.collapse(abc, 3); await sendDeleteKey(); assert_equals(editor.innerHTML, "

abcdef

"); checkGetTargetRangesOfBeforeinputOnDeleteSomething({ - startContainer: abc, - startOffset: 3, - endContainer: editor.querySelector("p"), + startContainer: p, + startOffset: 1, + endContainer: p, endOffset: 2, }); checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "

abc[]
def

"'); +// Deleting visible `
` element following white-space should not include +// the preceding white-space in the range. +promise_test(async () => { + reset(); + editor.innerHTML = "

abc
def

"; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(abc, 4); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc []
def

"'); + +// Deleting visible `
` element followed by white-space should include +// the following white-space in the range because it shouldn't become +// visible and should be deleted for avoiding it. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = "

abc
def

"; + let p = editor.querySelector("p"); + let abc = p.firstChild; + let def = editor.querySelector("br").nextSibling; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abcdef

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: def, + endOffset: 1, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc[]
def

"'); + // Deleting visible `
` element should be contained by a range of // `getTargetRanges()`. However, when only the `
` element is selected, // the range shouldn't start from nor end by surrounding text nodes? @@ -553,6 +597,172 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "

abc{
}def

"'); +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abcdef

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc[]def

"'); + +// White-spaces around `` element are visible so that they shouldn't +// be included into the target ranges. +promise_test(async () => { + reset(); + editor.innerHTML = `

abc def

`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(abc, 4); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc []def

"'); + +// White-spaces around `` element are visible so that they shouldn't +// be included into the target ranges. +promise_test(async () => { + reset(); + editor.innerHTML = `

abc def

`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abc def

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc[] def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, `

abcdef

`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc[]def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

abcdef

`; + let p = editor.querySelector("p"); + selection.collapse(p, 2); + await sendDeleteKey(); + assert_equals(editor.innerHTML, `

abcdef

`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 2, + endContainer: p, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc{}def

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let abc = div.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
abc[]
def
"'); + +// White-spaces around block element are invisible white-spaces so that +// they should be included into the target ranges to avoid they become +// visible. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let abc = div.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
abc[]
def
"'); + +// White-spaces around block element are invisible white-spaces so that +// they should be included into the target ranges to avoid they become +// visible. +// https://github.com/w3c/input-events/issues/112 +promise_test(async () => { + reset(); + editor.innerHTML = `
abc
def
`; + let div = editor.querySelector("div"); + let abc = div.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 2, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
abc[]
def
"'); + +// Invisible `
` element immediately before `
` element should be +// delete once, and both of them should be included in the target range. +promise_test(async () => { + reset(); + editor.innerHTML = `
abc

def
`; + let div = editor.querySelector("div"); + let abc = div.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "
abcdef
"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
abc[]

def
"'); + // Joining parent block and child block should remove invisible preceding // white-spaces of the child block and invisible leading white-spaces in // the child block, and they should be contained in a range of