Bug 1940377 - part 2: Make HandleInsertLineBreak use the new normalizer if it's available r=m_kato
When inserting a `<br>` element or a preformatted line break at middle of a `Text`, we need to split it first. At this time, we should normalize the surrounding white-spaces before split. Then, we can use only one `ReplaceTextTransaction` instance for the normalization in the most cases. Differential Revision: https://phabricator.services.mozilla.com/D239464
This commit is contained in:
@@ -958,7 +958,7 @@ class EditorDOMPointBase final {
|
||||
return !!maybeTextControl;
|
||||
}
|
||||
|
||||
bool IsStartOfContainer() const {
|
||||
[[nodiscard]] bool IsStartOfContainer() const {
|
||||
// If we're referring the first point in the container:
|
||||
// If mParent is not a container like a text node, mOffset is 0.
|
||||
// If mChild is initialized and it's first child of mParent.
|
||||
@@ -984,7 +984,29 @@ class EditorDOMPointBase final {
|
||||
return !mOffset.value();
|
||||
}
|
||||
|
||||
bool IsEndOfContainer() const {
|
||||
[[nodiscard]] bool IsMiddleOfContainer() const {
|
||||
if (NS_WARN_IF(!mParent)) {
|
||||
return false;
|
||||
}
|
||||
if (mParent->IsText()) {
|
||||
return *mOffset && *mOffset < mParent->Length();
|
||||
}
|
||||
if (!mParent->HasChildren()) {
|
||||
return false;
|
||||
}
|
||||
if (mIsChildInitialized) {
|
||||
NS_WARNING_ASSERTION(
|
||||
mOffset.isNothing() ||
|
||||
(!mChild && *mOffset == mParent->GetChildCount()) ||
|
||||
(mChild && mOffset == mParent->ComputeIndexOf(mChild)),
|
||||
"mOffset does not match with current offset of mChild");
|
||||
return mChild && mChild != mParent->GetFirstChild();
|
||||
}
|
||||
MOZ_ASSERT(mOffset.isSome());
|
||||
return *mOffset && *mOffset < mParent->Length();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsEndOfContainer() const {
|
||||
// If we're referring after the last point of the container:
|
||||
// If mParent is not a container like text node, mOffset is same as the
|
||||
// length of the container.
|
||||
|
||||
@@ -559,9 +559,9 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
|
||||
return !TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces;
|
||||
case EditSubAction::eInsertText:
|
||||
case EditSubAction::eInsertTextComingFromIME:
|
||||
case EditSubAction::eInsertLineBreak:
|
||||
return !StaticPrefs::
|
||||
editor_white_space_normalization_blink_compatible();
|
||||
case EditSubAction::eInsertLineBreak:
|
||||
case EditSubAction::eInsertParagraphSeparator:
|
||||
case EditSubAction::ePasteHTMLContent:
|
||||
case EditSubAction::eInsertHTMLSource:
|
||||
@@ -3230,6 +3230,163 @@ HTMLEditor::NormalizeWhiteSpacesToInsertText(
|
||||
return result;
|
||||
}
|
||||
|
||||
HTMLEditor::ReplaceWhiteSpacesData
|
||||
HTMLEditor::GetFollowingNormalizedStringToSplitAt(
|
||||
const EditorDOMPointInText& aPointToSplit) const {
|
||||
MOZ_ASSERT(aPointToSplit.IsSet());
|
||||
|
||||
if (EditorUtils::IsWhiteSpacePreformatted(
|
||||
*aPointToSplit.ContainerAs<Text>()) ||
|
||||
aPointToSplit.IsEndOfContainer()) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
const bool isNewLineCollapsible =
|
||||
!EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>());
|
||||
const auto IsPreformattedLineBreak = [&](char16_t aChar) {
|
||||
return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine;
|
||||
};
|
||||
const auto IsCollapsibleChar = [&](char16_t aChar) {
|
||||
return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar);
|
||||
};
|
||||
const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
|
||||
return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
|
||||
};
|
||||
const char16_t followingChar = aPointToSplit.Char();
|
||||
if (!IsCollapsibleCharOrNBSP(followingChar)) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
const uint32_t followingWhiteSpaceLength = [&]() {
|
||||
const auto nonWhiteSpaceOffset =
|
||||
HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
|
||||
*aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
|
||||
{HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
|
||||
MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(
|
||||
aPointToSplit.ContainerAs<Text>()->TextDataLength()) >=
|
||||
aPointToSplit.Offset());
|
||||
return nonWhiteSpaceOffset.valueOr(
|
||||
aPointToSplit.ContainerAs<Text>()->TextDataLength()) -
|
||||
aPointToSplit.Offset();
|
||||
}();
|
||||
MOZ_ASSERT(followingWhiteSpaceLength);
|
||||
if (NS_WARN_IF(!followingWhiteSpaceLength) ||
|
||||
(followingWhiteSpaceLength == 1u &&
|
||||
followingChar == HTMLEditUtils::kNBSP)) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
|
||||
const uint32_t followingInvisibleSpaceCount =
|
||||
HTMLEditUtils::GetInvisibleWhiteSpaceCount(
|
||||
*aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
|
||||
followingWhiteSpaceLength);
|
||||
MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
|
||||
const uint32_t newFollowingWhiteSpaceLength =
|
||||
followingWhiteSpaceLength - followingInvisibleSpaceCount;
|
||||
nsAutoString followingWhiteSpaces;
|
||||
if (newFollowingWhiteSpaceLength) {
|
||||
followingWhiteSpaces.SetLength(newFollowingWhiteSpaceLength);
|
||||
for (const auto offset : IntegerRange(newFollowingWhiteSpaceLength)) {
|
||||
followingWhiteSpaces.SetCharAt(' ', offset);
|
||||
}
|
||||
}
|
||||
ReplaceWhiteSpacesData result(std::move(followingWhiteSpaces),
|
||||
aPointToSplit.Offset(),
|
||||
followingWhiteSpaceLength);
|
||||
if (!result.mNormalizedString.IsEmpty()) {
|
||||
const nsTextFragment& textFragment =
|
||||
aPointToSplit.ContainerAs<Text>()->TextFragment();
|
||||
HTMLEditor::NormalizeAllWhiteSpaceSequences(
|
||||
result.mNormalizedString,
|
||||
CharPointData::InSameTextNode(CharPointType::TextEnd),
|
||||
CharPointData::InSameTextNode(
|
||||
result.mReplaceEndOffset >= textFragment.GetLength()
|
||||
? CharPointType::TextEnd
|
||||
: (textFragment.CharAt(result.mReplaceEndOffset) ==
|
||||
HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar)),
|
||||
isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
HTMLEditor::ReplaceWhiteSpacesData
|
||||
HTMLEditor::GetPrecedingNormalizedStringToSplitAt(
|
||||
const EditorDOMPointInText& aPointToSplit) const {
|
||||
MOZ_ASSERT(aPointToSplit.IsSet());
|
||||
|
||||
if (EditorUtils::IsWhiteSpacePreformatted(
|
||||
*aPointToSplit.ContainerAs<Text>()) ||
|
||||
aPointToSplit.IsStartOfContainer()) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
const bool isNewLineCollapsible =
|
||||
!EditorUtils::IsNewLinePreformatted(*aPointToSplit.ContainerAs<Text>());
|
||||
const auto IsPreformattedLineBreak = [&](char16_t aChar) {
|
||||
return !isNewLineCollapsible && aChar == HTMLEditUtils::kNewLine;
|
||||
};
|
||||
const auto IsCollapsibleChar = [&](char16_t aChar) {
|
||||
return !IsPreformattedLineBreak(aChar) && nsCRT::IsAsciiSpace(aChar);
|
||||
};
|
||||
const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
|
||||
return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
|
||||
};
|
||||
const char16_t precedingChar = aPointToSplit.PreviousChar();
|
||||
if (!IsCollapsibleCharOrNBSP(precedingChar)) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
const uint32_t precedingWhiteSpaceLength = [&]() {
|
||||
const auto nonWhiteSpaceOffset =
|
||||
HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
|
||||
*aPointToSplit.ContainerAs<Text>(), aPointToSplit.Offset(),
|
||||
{HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
|
||||
const uint32_t firstWhiteSpaceOffset =
|
||||
nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
|
||||
return aPointToSplit.Offset() - firstWhiteSpaceOffset;
|
||||
}();
|
||||
MOZ_ASSERT(precedingWhiteSpaceLength);
|
||||
if (NS_WARN_IF(!precedingWhiteSpaceLength) ||
|
||||
(precedingWhiteSpaceLength == 1u &&
|
||||
precedingChar == HTMLEditUtils::kNBSP)) {
|
||||
return ReplaceWhiteSpacesData();
|
||||
}
|
||||
|
||||
const uint32_t precedingInvisibleWhiteSpaceCount =
|
||||
HTMLEditUtils::GetInvisibleWhiteSpaceCount(
|
||||
*aPointToSplit.ContainerAs<Text>(),
|
||||
aPointToSplit.Offset() - precedingWhiteSpaceLength,
|
||||
precedingWhiteSpaceLength);
|
||||
MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
|
||||
const uint32_t newPrecedingWhiteSpaceLength =
|
||||
precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
|
||||
nsAutoString precedingWhiteSpaces;
|
||||
if (newPrecedingWhiteSpaceLength) {
|
||||
precedingWhiteSpaces.SetLength(newPrecedingWhiteSpaceLength);
|
||||
for (const auto offset : IntegerRange(newPrecedingWhiteSpaceLength)) {
|
||||
precedingWhiteSpaces.SetCharAt(' ', offset);
|
||||
}
|
||||
}
|
||||
ReplaceWhiteSpacesData result(
|
||||
std::move(precedingWhiteSpaces),
|
||||
aPointToSplit.Offset() - precedingWhiteSpaceLength,
|
||||
precedingWhiteSpaceLength);
|
||||
if (!result.mNormalizedString.IsEmpty()) {
|
||||
const nsTextFragment& textFragment =
|
||||
aPointToSplit.ContainerAs<Text>()->TextFragment();
|
||||
HTMLEditor::NormalizeAllWhiteSpaceSequences(
|
||||
result.mNormalizedString,
|
||||
CharPointData::InSameTextNode(
|
||||
!result.mReplaceStartOffset
|
||||
? CharPointType::TextEnd
|
||||
: (textFragment.CharAt(result.mReplaceStartOffset - 1u) ==
|
||||
HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar)),
|
||||
CharPointData::InSameTextNode(CharPointType::TextEnd),
|
||||
isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
|
||||
EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
|
||||
nsString& aNormalizedWhiteSpacesInStartNode,
|
||||
|
||||
@@ -4191,6 +4191,13 @@ Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction(
|
||||
return caretPointOrError;
|
||||
}
|
||||
|
||||
Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
|
||||
dom::Text& aTextNode, const ReplaceWhiteSpacesData& aData) {
|
||||
return ReplaceTextWithTransaction(aTextNode, aData.mReplaceStartOffset,
|
||||
aData.ReplaceLength(),
|
||||
aData.mNormalizedString);
|
||||
}
|
||||
|
||||
Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
|
||||
Text& aTextNode, uint32_t aOffset, uint32_t aLength,
|
||||
const nsAString& aStringToInsert) {
|
||||
@@ -4382,8 +4389,17 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertLineBreak(
|
||||
!CanInsertLineBreak(*aPointToInsert.ContainerAs<nsIContent>()))) {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
|
||||
return aPointToInsert;
|
||||
}
|
||||
Result<EditorDOMPoint, nsresult> pointToInsertOrError =
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
||||
*this, aPointToInsert);
|
||||
if (NS_WARN_IF(pointToInsertOrError.isErr())) {
|
||||
return pointToInsertOrError.propagateErr();
|
||||
}
|
||||
return pointToInsertOrError.unwrap();
|
||||
}
|
||||
|
||||
// If the text node is not in an element node, we cannot insert a line break
|
||||
// around the text node.
|
||||
@@ -4394,21 +4410,34 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertLineBreak(
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
if (aPointToInsert.IsStartOfContainer()) {
|
||||
Result<EditorDOMPoint, nsresult> pointToInsertOrError =
|
||||
StaticPrefs::editor_white_space_normalization_blink_compatible()
|
||||
? WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
||||
*this, aPointToInsert)
|
||||
: aPointToInsert;
|
||||
if (NS_WARN_IF(pointToInsertOrError.isErr())) {
|
||||
return pointToInsertOrError.propagateErr();
|
||||
}
|
||||
const EditorDOMPoint pointToInsert = pointToInsertOrError.unwrap();
|
||||
if (!pointToInsert.IsInTextNode()) {
|
||||
return pointToInsert.ParentPoint();
|
||||
}
|
||||
|
||||
if (pointToInsert.IsStartOfContainer()) {
|
||||
// Insert before the text node.
|
||||
return aPointToInsert.ParentPoint();
|
||||
return pointToInsert.ParentPoint();
|
||||
}
|
||||
|
||||
if (aPointToInsert.IsEndOfContainer()) {
|
||||
if (pointToInsert.IsEndOfContainer()) {
|
||||
// Insert after the text node.
|
||||
return EditorDOMPoint::After(*aPointToInsert.ContainerAs<Text>());
|
||||
return EditorDOMPoint::After(*pointToInsert.ContainerAs<Text>());
|
||||
}
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
|
||||
MOZ_DIAGNOSTIC_ASSERT(pointToInsert.IsSetAndValid());
|
||||
|
||||
// Unfortunately, we need to split the text node at the offset.
|
||||
Result<SplitNodeResult, nsresult> splitTextNodeResult =
|
||||
SplitNodeWithTransaction(aPointToInsert);
|
||||
SplitNodeWithTransaction(pointToInsert);
|
||||
if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
|
||||
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
|
||||
return splitTextNodeResult.propagateErr();
|
||||
|
||||
@@ -856,6 +856,15 @@ class HTMLEditor final : public EditorBase,
|
||||
InsertOrReplaceTextWithTransaction(const EditorDOMPoint& aPointToInsert,
|
||||
const NormalizedStringToInsertText& aData);
|
||||
|
||||
struct ReplaceWhiteSpacesData;
|
||||
|
||||
/**
|
||||
* Replace or insert white-spaces of aData to aTextNode.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult>
|
||||
ReplaceTextWithTransaction(dom::Text& aTextNode,
|
||||
const ReplaceWhiteSpacesData& aData);
|
||||
|
||||
/**
|
||||
* Insert aStringToInsert to aPointToInsert. If the point is not editable,
|
||||
* this returns error.
|
||||
@@ -2231,6 +2240,20 @@ class HTMLEditor final : public EditorBase,
|
||||
const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
|
||||
NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const;
|
||||
|
||||
/**
|
||||
* Return normalized white-spaces after aPointToSplit if there are some
|
||||
* collapsible white-spaces after the point.
|
||||
*/
|
||||
ReplaceWhiteSpacesData GetFollowingNormalizedStringToSplitAt(
|
||||
const EditorDOMPointInText& aPointToSplit) const;
|
||||
|
||||
/**
|
||||
* Return normalized white-spaces before aPointToSplit if there are some
|
||||
* collapsible white-spaces before the point.
|
||||
*/
|
||||
ReplaceWhiteSpacesData GetPrecedingNormalizedStringToSplitAt(
|
||||
const EditorDOMPointInText& aPointToSplit) const;
|
||||
|
||||
/**
|
||||
* ExtendRangeToDeleteWithNormalizingWhiteSpaces() is a helper method of
|
||||
* DeleteTextAndNormalizeSurroundingWhiteSpaces(). This expands
|
||||
|
||||
@@ -686,6 +686,113 @@ struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final {
|
||||
const uint32_t mNewLengthAfter = 0u;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* ReplaceWhiteSpacesData stores normalized string to replace white-spaces in
|
||||
* a `Text`. If ReplaceLength() returns 0, this user needs to do nothing.
|
||||
******************************************************************************/
|
||||
|
||||
struct MOZ_STACK_CLASS HTMLEditor::ReplaceWhiteSpacesData final {
|
||||
ReplaceWhiteSpacesData() = default;
|
||||
ReplaceWhiteSpacesData(const nsAString& aWhiteSpaces, uint32_t aStartOffset,
|
||||
uint32_t aReplaceLength)
|
||||
: mNormalizedString(aWhiteSpaces),
|
||||
mReplaceStartOffset(aStartOffset),
|
||||
mReplaceEndOffset(aStartOffset + aReplaceLength) {
|
||||
MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
|
||||
}
|
||||
ReplaceWhiteSpacesData(nsAutoString&& aWhiteSpaces, uint32_t aStartOffset,
|
||||
uint32_t aReplaceLength)
|
||||
: mNormalizedString(std::forward<nsAutoString>(aWhiteSpaces)),
|
||||
mReplaceStartOffset(aStartOffset),
|
||||
mReplaceEndOffset(aStartOffset + aReplaceLength) {
|
||||
MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
|
||||
}
|
||||
|
||||
ReplaceWhiteSpacesData GetMinimizedData(const Text& aText) const {
|
||||
if (!ReplaceLength()) {
|
||||
return *this;
|
||||
}
|
||||
const nsTextFragment& textFragment = aText.TextFragment();
|
||||
const auto minimizedReplaceStart = [&]() -> uint32_t {
|
||||
if (mNormalizedString.IsEmpty()) {
|
||||
return mReplaceStartOffset;
|
||||
}
|
||||
const uint32_t firstDiffCharOffset =
|
||||
textFragment.FindFirstDifferentCharOffset(mNormalizedString,
|
||||
mReplaceStartOffset);
|
||||
if (firstDiffCharOffset == nsTextFragment::kNotFound) {
|
||||
// We don't need to insert new white-spaces,
|
||||
return mReplaceStartOffset + mNormalizedString.Length();
|
||||
}
|
||||
return firstDiffCharOffset;
|
||||
}();
|
||||
const auto minimizedReplaceEnd = [&]() -> uint32_t {
|
||||
if (mNormalizedString.IsEmpty()) {
|
||||
return mReplaceEndOffset;
|
||||
}
|
||||
if (minimizedReplaceStart ==
|
||||
mReplaceStartOffset + mNormalizedString.Length()) {
|
||||
// Note that here may be invisible white-spaces before
|
||||
// mReplaceEndOffset. Then, this value may be larger than
|
||||
// minimizedReplaceStart.
|
||||
MOZ_ASSERT(mReplaceEndOffset >= minimizedReplaceStart);
|
||||
return mReplaceEndOffset;
|
||||
}
|
||||
if (ReplaceLength() != mNormalizedString.Length()) {
|
||||
// If we're deleting some invisible white-spaces, don't shrink the end
|
||||
// of the replacing range because it may shrink mNormalizedString too
|
||||
// much.
|
||||
return mReplaceEndOffset;
|
||||
}
|
||||
const auto lastDiffCharOffset =
|
||||
textFragment.RFindFirstDifferentCharOffset(mNormalizedString,
|
||||
mReplaceEndOffset);
|
||||
MOZ_ASSERT(lastDiffCharOffset != nsTextFragment::kNotFound);
|
||||
return lastDiffCharOffset == nsTextFragment::kNotFound
|
||||
? mReplaceEndOffset
|
||||
: lastDiffCharOffset + 1u;
|
||||
}();
|
||||
if (minimizedReplaceStart == mReplaceStartOffset &&
|
||||
minimizedReplaceEnd == mReplaceEndOffset) {
|
||||
return *this;
|
||||
}
|
||||
const uint32_t precedingUnnecessaryLength =
|
||||
minimizedReplaceStart - mReplaceStartOffset;
|
||||
const uint32_t followingUnnecessaryLength =
|
||||
mReplaceEndOffset - minimizedReplaceEnd;
|
||||
return ReplaceWhiteSpacesData(
|
||||
Substring(mNormalizedString, precedingUnnecessaryLength,
|
||||
mNormalizedString.Length() - (precedingUnnecessaryLength +
|
||||
followingUnnecessaryLength)),
|
||||
minimizedReplaceStart, minimizedReplaceEnd - minimizedReplaceStart);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t ReplaceLength() const {
|
||||
return mReplaceEndOffset - mReplaceStartOffset;
|
||||
}
|
||||
[[nodiscard]] uint32_t DeletingInvisibleWhiteSpaces() const {
|
||||
return ReplaceLength() - mNormalizedString.Length();
|
||||
}
|
||||
|
||||
[[nodiscard]] ReplaceWhiteSpacesData operator+(
|
||||
const ReplaceWhiteSpacesData& aOther) const {
|
||||
if (!ReplaceLength()) {
|
||||
return aOther;
|
||||
}
|
||||
if (!aOther.ReplaceLength()) {
|
||||
return *this;
|
||||
}
|
||||
MOZ_ASSERT(mReplaceEndOffset == aOther.mReplaceStartOffset);
|
||||
return ReplaceWhiteSpacesData(
|
||||
nsAutoString(mNormalizedString + aOther.mNormalizedString),
|
||||
mReplaceStartOffset, aOther.mReplaceEndOffset);
|
||||
}
|
||||
|
||||
nsAutoString mNormalizedString;
|
||||
const uint32_t mReplaceStartOffset = 0u;
|
||||
const uint32_t mReplaceEndOffset = 0u;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef HTMLEditorNestedClasses_h
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
using LeafNodeType = HTMLEditUtils::LeafNodeType;
|
||||
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
|
||||
|
||||
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
|
||||
@@ -817,6 +818,271 @@ Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper::
|
||||
return std::move(unwrappedMoveContentResult);
|
||||
}
|
||||
|
||||
// static
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit) {
|
||||
MOZ_ASSERT(aPointToSplit.IsSetAndValid());
|
||||
MOZ_ASSERT(StaticPrefs::editor_white_space_normalization_blink_compatible());
|
||||
|
||||
if (EditorUtils::IsWhiteSpacePreformatted(
|
||||
*aPointToSplit.ContainerAs<Text>())) {
|
||||
return aPointToSplit.To<EditorDOMPoint>();
|
||||
}
|
||||
|
||||
const OwningNonNull<Text> textNode = *aPointToSplit.ContainerAs<Text>();
|
||||
if (!textNode->TextDataLength()) {
|
||||
// Delete if it's an empty `Text` node and removable.
|
||||
if (!HTMLEditUtils::IsRemovableNode(*textNode)) {
|
||||
// It's logically odd to call this for non-editable `Text`, but it may
|
||||
// happen if surrounding white-space sequence contains empty non-editable
|
||||
// `Text`. In that case, the caller needs to normalize its preceding
|
||||
// `Text` nodes too.
|
||||
return EditorDOMPoint();
|
||||
}
|
||||
const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode();
|
||||
const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling();
|
||||
nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
|
||||
return Err(rv);
|
||||
}
|
||||
if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
return EditorDOMPoint(nextSibling);
|
||||
}
|
||||
|
||||
const HTMLEditor::ReplaceWhiteSpacesData replacePrecedingWhiteSpacesData =
|
||||
aPointToSplit.IsStartOfContainer() ||
|
||||
// Chrome does not normalize the left `Text` at least when it ends
|
||||
// with an NBSP.
|
||||
aPointToSplit.IsPreviousCharNBSP()
|
||||
? HTMLEditor::ReplaceWhiteSpacesData()
|
||||
: aHTMLEditor.GetPrecedingNormalizedStringToSplitAt(aPointToSplit);
|
||||
const HTMLEditor::ReplaceWhiteSpacesData replaceFollowingWhiteSpaceData =
|
||||
aHTMLEditor.GetFollowingNormalizedStringToSplitAt(aPointToSplit);
|
||||
const HTMLEditor::ReplaceWhiteSpacesData replaceWhiteSpacesData =
|
||||
(replacePrecedingWhiteSpacesData + replaceFollowingWhiteSpaceData)
|
||||
.GetMinimizedData(*textNode);
|
||||
if (!replaceWhiteSpacesData.ReplaceLength()) {
|
||||
return aPointToSplit.To<EditorDOMPoint>();
|
||||
}
|
||||
if (replaceWhiteSpacesData.mNormalizedString.IsEmpty() &&
|
||||
replaceWhiteSpacesData.ReplaceLength() == textNode->TextDataLength()) {
|
||||
// If there is only invisible white-spaces, mNormalizedString is empty
|
||||
// string but replace length is same the the `Text` length. In this case, we
|
||||
// should delete the `Text` to avoid empty `Text` to stay in the DOM tree.
|
||||
const nsCOMPtr<nsINode> parentNode = textNode->GetParentNode();
|
||||
const nsCOMPtr<nsIContent> nextSibling = textNode->GetNextSibling();
|
||||
nsresult rv = aHTMLEditor.DeleteNodeWithTransaction(textNode);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
|
||||
return Err(rv);
|
||||
}
|
||||
if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
return EditorDOMPoint(nextSibling);
|
||||
}
|
||||
Result<InsertTextResult, nsresult> replaceWhiteSpacesResultOrError =
|
||||
aHTMLEditor.ReplaceTextWithTransaction(textNode, replaceWhiteSpacesData);
|
||||
if (MOZ_UNLIKELY(replaceWhiteSpacesResultOrError.isErr())) {
|
||||
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
|
||||
return replaceWhiteSpacesResultOrError.propagateErr();
|
||||
}
|
||||
replaceWhiteSpacesResultOrError.unwrap().IgnoreCaretPointSuggestion();
|
||||
const uint32_t offsetToSplit =
|
||||
aPointToSplit.Offset() - replacePrecedingWhiteSpacesData.ReplaceLength() +
|
||||
replacePrecedingWhiteSpacesData.mNormalizedString.Length();
|
||||
if (NS_WARN_IF(textNode->TextDataLength() < offsetToSplit)) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
return EditorDOMPoint(textNode, offsetToSplit);
|
||||
}
|
||||
|
||||
// static
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit) {
|
||||
MOZ_ASSERT(aPointToSplit.IsSet());
|
||||
MOZ_ASSERT(StaticPrefs::editor_white_space_normalization_blink_compatible());
|
||||
|
||||
// If the insertion point is not in composed doc, we're probably initializing
|
||||
// an element which will be inserted. In such case, the caller should own the
|
||||
// responsibility for normalizing the white-spaces.
|
||||
if (!aPointToSplit.IsInComposedDoc()) {
|
||||
return aPointToSplit;
|
||||
}
|
||||
|
||||
EditorDOMPoint pointToSplit(aPointToSplit);
|
||||
{
|
||||
AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(),
|
||||
&pointToSplit);
|
||||
Result<EditorDOMPoint, nsresult> pointToSplitOrError =
|
||||
WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces(aHTMLEditor,
|
||||
pointToSplit);
|
||||
if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"WhiteSpaceVisibilityKeeper::EnsureNoInvisibleWhiteSpaces() failed");
|
||||
return pointToSplitOrError.propagateErr();
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
|
||||
if (pointToSplit.IsInTextNode()) {
|
||||
Result<EditorDOMPoint, nsresult> pointToSplitOrError =
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
|
||||
aHTMLEditor, pointToSplit.AsInText());
|
||||
if (MOZ_UNLIKELY(pointToSplitOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
|
||||
"failed");
|
||||
return pointToSplitOrError.propagateErr();
|
||||
}
|
||||
pointToSplit = pointToSplitOrError.unwrap().To<EditorDOMPoint>();
|
||||
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
// If we normalize white-spaces in middle of the `Text`, we don't need to
|
||||
// touch surrounding `Text` nodes.
|
||||
if (pointToSplit.IsMiddleOfContainer()) {
|
||||
return pointToSplit;
|
||||
}
|
||||
}
|
||||
|
||||
// Preceding and/or following white-space sequence may be across multiple
|
||||
// `Text` nodes. Then, they may become unexpectedly visible without
|
||||
// normalizing the white-spaces. Therefore, we need to list up all possible
|
||||
// `Text` nodes first. Then, normalize them unless the `Text` is not
|
||||
const RefPtr<Element> closestBlockElement =
|
||||
HTMLEditUtils::GetInclusiveAncestorElement(
|
||||
*pointToSplit.ContainerAs<nsIContent>(),
|
||||
HTMLEditUtils::ClosestBlockElement,
|
||||
BlockInlineCheck::UseComputedDisplayStyle);
|
||||
AutoTArray<OwningNonNull<Text>, 3> precedingTextNodes, followingTextNodes;
|
||||
if (!pointToSplit.IsInTextNode() || pointToSplit.IsStartOfContainer()) {
|
||||
for (nsCOMPtr<nsIContent> previousContent =
|
||||
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
|
||||
pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
|
||||
BlockInlineCheck::UseComputedDisplayStyle,
|
||||
closestBlockElement);
|
||||
previousContent;
|
||||
previousContent =
|
||||
HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
|
||||
*previousContent, {LeafNodeType::LeafNodeOrChildBlock},
|
||||
BlockInlineCheck::UseComputedDisplayStyle,
|
||||
closestBlockElement)) {
|
||||
if (auto* const textNode = Text::FromNode(previousContent)) {
|
||||
if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) &&
|
||||
textNode->TextDataLength()) {
|
||||
break;
|
||||
}
|
||||
// Chrome does not normalize preceding `Text` at least when it ends with
|
||||
// an NBSP.
|
||||
if (textNode->TextDataLength() &&
|
||||
textNode->TextFragment().CharAt(textNode->TextLength() - 1u) ==
|
||||
HTMLEditUtils::kNBSP) {
|
||||
break;
|
||||
}
|
||||
precedingTextNodes.AppendElement(*textNode);
|
||||
if (textNode->TextIsOnlyWhitespace()) {
|
||||
// white-space only `Text` will be removed, so, we need to check
|
||||
// preceding one too.
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (auto* const element = Element::FromNode(previousContent)) {
|
||||
if (HTMLEditUtils::IsBlockElement(
|
||||
*element, BlockInlineCheck::UseComputedDisplayStyle) ||
|
||||
HTMLEditUtils::IsNonEditableReplacedContent(*element)) {
|
||||
break;
|
||||
}
|
||||
// Ignore invisible inline elements
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pointToSplit.IsInTextNode() || pointToSplit.IsEndOfContainer()) {
|
||||
for (nsCOMPtr<nsIContent> nextContent =
|
||||
HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
|
||||
pointToSplit, {LeafNodeType::LeafNodeOrChildBlock},
|
||||
BlockInlineCheck::UseComputedDisplayStyle,
|
||||
closestBlockElement);
|
||||
nextContent;
|
||||
nextContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
|
||||
*nextContent, {LeafNodeType::LeafNodeOrChildBlock},
|
||||
BlockInlineCheck::UseComputedDisplayStyle, closestBlockElement)) {
|
||||
if (auto* const textNode = Text::FromNode(nextContent)) {
|
||||
if (!HTMLEditUtils::IsSimplyEditableNode(*textNode) &&
|
||||
textNode->TextDataLength()) {
|
||||
break;
|
||||
}
|
||||
followingTextNodes.AppendElement(*textNode);
|
||||
if (textNode->TextIsOnlyWhitespace() &&
|
||||
EditorUtils::IsWhiteSpacePreformatted(*textNode)) {
|
||||
// white-space only `Text` will be removed, so, we need to check next
|
||||
// one too.
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (auto* const element = Element::FromNode(nextContent)) {
|
||||
if (HTMLEditUtils::IsBlockElement(
|
||||
*element, BlockInlineCheck::UseComputedDisplayStyle) ||
|
||||
HTMLEditUtils::IsNonEditableReplacedContent(*element)) {
|
||||
break;
|
||||
}
|
||||
// Ignore invisible inline elements
|
||||
}
|
||||
}
|
||||
}
|
||||
AutoTrackDOMPoint trackPointToSplit(aHTMLEditor.RangeUpdaterRef(),
|
||||
&pointToSplit);
|
||||
for (const auto& textNode : precedingTextNodes) {
|
||||
Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError =
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
|
||||
aHTMLEditor, EditorDOMPointInText::AtEndOf(textNode));
|
||||
if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
|
||||
"failed");
|
||||
return normalizeWhiteSpacesResultOrError.propagateErr();
|
||||
}
|
||||
if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() &&
|
||||
!normalizeWhiteSpacesResultOrError.inspect().IsStartOfContainer()) {
|
||||
// The white-space sequence started from middle of this node, so, we need
|
||||
// to do this for the preceding nodes.
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& textNode : followingTextNodes) {
|
||||
Result<EditorDOMPoint, nsresult> normalizeWhiteSpacesResultOrError =
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt(
|
||||
aHTMLEditor, EditorDOMPointInText(textNode, 0u));
|
||||
if (MOZ_UNLIKELY(normalizeWhiteSpacesResultOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitTextNodeAt() "
|
||||
"failed");
|
||||
return normalizeWhiteSpacesResultOrError.propagateErr();
|
||||
}
|
||||
if (normalizeWhiteSpacesResultOrError.inspect().IsInTextNode() &&
|
||||
!normalizeWhiteSpacesResultOrError.inspect().IsEndOfContainer()) {
|
||||
// The white-space sequence ended in middle of this node, so, we need
|
||||
// to do this for the following nodes.
|
||||
break;
|
||||
}
|
||||
}
|
||||
trackPointToSplit.FlushAndStopTracking();
|
||||
if (NS_WARN_IF(!pointToSplit.IsInContentNode())) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
return std::move(pointToSplit);
|
||||
}
|
||||
|
||||
// static
|
||||
Result<CreateLineBreakResult, nsresult>
|
||||
WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
@@ -826,6 +1092,9 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
return Err(NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
EditorDOMPoint pointToInsert(aPointToInsert);
|
||||
// TODO: Delete this block once we ship the new normalizer.
|
||||
if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
|
||||
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
||||
// meanwhile, the pre case is handled in HandleInsertText() in
|
||||
// HTMLEditSubActionHandler.cpp
|
||||
@@ -841,7 +1110,8 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
.GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
|
||||
EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine =
|
||||
textFragmentDataAtInsertionPoint
|
||||
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
|
||||
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
|
||||
aPointToInsert);
|
||||
const Maybe<const VisibleWhiteSpacesData> visibleWhiteSpaces =
|
||||
!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() ||
|
||||
!invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()
|
||||
@@ -852,10 +1122,10 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
? visibleWhiteSpaces.ref().ComparePoint(aPointToInsert)
|
||||
: PointPosition::NotInSameDOMTree;
|
||||
|
||||
EditorDOMPoint pointToInsert(aPointToInsert);
|
||||
EditorDOMPoint atNBSPReplaceableWithSP;
|
||||
if (!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() &&
|
||||
(pointPositionWithVisibleWhiteSpaces == PointPosition::MiddleOfFragment ||
|
||||
(pointPositionWithVisibleWhiteSpaces ==
|
||||
PointPosition::MiddleOfFragment ||
|
||||
pointPositionWithVisibleWhiteSpaces == PointPosition::EndOfFragment)) {
|
||||
atNBSPReplaceableWithSP =
|
||||
textFragmentDataAtInsertionPoint
|
||||
@@ -936,8 +1206,8 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
textFragmentDataAtInsertionPoint
|
||||
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
|
||||
atNextCharOfInsertionPoint, nsIEditor::eNone,
|
||||
// XXX Shouldn't be "No"? Skipping non-editable nodes may
|
||||
// have visible content.
|
||||
// XXX Shouldn't be "No"? Skipping non-editable nodes
|
||||
// may have visible content.
|
||||
IgnoreNonEditableNodes::Yes);
|
||||
nsresult rv =
|
||||
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
|
||||
@@ -1009,7 +1279,8 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
|
||||
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
|
||||
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
|
||||
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
|
||||
NS_WARNING(
|
||||
"HTMLEditor::ReplaceTextWithTransaction() failed failed");
|
||||
return replaceTextResult.propagateErr();
|
||||
}
|
||||
// Ignore caret suggestion because there was
|
||||
@@ -1023,6 +1294,21 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Result<EditorDOMPoint, nsresult>
|
||||
normalizeSurroundingWhiteSpacesResultOrError =
|
||||
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
||||
aHTMLEditor, aPointToInsert);
|
||||
if (MOZ_UNLIKELY(normalizeSurroundingWhiteSpacesResultOrError.isErr())) {
|
||||
NS_WARNING(
|
||||
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed");
|
||||
return normalizeSurroundingWhiteSpacesResultOrError.propagateErr();
|
||||
}
|
||||
pointToInsert = normalizeSurroundingWhiteSpacesResultOrError.unwrap();
|
||||
if (NS_WARN_IF(!pointToInsert.IsSet())) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
}
|
||||
|
||||
Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
|
||||
aHTMLEditor.InsertLineBreak(WithTransaction::Yes, aLineBreakType,
|
||||
|
||||
@@ -130,6 +130,15 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
const EditorDOMPoint& aPointToSplit,
|
||||
const Element& aSplittingBlockElement);
|
||||
|
||||
/**
|
||||
* Normalize surrounding white-spaces of aPointToSplit. This may normalize
|
||||
* 2 `Text` nodes if the point is surrounded by them.
|
||||
* Note that this is designed only for the new normalizer.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
|
||||
NormalizeWhiteSpacesToSplitAt(HTMLEditor& aHTMLEditor,
|
||||
const EditorDOMPoint& aPointToSplit);
|
||||
|
||||
/**
|
||||
* MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
|
||||
* first line in aRightBlockElement into end of aLeftBlockElement which
|
||||
@@ -347,6 +356,17 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
|
||||
const nsAString& aReplaceString);
|
||||
|
||||
/**
|
||||
* Normalize surrounding white-spaces of aPointToSplit.
|
||||
*
|
||||
* @return The split point which you specified before. Note that the result
|
||||
* may be different from aPointToSplit if this deletes some invisible
|
||||
* white-spaces.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
|
||||
NormalizeWhiteSpacesToSplitTextNodeAt(
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit);
|
||||
|
||||
/**
|
||||
* Delete leading or trailing invisible white-spaces around block boundaries
|
||||
* or collapsed white-spaces in a white-space sequence if aPoint is around
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
[insertlinebreak.tentative.html]
|
||||
[execCommand("insertlinebreak", false, "") at "a[\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
|
||||
[execCommand("insertlinebreak", false, "") at "a [\] b"]
|
||||
expected: FAIL
|
||||
Reference in New Issue
Block a user