/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
if (MOZ_UNLIKELY(!aPoint.IsSet())) { return WSScanResult::Error();
}
// We may not be able to check editable state in uncomposed tree as expected. // For example, only some descendants in an editing host is temporarily // removed from the tree, they are not editable unless nested contenteditable // attribute is set to "true". if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) { return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
*aPoint.template ContainerAs<nsIContent>(),
WSType::InUncomposedDoc);
}
if (!TextFragmentDataAtStartRef().IsInitialized()) { return WSScanResult::Error();
}
// If the range has visible text and start of the visible text is before // aPoint, return previous character in the text. const VisibleWhiteSpacesData& visibleWhiteSpaces =
TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef(); if (visibleWhiteSpaces.IsInitialized() &&
visibleWhiteSpaces.StartRef().IsBefore(aPoint)) { // If the visible things are not editable, we shouldn't scan "editable" // things now. Whether keep scanning editable things or not should be // considered by the caller. if (mScanMode == Scan::EditableNodes && aPoint.GetChild() &&
!HTMLEditUtils::IsSimplyEditableNode((*aPoint.GetChild()))) { return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
*aPoint.GetChild(), WSType::SpecialContent);
} constauto atPreviousChar =
GetPreviousCharPoint<EditorRawDOMPointInText>(aPoint); // When it's a non-empty text node, return it. if (atPreviousChar.IsSet() && !atPreviousChar.IsContainerEmpty()) {
MOZ_ASSERT(!atPreviousChar.IsEndOfContainer()); return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
atPreviousChar.template NextPoint<EditorDOMPoint>(),
atPreviousChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
: atPreviousChar.IsCharPreformattedNewLine()
? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters);
}
}
if (NS_WARN_IF(TextFragmentDataAtStartRef().StartRawReason() ==
WSType::UnexpectedError)) { return WSScanResult::Error();
}
switch (TextFragmentDataAtStartRef().StartRawReason()) { case WSType::CollapsibleWhiteSpaces: case WSType::NonCollapsibleCharacters: case WSType::PreformattedLineBreak:
MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet()); // XXX: If we find the character at last of a text node and we started // scanning from following text node of it, some callers may work with the // point in the following text node instead of end of the found text node. return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
TextFragmentDataAtStartRef().StartRef(),
TextFragmentDataAtStartRef().StartRawReason()); default: break;
}
// Otherwise, return the start of the range. if (TextFragmentDataAtStartRef().GetStartReasonContent() !=
TextFragmentDataAtStartRef().StartRef().GetContainer()) { if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetStartReasonContent())) { return WSScanResult::Error();
} // In this case, TextFragmentDataAtStartRef().StartRef().Offset() is not // meaningful. return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
*TextFragmentDataAtStartRef().GetStartReasonContent(),
TextFragmentDataAtStartRef().StartRawReason());
} if (NS_WARN_IF(!TextFragmentDataAtStartRef().StartRef().IsSet())) { return WSScanResult::Error();
} return WSScanResult(*this, WSScanResult::ScanDirection::Backward,
TextFragmentDataAtStartRef().StartRef(),
TextFragmentDataAtStartRef().StartRawReason());
}
if (MOZ_UNLIKELY(!aPoint.IsSet())) { return WSScanResult::Error();
}
// We may not be able to check editable state in uncomposed tree as expected. // For example, only some descendants in an editing host is temporarily // removed from the tree, they are not editable unless nested contenteditable // attribute is set to "true". if (MOZ_UNLIKELY(!aPoint.IsInComposedDoc())) { return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
*aPoint.template ContainerAs<nsIContent>(),
WSType::InUncomposedDoc);
}
if (!TextFragmentDataAtStartRef().IsInitialized()) { return WSScanResult::Error();
}
// If the range has visible text and aPoint equals or is before the end of the // visible text, return inclusive next character in the text. const VisibleWhiteSpacesData& visibleWhiteSpaces =
TextFragmentDataAtStartRef().VisibleWhiteSpacesDataRef(); if (visibleWhiteSpaces.IsInitialized() &&
aPoint.EqualsOrIsBefore(visibleWhiteSpaces.EndRef())) { // If the visible things are not editable, we shouldn't scan "editable" // things now. Whether keep scanning editable things or not should be // considered by the caller. if (mScanMode == Scan::EditableNodes && aPoint.GetChild() &&
!HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetChild())) { return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
*aPoint.GetChild(), WSType::SpecialContent);
} constauto atNextChar = GetInclusiveNextCharPoint<EditorDOMPoint>(aPoint); // When it's a non-empty text node, return it. if (atNextChar.IsSet() && !atNextChar.IsContainerEmpty()) { return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
atNextChar,
!atNextChar.IsEndOfContainer() &&
atNextChar.IsCharCollapsibleASCIISpaceOrNBSP()
? WSType::CollapsibleWhiteSpaces
: !atNextChar.IsEndOfContainer() &&
atNextChar.IsCharPreformattedNewLine()
? WSType::PreformattedLineBreak
: WSType::NonCollapsibleCharacters);
}
}
if (NS_WARN_IF(TextFragmentDataAtStartRef().EndRawReason() ==
WSType::UnexpectedError)) { return WSScanResult::Error();
}
switch (TextFragmentDataAtStartRef().EndRawReason()) { case WSType::CollapsibleWhiteSpaces: case WSType::NonCollapsibleCharacters: case WSType::PreformattedLineBreak:
MOZ_ASSERT(TextFragmentDataAtStartRef().StartRef().IsSet()); // XXX: If we find the character at start of a text node and we // started scanning from preceding text node of it, some callers may want // to work with the point at end of the preceding text node instead of // start of the found text node. return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
TextFragmentDataAtStartRef().EndRef(),
TextFragmentDataAtStartRef().EndRawReason()); default: break;
}
// Otherwise, return the end of the range. if (TextFragmentDataAtStartRef().GetEndReasonContent() !=
TextFragmentDataAtStartRef().EndRef().GetContainer()) { if (NS_WARN_IF(!TextFragmentDataAtStartRef().GetEndReasonContent())) { return WSScanResult::Error();
} // In this case, TextFragmentDataAtStartRef().EndRef().Offset() is not // meaningful. return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
*TextFragmentDataAtStartRef().GetEndReasonContent(),
TextFragmentDataAtStartRef().EndRawReason());
} if (NS_WARN_IF(!TextFragmentDataAtStartRef().EndRef().IsSet())) { return WSScanResult::Error();
} return WSScanResult(*this, WSScanResult::ScanDirection::Forward,
TextFragmentDataAtStartRef().EndRef(),
TextFragmentDataAtStartRef().EndRawReason());
}
/***************************************************************************** * Implementation for new white-space normalizer
*****************************************************************************/
// Corresponding to handling invisible white-spaces part of // `TextFragmentData::GetReplaceRangeDataAtEndOfDeletionRange()` and // `TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange()`
// XXX `GetReplaceRangeDataAtEndOfDeletionRange()` and // `GetReplaceRangeDataAtStartOfDeletionRange()` use // `GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt()` and // `GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt()`. // However, they are really odd as mentioned with "XXX" comments // in them. For the new white-space normalizer, we need to treat // invisible white-spaces stricter because the legacy path handles // white-spaces multiple times (e.g., calling `HTMLEditor:: // DeleteNodeIfInvisibleAndEditableTextNode()` later) and that hides // the bug, but in the new path, we should stop doing same things // multiple times for both performance and footprint. Therefore, // even though the result might be different in some edge cases, // we should use clean path for now. Perhaps, we should fix the odd // cases before shipping `beforeinput` event in release channel.
// static
Result<EditorDOMRangeInTexts, nsresult>
WSRunScanner::GetRangeInTextNodesToBackspaceFrom(
Scan aScanMode, const EditorDOMPoint& aPoint, const Element* aAncestorLimiter /* = nullptr */) { // Corresponding to computing delete range part of // `WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace()`
MOZ_ASSERT(aPoint.IsSetAndValid());
const TextFragmentData textFragmentDataAtCaret(
aScanMode, aPoint, BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) { return Err(NS_ERROR_FAILURE);
} auto atPreviousChar =
textFragmentDataAtCaret.GetPreviousCharPoint<EditorDOMPointInText>(
aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!atPreviousChar.IsSet()) { return EditorDOMRangeInTexts(); // There is no content in the block.
}
// XXX When previous char point is in an empty text node, we do nothing, // but this must look odd from point of user view. We should delete // something before aPoint. if (atPreviousChar.IsEndOfContainer()) { return EditorDOMRangeInTexts();
}
// Extend delete range if previous char is a low surrogate following // a high surrogate.
EditorDOMPointInText atNextChar = atPreviousChar.NextPoint(); if (!atPreviousChar.IsStartOfContainer()) { if (atPreviousChar.IsCharLowSurrogateFollowingHighSurrogate()) {
atPreviousChar = atPreviousChar.PreviousPoint();
} // If caret is in middle of a surrogate pair, delete the surrogate pair // (blink-compat). elseif (atPreviousChar.IsCharHighSurrogateFollowedByLowSurrogate()) {
atNextChar = atNextChar.NextPoint();
}
}
// If previous char is an collapsible white-spaces, delete all adjacent // white-spaces which are collapsed together.
EditorDOMRangeInTexts rangeToDelete; if (atPreviousChar.IsCharCollapsibleASCIISpace() ||
atPreviousChar.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) { constauto startToDelete =
textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
atPreviousChar, nsIEditor::ePrevious,
ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!startToDelete.IsSet()) {
NS_WARNING( "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed"); return Err(NS_ERROR_FAILURE);
} constauto endToDelete =
textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
atPreviousChar, nsIEditor::ePrevious,
ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!endToDelete.IsSet()) {
NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed"); return Err(NS_ERROR_FAILURE);
}
rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
} // if previous char is not a collapsible white-space, remove it. else {
rangeToDelete = EditorDOMRangeInTexts(atPreviousChar, atNextChar);
}
// If there is no removable and visible content, we should do nothing. if (rangeToDelete.Collapsed()) { return EditorDOMRangeInTexts();
}
// And also delete invisible white-spaces if they become visible. const TextFragmentData textFragmentDataAtStart =
rangeToDelete.StartRef() != aPoint
? TextFragmentData(aScanMode, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter)
: textFragmentDataAtCaret; const TextFragmentData textFragmentDataAtEnd =
rangeToDelete.EndRef() != aPoint
? TextFragmentData(aScanMode, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter)
: textFragmentDataAtCaret; if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { return Err(NS_ERROR_FAILURE);
}
EditorDOMRangeInTexts extendedRangeToDelete =
WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
textFragmentDataAtStart, textFragmentDataAtEnd);
MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid()); return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
: rangeToDelete;
}
// static
Result<EditorDOMRangeInTexts, nsresult>
WSRunScanner::GetRangeInTextNodesToForwardDeleteFrom(
Scan aScanMode, const EditorDOMPoint& aPoint, const Element* aAncestorLimiter /* = nullptr */) { // Corresponding to computing delete range part of // `WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace()`
MOZ_ASSERT(aPoint.IsSetAndValid());
const TextFragmentData textFragmentDataAtCaret(
aScanMode, aPoint, BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtCaret.IsInitialized())) { return Err(NS_ERROR_FAILURE);
} auto atCaret =
textFragmentDataAtCaret.GetInclusiveNextCharPoint<EditorDOMPointInText>(
aPoint, ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!atCaret.IsSet()) { return EditorDOMRangeInTexts(); // There is no content in the block.
} // If caret is in middle of a surrogate pair, we should remove next // character (blink-compat). if (!atCaret.IsEndOfContainer() &&
atCaret.IsCharLowSurrogateFollowingHighSurrogate()) {
atCaret = atCaret.NextPoint();
}
// XXX When next char point is in an empty text node, we do nothing, // but this must look odd from point of user view. We should delete // something after aPoint. if (atCaret.IsEndOfContainer()) { return EditorDOMRangeInTexts();
}
// Extend delete range if previous char is a low surrogate following // a high surrogate.
EditorDOMPointInText atNextChar = atCaret.NextPoint(); if (atCaret.IsCharHighSurrogateFollowedByLowSurrogate()) {
atNextChar = atNextChar.NextPoint();
}
// If next char is a collapsible white-space, delete all adjacent white-spaces // which are collapsed together.
EditorDOMRangeInTexts rangeToDelete; if (atCaret.IsCharCollapsibleASCIISpace() ||
atCaret.IsCharPreformattedNewLineCollapsedWithWhiteSpaces()) { constauto startToDelete =
textFragmentDataAtCaret
.GetFirstASCIIWhiteSpacePointCollapsedTo<EditorDOMPointInText>(
atCaret, nsIEditor::eNext,
ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!startToDelete.IsSet()) {
NS_WARNING( "WSRunScanner::GetFirstASCIIWhiteSpacePointCollapsedTo() failed"); return Err(NS_ERROR_FAILURE);
} const EditorDOMPointInText endToDelete =
textFragmentDataAtCaret
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
atCaret, nsIEditor::eNext,
ShouldIgnoreNonEditableSiblingsOrDescendants(aScanMode)); if (!endToDelete.IsSet()) {
NS_WARNING("WSRunScanner::GetEndOfCollapsibleASCIIWhiteSpaces() failed"); return Err(NS_ERROR_FAILURE);
}
rangeToDelete = EditorDOMRangeInTexts(startToDelete, endToDelete);
} // if next char is not a collapsible white-space, remove it. else {
rangeToDelete = EditorDOMRangeInTexts(atCaret, atNextChar);
}
// If there is no removable and visible content, we should do nothing. if (rangeToDelete.Collapsed()) { return EditorDOMRangeInTexts();
}
// And also delete invisible white-spaces if they become visible. const TextFragmentData textFragmentDataAtStart =
rangeToDelete.StartRef() != aPoint
? TextFragmentData(aScanMode, rangeToDelete.StartRef(),
BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter)
: textFragmentDataAtCaret; const TextFragmentData textFragmentDataAtEnd =
rangeToDelete.EndRef() != aPoint
? TextFragmentData(aScanMode, rangeToDelete.EndRef(),
BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter)
: textFragmentDataAtCaret; if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized()) ||
NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { return Err(NS_ERROR_FAILURE);
}
EditorDOMRangeInTexts extendedRangeToDelete =
WSRunScanner::ComputeRangeInTextNodesContainingInvisibleWhiteSpaces(
textFragmentDataAtStart, textFragmentDataAtEnd);
MOZ_ASSERT(extendedRangeToDelete.IsPositionedAndValid()); return extendedRangeToDelete.IsPositioned() ? extendedRangeToDelete
: rangeToDelete;
}
// static
EditorDOMRange WSRunScanner::GetRangesForDeletingAtomicContent(
Scan aScanMode, const nsIContent& aAtomicContent, const Element* aAncestorLimiter /* = nullptr */) { if (aAtomicContent.IsHTMLElement(nsGkAtoms::br)) { // Preceding white-spaces should be preserved, but the following // white-spaces should be invisible around `<br>` element. const TextFragmentData textFragmentDataAfterBRElement(
aScanMode, EditorDOMPoint::After(aAtomicContent),
BlockInlineCheck::UseComputedDisplayStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAfterBRElement.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} const EditorDOMRangeInTexts followingInvisibleWhiteSpaces =
textFragmentDataAfterBRElement.GetNonCollapsedRangeInTexts(
textFragmentDataAfterBRElement
.InvisibleLeadingWhiteSpaceRangeRef()); return followingInvisibleWhiteSpaces.IsPositioned() &&
!followingInvisibleWhiteSpaces.Collapsed()
? EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
followingInvisibleWhiteSpaces.EndRef())
: EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
if (!HTMLEditUtils::IsBlockElement(
aAtomicContent, BlockInlineCheck::UseComputedDisplayStyle)) { // Both preceding and following white-spaces around it should be preserved // around inline elements like `<img>`. return EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
// Both preceding and following white-spaces can be invisible around a // block element. const TextFragmentData textFragmentDataBeforeAtomicContent(
aScanMode, EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
BlockInlineCheck::UseComputedDisplayStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataBeforeAtomicContent.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} const EditorDOMRangeInTexts precedingInvisibleWhiteSpaces =
textFragmentDataBeforeAtomicContent.GetNonCollapsedRangeInTexts(
textFragmentDataBeforeAtomicContent
.InvisibleTrailingWhiteSpaceRangeRef()); const TextFragmentData textFragmentDataAfterAtomicContent(
aScanMode, EditorDOMPoint::After(aAtomicContent),
BlockInlineCheck::UseComputedDisplayStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAfterAtomicContent.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} 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<nsIContent*>(&aAtomicContent)),
followingInvisibleWhiteSpaces.EndRef());
} return EditorDOMRange(
EditorDOMPoint(const_cast<nsIContent*>(&aAtomicContent)),
EditorDOMPoint::After(aAtomicContent));
}
EditorDOMRange range; // Include trailing invisible white-spaces in aLeftBlockElement. const TextFragmentData textFragmentDataAtEndOfLeftBlockElement(
aScanMode,
aPointContainingTheOtherBlock.GetContainer() == &aLeftBlockElement
? aPointContainingTheOtherBlock
: EditorDOMPoint::AtEndOf(const_cast<Element&>(aLeftBlockElement)),
BlockInlineCheck::UseComputedDisplayOutsideStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtEndOfLeftBlockElement.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} if (textFragmentDataAtEndOfLeftBlockElement.StartsFromInvisibleBRElement()) { // If the left block element ends with an invisible `<br>` element, // it'll be deleted (and it means there is no invisible trailing // white-spaces). Therefore, the range should start from the invisible // `<br>` element.
range.SetStart(EditorDOMPoint(
textFragmentDataAtEndOfLeftBlockElement.StartReasonBRElementPtr()));
} else { const EditorDOMRange& trailingWhiteSpaceRange =
textFragmentDataAtEndOfLeftBlockElement
.InvisibleTrailingWhiteSpaceRangeRef(); if (trailingWhiteSpaceRange.StartRef().IsSet()) {
range.SetStart(trailingWhiteSpaceRange.StartRef());
} else {
range.SetStart(textFragmentDataAtEndOfLeftBlockElement.ScanStartRef());
}
} // Include leading invisible white-spaces in aRightBlockElement. const TextFragmentData textFragmentDataAtStartOfRightBlockElement(
aScanMode,
aPointContainingTheOtherBlock.GetContainer() == &aRightBlockElement &&
!aPointContainingTheOtherBlock.IsEndOfContainer()
? aPointContainingTheOtherBlock.NextPoint()
: EditorDOMPoint(const_cast<Element*>(&aRightBlockElement), 0u),
BlockInlineCheck::UseComputedDisplayOutsideStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtStartOfRightBlockElement.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} const EditorDOMRange& leadingWhiteSpaceRange =
textFragmentDataAtStartOfRightBlockElement
.InvisibleLeadingWhiteSpaceRangeRef(); if (leadingWhiteSpaceRange.EndRef().IsSet()) {
range.SetEnd(leadingWhiteSpaceRange.EndRef());
} else {
range.SetEnd(textFragmentDataAtStartOfRightBlockElement.ScanStartRef());
} return range;
}
EditorDOMRange result; const TextFragmentData textFragmentDataAtStart(
aScanMode, aRange.StartRef(), BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtStart =
textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
textFragmentDataAtStart.InvisibleLeadingWhiteSpaceRangeRef()); if (invisibleLeadingWhiteSpacesAtStart.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtStart.Collapsed()) {
result.SetStart(invisibleLeadingWhiteSpacesAtStart.StartRef());
} else { const EditorDOMRangeInTexts invisibleTrailingWhiteSpacesAtStart =
textFragmentDataAtStart.GetNonCollapsedRangeInTexts(
textFragmentDataAtStart.InvisibleTrailingWhiteSpaceRangeRef()); if (invisibleTrailingWhiteSpacesAtStart.IsPositioned() &&
!invisibleTrailingWhiteSpacesAtStart.Collapsed()) {
MOZ_ASSERT(
invisibleTrailingWhiteSpacesAtStart.StartRef().EqualsOrIsBefore(
aRange.StartRef()));
result.SetStart(invisibleTrailingWhiteSpacesAtStart.StartRef());
} // If there is no invisible white-space and the line starts with a // text node, shrink the range to start of the text node. elseif (!aRange.StartRef().IsInTextNode() &&
(textFragmentDataAtStart.StartsFromBlockBoundary() ||
textFragmentDataAtStart.StartsFromInlineEditingHostBoundary()) &&
textFragmentDataAtStart.EndRef().IsInTextNode()) {
result.SetStart(textFragmentDataAtStart.EndRef());
}
} if (!result.StartRef().IsSet()) {
result.SetStart(aRange.StartRef());
}
const TextFragmentData textFragmentDataAtEnd(
aScanMode, aRange.EndRef(), BlockInlineCheck::UseComputedDisplayStyle,
aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { return EditorDOMRange(); // TODO: Make here return error with Err.
} const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
textFragmentDataAtEnd.InvisibleTrailingWhiteSpaceRangeRef()); if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
} else { const EditorDOMRangeInTexts invisibleLeadingWhiteSpacesAtEnd =
textFragmentDataAtEnd.GetNonCollapsedRangeInTexts(
textFragmentDataAtEnd.InvisibleLeadingWhiteSpaceRangeRef()); if (invisibleLeadingWhiteSpacesAtEnd.IsPositioned() &&
!invisibleLeadingWhiteSpacesAtEnd.Collapsed()) {
MOZ_ASSERT(aRange.EndRef().EqualsOrIsBefore(
invisibleLeadingWhiteSpacesAtEnd.EndRef()));
result.SetEnd(invisibleLeadingWhiteSpacesAtEnd.EndRef());
} // If there is no invisible white-space and the line ends with a text // node, shrink the range to end of the text node. elseif (!aRange.EndRef().IsInTextNode() &&
(textFragmentDataAtEnd.EndsByBlockBoundary() ||
textFragmentDataAtEnd.EndsByInlineEditingHostBoundary()) &&
textFragmentDataAtEnd.StartRef().IsInTextNode()) {
result.SetEnd(EditorDOMPoint::AtEndOf(
*textFragmentDataAtEnd.StartRef().ContainerAs<Text>()));
}
} if (!result.EndRef().IsSet()) {
result.SetEnd(aRange.EndRef());
}
MOZ_ASSERT(result.IsPositionedAndValid()); return result;
}
/****************************************************************************** * Utilities for other things.
******************************************************************************/
// static
Result<bool, nsresult>
WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
Scan aScanMode, nsRange& aRange, const Element* aAncestorLimiter /* = nullptr */) {
MOZ_ASSERT(aRange.IsPositioned());
MOZ_ASSERT(!aRange.IsInAnySelection(), "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()) { returnfalse;
}
// If the range crosses a block boundary, we should do nothing for now // because it hits a bug of inserting a padding `<br>` element after // joining the blocks. if (HTMLEditUtils::GetInclusiveAncestorElement(
*aRange.GetStartContainer()->AsContent(),
aScanMode == Scan::EditableNodes
? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
: HTMLEditUtils::ClosestBlockElementExceptHRElement,
BlockInlineCheck::UseComputedDisplayStyle) !=
HTMLEditUtils::GetInclusiveAncestorElement(
*aRange.GetEndContainer()->AsContent(),
aScanMode == Scan::EditableNodes
? HTMLEditUtils::ClosestEditableBlockElementExceptHRElement
: HTMLEditUtils::ClosestBlockElementExceptHRElement,
BlockInlineCheck::UseComputedDisplayStyle)) { returnfalse;
}
nsIContent* startContent = nullptr; if (aRange.GetStartContainer() && aRange.GetStartContainer()->IsText() &&
aRange.GetStartContainer()->AsText()->Length() == aRange.StartOffset()) { // If next content is a visible `<br>` element, special inline content // (e.g., `<img>`, non-editable text node, etc) or a block level void // element like `<hr>`, the range should start with it. const TextFragmentData textFragmentDataAtStart(
aScanMode, EditorRawDOMPoint(aRange.StartRef()),
BlockInlineCheck::UseComputedDisplayStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtStart.IsInitialized())) { return Err(NS_ERROR_FAILURE);
} if (textFragmentDataAtStart.EndsByVisibleBRElement()) {
startContent = textFragmentDataAtStart.EndReasonBRElementPtr();
} elseif (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 `<br>` element, special inline content // (e.g., `<img>`, non-editable text node, etc) or a block level void // element like `<hr>`, the range should end after it. const TextFragmentData textFragmentDataAtEnd(
aScanMode, EditorRawDOMPoint(aRange.EndRef()),
BlockInlineCheck::UseComputedDisplayStyle, aAncestorLimiter); if (NS_WARN_IF(!textFragmentDataAtEnd.IsInitialized())) { return Err(NS_ERROR_FAILURE);
} if (textFragmentDataAtEnd.StartsFromVisibleBRElement()) {
endContent = textFragmentDataAtEnd.StartReasonBRElementPtr();
} elseif (textFragmentDataAtEnd.StartsFromSpecialContent() ||
(textFragmentDataAtEnd.StartsFromOtherBlockElement() &&
!HTMLEditUtils::IsContainerNode(
*textFragmentDataAtEnd
.StartReasonOtherBlockElementPtr()))) {
endContent = textFragmentDataAtEnd.GetStartReasonContent();
}
}
if (!startContent && !endContent) { returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.