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;
|
return !!maybeTextControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsStartOfContainer() const {
|
[[nodiscard]] bool IsStartOfContainer() const {
|
||||||
// If we're referring the first point in the container:
|
// If we're referring the first point in the container:
|
||||||
// If mParent is not a container like a text node, mOffset is 0.
|
// If mParent is not a container like a text node, mOffset is 0.
|
||||||
// If mChild is initialized and it's first child of mParent.
|
// If mChild is initialized and it's first child of mParent.
|
||||||
@@ -984,7 +984,29 @@ class EditorDOMPointBase final {
|
|||||||
return !mOffset.value();
|
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 we're referring after the last point of the container:
|
||||||
// If mParent is not a container like text node, mOffset is same as the
|
// If mParent is not a container like text node, mOffset is same as the
|
||||||
// length of the container.
|
// length of the container.
|
||||||
|
|||||||
@@ -559,9 +559,9 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
|
|||||||
return !TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces;
|
return !TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces;
|
||||||
case EditSubAction::eInsertText:
|
case EditSubAction::eInsertText:
|
||||||
case EditSubAction::eInsertTextComingFromIME:
|
case EditSubAction::eInsertTextComingFromIME:
|
||||||
|
case EditSubAction::eInsertLineBreak:
|
||||||
return !StaticPrefs::
|
return !StaticPrefs::
|
||||||
editor_white_space_normalization_blink_compatible();
|
editor_white_space_normalization_blink_compatible();
|
||||||
case EditSubAction::eInsertLineBreak:
|
|
||||||
case EditSubAction::eInsertParagraphSeparator:
|
case EditSubAction::eInsertParagraphSeparator:
|
||||||
case EditSubAction::ePasteHTMLContent:
|
case EditSubAction::ePasteHTMLContent:
|
||||||
case EditSubAction::eInsertHTMLSource:
|
case EditSubAction::eInsertHTMLSource:
|
||||||
@@ -3230,6 +3230,163 @@ HTMLEditor::NormalizeWhiteSpacesToInsertText(
|
|||||||
return result;
|
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(
|
void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
|
||||||
EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
|
EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
|
||||||
nsString& aNormalizedWhiteSpacesInStartNode,
|
nsString& aNormalizedWhiteSpacesInStartNode,
|
||||||
|
|||||||
@@ -4191,6 +4191,13 @@ Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction(
|
|||||||
return caretPointOrError;
|
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(
|
Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
|
||||||
Text& aTextNode, uint32_t aOffset, uint32_t aLength,
|
Text& aTextNode, uint32_t aOffset, uint32_t aLength,
|
||||||
const nsAString& aStringToInsert) {
|
const nsAString& aStringToInsert) {
|
||||||
@@ -4382,7 +4389,16 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertLineBreak(
|
|||||||
!CanInsertLineBreak(*aPointToInsert.ContainerAs<nsIContent>()))) {
|
!CanInsertLineBreak(*aPointToInsert.ContainerAs<nsIContent>()))) {
|
||||||
return Err(NS_ERROR_FAILURE);
|
return Err(NS_ERROR_FAILURE);
|
||||||
}
|
}
|
||||||
return aPointToInsert;
|
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
|
// If the text node is not in an element node, we cannot insert a line break
|
||||||
@@ -4394,21 +4410,34 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertLineBreak(
|
|||||||
return Err(NS_ERROR_FAILURE);
|
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.
|
// Insert before the text node.
|
||||||
return aPointToInsert.ParentPoint();
|
return pointToInsert.ParentPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aPointToInsert.IsEndOfContainer()) {
|
if (pointToInsert.IsEndOfContainer()) {
|
||||||
// Insert after the text node.
|
// 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.
|
// Unfortunately, we need to split the text node at the offset.
|
||||||
Result<SplitNodeResult, nsresult> splitTextNodeResult =
|
Result<SplitNodeResult, nsresult> splitTextNodeResult =
|
||||||
SplitNodeWithTransaction(aPointToInsert);
|
SplitNodeWithTransaction(pointToInsert);
|
||||||
if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
|
if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
|
||||||
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
|
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
|
||||||
return splitTextNodeResult.propagateErr();
|
return splitTextNodeResult.propagateErr();
|
||||||
|
|||||||
@@ -856,6 +856,15 @@ class HTMLEditor final : public EditorBase,
|
|||||||
InsertOrReplaceTextWithTransaction(const EditorDOMPoint& aPointToInsert,
|
InsertOrReplaceTextWithTransaction(const EditorDOMPoint& aPointToInsert,
|
||||||
const NormalizedStringToInsertText& aData);
|
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,
|
* Insert aStringToInsert to aPointToInsert. If the point is not editable,
|
||||||
* this returns error.
|
* this returns error.
|
||||||
@@ -2231,6 +2240,20 @@ class HTMLEditor final : public EditorBase,
|
|||||||
const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
|
const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
|
||||||
NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const;
|
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
|
* ExtendRangeToDeleteWithNormalizingWhiteSpaces() is a helper method of
|
||||||
* DeleteTextAndNormalizeSurroundingWhiteSpaces(). This expands
|
* DeleteTextAndNormalizeSurroundingWhiteSpaces(). This expands
|
||||||
|
|||||||
@@ -686,6 +686,113 @@ struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final {
|
|||||||
const uint32_t mNewLengthAfter = 0u;
|
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
|
} // namespace mozilla
|
||||||
|
|
||||||
#endif // #ifndef HTMLEditorNestedClasses_h
|
#endif // #ifndef HTMLEditorNestedClasses_h
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace mozilla {
|
|||||||
|
|
||||||
using namespace dom;
|
using namespace dom;
|
||||||
|
|
||||||
|
using LeafNodeType = HTMLEditUtils::LeafNodeType;
|
||||||
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
|
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
|
||||||
|
|
||||||
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
|
template nsresult WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(
|
||||||
@@ -817,6 +818,271 @@ Result<MoveNodeResult, nsresult> WhiteSpaceVisibilityKeeper::
|
|||||||
return std::move(unwrappedMoveContentResult);
|
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
|
// static
|
||||||
Result<CreateLineBreakResult, nsresult>
|
Result<CreateLineBreakResult, nsresult>
|
||||||
WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
||||||
@@ -826,104 +1092,54 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
|||||||
return Err(NS_ERROR_INVALID_ARG);
|
return Err(NS_ERROR_INVALID_ARG);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
|
||||||
// meanwhile, the pre case is handled in HandleInsertText() in
|
|
||||||
// HTMLEditSubActionHandler.cpp
|
|
||||||
|
|
||||||
const TextFragmentData textFragmentDataAtInsertionPoint(
|
|
||||||
Scan::EditableNodes, aPointToInsert,
|
|
||||||
BlockInlineCheck::UseComputedDisplayStyle);
|
|
||||||
if (NS_WARN_IF(!textFragmentDataAtInsertionPoint.IsInitialized())) {
|
|
||||||
return Err(NS_ERROR_FAILURE);
|
|
||||||
}
|
|
||||||
EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine =
|
|
||||||
textFragmentDataAtInsertionPoint
|
|
||||||
.GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
|
|
||||||
EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine =
|
|
||||||
textFragmentDataAtInsertionPoint
|
|
||||||
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
|
|
||||||
const Maybe<const VisibleWhiteSpacesData> visibleWhiteSpaces =
|
|
||||||
!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() ||
|
|
||||||
!invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()
|
|
||||||
? Some(textFragmentDataAtInsertionPoint.VisibleWhiteSpacesDataRef())
|
|
||||||
: Nothing();
|
|
||||||
const PointPosition pointPositionWithVisibleWhiteSpaces =
|
|
||||||
visibleWhiteSpaces.isSome() && visibleWhiteSpaces.ref().IsInitialized()
|
|
||||||
? visibleWhiteSpaces.ref().ComparePoint(aPointToInsert)
|
|
||||||
: PointPosition::NotInSameDOMTree;
|
|
||||||
|
|
||||||
EditorDOMPoint pointToInsert(aPointToInsert);
|
EditorDOMPoint pointToInsert(aPointToInsert);
|
||||||
EditorDOMPoint atNBSPReplaceableWithSP;
|
// TODO: Delete this block once we ship the new normalizer.
|
||||||
if (!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() &&
|
if (!StaticPrefs::editor_white_space_normalization_blink_compatible()) {
|
||||||
(pointPositionWithVisibleWhiteSpaces == PointPosition::MiddleOfFragment ||
|
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
|
||||||
pointPositionWithVisibleWhiteSpaces == PointPosition::EndOfFragment)) {
|
// meanwhile, the pre case is handled in HandleInsertText() in
|
||||||
atNBSPReplaceableWithSP =
|
// HTMLEditSubActionHandler.cpp
|
||||||
textFragmentDataAtInsertionPoint
|
|
||||||
.GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
|
|
||||||
pointToInsert)
|
|
||||||
.To<EditorDOMPoint>();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
const TextFragmentData textFragmentDataAtInsertionPoint(
|
||||||
if (invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()) {
|
Scan::EditableNodes, aPointToInsert,
|
||||||
if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine.Collapsed()) {
|
BlockInlineCheck::UseComputedDisplayStyle);
|
||||||
// XXX Why don't we remove all of the invisible white-spaces?
|
if (NS_WARN_IF(!textFragmentDataAtInsertionPoint.IsInitialized())) {
|
||||||
MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef() ==
|
return Err(NS_ERROR_FAILURE);
|
||||||
pointToInsert);
|
|
||||||
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
|
||||||
&pointToInsert);
|
|
||||||
AutoTrackDOMPoint trackEndOfLineNBSP(aHTMLEditor.RangeUpdaterRef(),
|
|
||||||
&atNBSPReplaceableWithSP);
|
|
||||||
AutoTrackDOMRange trackLeadingWhiteSpaceRange(
|
|
||||||
aHTMLEditor.RangeUpdaterRef(),
|
|
||||||
&invisibleLeadingWhiteSpaceRangeOfNewLine);
|
|
||||||
Result<CaretPoint, nsresult> caretPointOrError =
|
|
||||||
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
|
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef(),
|
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.EndRef(),
|
|
||||||
HTMLEditor::TreatEmptyTextNodes::
|
|
||||||
KeepIfContainerOfRangeBoundaries);
|
|
||||||
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
|
|
||||||
NS_WARNING(
|
|
||||||
"HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
|
|
||||||
return caretPointOrError.propagateErr();
|
|
||||||
}
|
|
||||||
nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
|
|
||||||
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
|
|
||||||
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
|
||||||
SuggestCaret::AndIgnoreTrivialError});
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
|
|
||||||
return Err(rv);
|
|
||||||
}
|
|
||||||
NS_WARNING_ASSERTION(
|
|
||||||
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
|
||||||
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
|
||||||
// Don't refer the following variables anymore unless tracking the
|
|
||||||
// change.
|
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If new line will start with visible white-spaces, it needs to be start
|
EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine =
|
||||||
// with an NBSP.
|
textFragmentDataAtInsertionPoint
|
||||||
else if (pointPositionWithVisibleWhiteSpaces ==
|
.GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(aPointToInsert);
|
||||||
PointPosition::StartOfFragment ||
|
EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine =
|
||||||
pointPositionWithVisibleWhiteSpaces ==
|
textFragmentDataAtInsertionPoint
|
||||||
PointPosition::MiddleOfFragment) {
|
.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
|
||||||
const auto atNextCharOfInsertionPoint =
|
aPointToInsert);
|
||||||
|
const Maybe<const VisibleWhiteSpacesData> visibleWhiteSpaces =
|
||||||
|
!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() ||
|
||||||
|
!invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()
|
||||||
|
? Some(textFragmentDataAtInsertionPoint.VisibleWhiteSpacesDataRef())
|
||||||
|
: Nothing();
|
||||||
|
const PointPosition pointPositionWithVisibleWhiteSpaces =
|
||||||
|
visibleWhiteSpaces.isSome() && visibleWhiteSpaces.ref().IsInitialized()
|
||||||
|
? visibleWhiteSpaces.ref().ComparePoint(aPointToInsert)
|
||||||
|
: PointPosition::NotInSameDOMTree;
|
||||||
|
|
||||||
|
EditorDOMPoint atNBSPReplaceableWithSP;
|
||||||
|
if (!invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() &&
|
||||||
|
(pointPositionWithVisibleWhiteSpaces ==
|
||||||
|
PointPosition::MiddleOfFragment ||
|
||||||
|
pointPositionWithVisibleWhiteSpaces == PointPosition::EndOfFragment)) {
|
||||||
|
atNBSPReplaceableWithSP =
|
||||||
textFragmentDataAtInsertionPoint
|
textFragmentDataAtInsertionPoint
|
||||||
.GetInclusiveNextCharPoint<EditorDOMPointInText>(
|
.GetPreviousNBSPPointIfNeedToReplaceWithASCIIWhiteSpace(
|
||||||
pointToInsert, IgnoreNonEditableNodes::Yes);
|
pointToInsert)
|
||||||
if (atNextCharOfInsertionPoint.IsSet() &&
|
.To<EditorDOMPoint>();
|
||||||
!atNextCharOfInsertionPoint.IsEndOfContainer() &&
|
}
|
||||||
atNextCharOfInsertionPoint.IsCharCollapsibleASCIISpace()) {
|
|
||||||
const auto atPreviousCharOfNextCharOfInsertionPoint =
|
{
|
||||||
textFragmentDataAtInsertionPoint
|
if (invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()) {
|
||||||
.GetPreviousCharPoint<EditorDOMPointInText>(
|
if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine.Collapsed()) {
|
||||||
atNextCharOfInsertionPoint, IgnoreNonEditableNodes::Yes);
|
// XXX Why don't we remove all of the invisible white-spaces?
|
||||||
if (!atPreviousCharOfNextCharOfInsertionPoint.IsSet() ||
|
MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef() ==
|
||||||
atPreviousCharOfNextCharOfInsertionPoint.IsEndOfContainer() ||
|
pointToInsert);
|
||||||
!atPreviousCharOfNextCharOfInsertionPoint.IsCharASCIISpace()) {
|
|
||||||
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
||||||
&pointToInsert);
|
&pointToInsert);
|
||||||
AutoTrackDOMPoint trackEndOfLineNBSP(aHTMLEditor.RangeUpdaterRef(),
|
AutoTrackDOMPoint trackEndOfLineNBSP(aHTMLEditor.RangeUpdaterRef(),
|
||||||
@@ -931,96 +1147,166 @@ WhiteSpaceVisibilityKeeper::InsertLineBreak(
|
|||||||
AutoTrackDOMRange trackLeadingWhiteSpaceRange(
|
AutoTrackDOMRange trackLeadingWhiteSpaceRange(
|
||||||
aHTMLEditor.RangeUpdaterRef(),
|
aHTMLEditor.RangeUpdaterRef(),
|
||||||
&invisibleLeadingWhiteSpaceRangeOfNewLine);
|
&invisibleLeadingWhiteSpaceRangeOfNewLine);
|
||||||
// We are at start of non-NBSPs. Convert to a single NBSP.
|
Result<CaretPoint, nsresult> caretPointOrError =
|
||||||
const auto endOfCollapsibleASCIIWhiteSpaces =
|
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
|
||||||
textFragmentDataAtInsertionPoint
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef(),
|
||||||
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.EndRef(),
|
||||||
atNextCharOfInsertionPoint, nsIEditor::eNone,
|
HTMLEditor::TreatEmptyTextNodes::
|
||||||
// XXX Shouldn't be "No"? Skipping non-editable nodes may
|
KeepIfContainerOfRangeBoundaries);
|
||||||
// have visible content.
|
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
|
||||||
IgnoreNonEditableNodes::Yes);
|
|
||||||
nsresult rv =
|
|
||||||
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
|
|
||||||
aHTMLEditor,
|
|
||||||
EditorDOMRangeInTexts(atNextCharOfInsertionPoint,
|
|
||||||
endOfCollapsibleASCIIWhiteSpaces),
|
|
||||||
nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
|
|
||||||
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
|
|
||||||
NS_WARNING(
|
NS_WARNING(
|
||||||
"WhiteSpaceVisibilityKeeper::"
|
"HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
|
||||||
"ReplaceTextAndRemoveEmptyTextNodes() failed");
|
return caretPointOrError.propagateErr();
|
||||||
|
}
|
||||||
|
nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
|
||||||
|
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
|
||||||
|
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
||||||
|
SuggestCaret::AndIgnoreTrivialError});
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
|
||||||
return Err(rv);
|
return Err(rv);
|
||||||
}
|
}
|
||||||
|
NS_WARNING_ASSERTION(
|
||||||
|
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
||||||
|
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
||||||
// Don't refer the following variables anymore unless tracking the
|
// Don't refer the following variables anymore unless tracking the
|
||||||
// change.
|
// change.
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// If new line will start with visible white-spaces, it needs to be start
|
||||||
|
// with an NBSP.
|
||||||
|
else if (pointPositionWithVisibleWhiteSpaces ==
|
||||||
|
PointPosition::StartOfFragment ||
|
||||||
|
pointPositionWithVisibleWhiteSpaces ==
|
||||||
|
PointPosition::MiddleOfFragment) {
|
||||||
|
const auto atNextCharOfInsertionPoint =
|
||||||
|
textFragmentDataAtInsertionPoint
|
||||||
|
.GetInclusiveNextCharPoint<EditorDOMPointInText>(
|
||||||
|
pointToInsert, IgnoreNonEditableNodes::Yes);
|
||||||
|
if (atNextCharOfInsertionPoint.IsSet() &&
|
||||||
|
!atNextCharOfInsertionPoint.IsEndOfContainer() &&
|
||||||
|
atNextCharOfInsertionPoint.IsCharCollapsibleASCIISpace()) {
|
||||||
|
const auto atPreviousCharOfNextCharOfInsertionPoint =
|
||||||
|
textFragmentDataAtInsertionPoint
|
||||||
|
.GetPreviousCharPoint<EditorDOMPointInText>(
|
||||||
|
atNextCharOfInsertionPoint, IgnoreNonEditableNodes::Yes);
|
||||||
|
if (!atPreviousCharOfNextCharOfInsertionPoint.IsSet() ||
|
||||||
|
atPreviousCharOfNextCharOfInsertionPoint.IsEndOfContainer() ||
|
||||||
|
!atPreviousCharOfNextCharOfInsertionPoint.IsCharASCIISpace()) {
|
||||||
|
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
||||||
|
&pointToInsert);
|
||||||
|
AutoTrackDOMPoint trackEndOfLineNBSP(aHTMLEditor.RangeUpdaterRef(),
|
||||||
|
&atNBSPReplaceableWithSP);
|
||||||
|
AutoTrackDOMRange trackLeadingWhiteSpaceRange(
|
||||||
|
aHTMLEditor.RangeUpdaterRef(),
|
||||||
|
&invisibleLeadingWhiteSpaceRangeOfNewLine);
|
||||||
|
// We are at start of non-NBSPs. Convert to a single NBSP.
|
||||||
|
const auto endOfCollapsibleASCIIWhiteSpaces =
|
||||||
|
textFragmentDataAtInsertionPoint
|
||||||
|
.GetEndOfCollapsibleASCIIWhiteSpaces<EditorDOMPointInText>(
|
||||||
|
atNextCharOfInsertionPoint, nsIEditor::eNone,
|
||||||
|
// XXX Shouldn't be "No"? Skipping non-editable nodes
|
||||||
|
// may have visible content.
|
||||||
|
IgnoreNonEditableNodes::Yes);
|
||||||
|
nsresult rv =
|
||||||
|
WhiteSpaceVisibilityKeeper::ReplaceTextAndRemoveEmptyTextNodes(
|
||||||
|
aHTMLEditor,
|
||||||
|
EditorDOMRangeInTexts(atNextCharOfInsertionPoint,
|
||||||
|
endOfCollapsibleASCIIWhiteSpaces),
|
||||||
|
nsDependentSubstring(&HTMLEditUtils::kNBSP, 1));
|
||||||
|
if (MOZ_UNLIKELY(NS_FAILED(rv))) {
|
||||||
|
NS_WARNING(
|
||||||
|
"WhiteSpaceVisibilityKeeper::"
|
||||||
|
"ReplaceTextAndRemoveEmptyTextNodes() failed");
|
||||||
|
return Err(rv);
|
||||||
|
}
|
||||||
|
// Don't refer the following variables anymore unless tracking the
|
||||||
|
// change.
|
||||||
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned()) {
|
if (invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned()) {
|
||||||
if (!invisibleLeadingWhiteSpaceRangeOfNewLine.Collapsed()) {
|
if (!invisibleLeadingWhiteSpaceRangeOfNewLine.Collapsed()) {
|
||||||
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
||||||
&pointToInsert);
|
&pointToInsert);
|
||||||
// XXX Why don't we remove all of the invisible white-spaces?
|
// XXX Why don't we remove all of the invisible white-spaces?
|
||||||
MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef() ==
|
MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef() ==
|
||||||
pointToInsert);
|
pointToInsert);
|
||||||
Result<CaretPoint, nsresult> caretPointOrError =
|
Result<CaretPoint, nsresult> caretPointOrError =
|
||||||
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
|
aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
|
||||||
invisibleLeadingWhiteSpaceRangeOfNewLine.StartRef(),
|
invisibleLeadingWhiteSpaceRangeOfNewLine.StartRef(),
|
||||||
invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef(),
|
invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef(),
|
||||||
HTMLEditor::TreatEmptyTextNodes::
|
HTMLEditor::TreatEmptyTextNodes::
|
||||||
KeepIfContainerOfRangeBoundaries);
|
KeepIfContainerOfRangeBoundaries);
|
||||||
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
|
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
|
||||||
NS_WARNING(
|
NS_WARNING(
|
||||||
"HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
|
"HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
|
||||||
return caretPointOrError.propagateErr();
|
return caretPointOrError.propagateErr();
|
||||||
|
}
|
||||||
|
nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
|
||||||
|
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
|
||||||
|
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
||||||
|
SuggestCaret::AndIgnoreTrivialError});
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
|
||||||
|
return Err(rv);
|
||||||
|
}
|
||||||
|
NS_WARNING_ASSERTION(
|
||||||
|
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
||||||
|
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
||||||
|
// Don't refer the following variables anymore unless tracking the
|
||||||
|
// change.
|
||||||
|
atNBSPReplaceableWithSP.Clear();
|
||||||
|
invisibleLeadingWhiteSpaceRangeOfNewLine.Clear();
|
||||||
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
||||||
}
|
}
|
||||||
nsresult rv = caretPointOrError.unwrap().SuggestCaretPointTo(
|
}
|
||||||
aHTMLEditor, {SuggestCaret::OnlyIfHasSuggestion,
|
// If the `<br>` element is put immediately after an NBSP, it should be
|
||||||
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
// replaced with an ASCII white-space.
|
||||||
SuggestCaret::AndIgnoreTrivialError});
|
else if (atNBSPReplaceableWithSP.IsInTextNode()) {
|
||||||
if (NS_FAILED(rv)) {
|
const EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace =
|
||||||
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
|
atNBSPReplaceableWithSP.AsInText();
|
||||||
return Err(rv);
|
if (!atNBSPReplacedWithASCIIWhiteSpace.IsEndOfContainer() &&
|
||||||
|
atNBSPReplacedWithASCIIWhiteSpace.IsCharNBSP()) {
|
||||||
|
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
||||||
|
&pointToInsert);
|
||||||
|
Result<InsertTextResult, nsresult> replaceTextResult =
|
||||||
|
aHTMLEditor.ReplaceTextWithTransaction(
|
||||||
|
MOZ_KnownLive(
|
||||||
|
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
|
||||||
|
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
|
||||||
|
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
|
||||||
|
NS_WARNING(
|
||||||
|
"HTMLEditor::ReplaceTextWithTransaction() failed failed");
|
||||||
|
return replaceTextResult.propagateErr();
|
||||||
|
}
|
||||||
|
// Ignore caret suggestion because there was
|
||||||
|
// AutoTransactionsConserveSelection.
|
||||||
|
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
|
||||||
|
// Don't refer the following variables anymore unless tracking the
|
||||||
|
// change.
|
||||||
|
atNBSPReplaceableWithSP.Clear();
|
||||||
|
invisibleLeadingWhiteSpaceRangeOfNewLine.Clear();
|
||||||
|
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
||||||
}
|
}
|
||||||
NS_WARNING_ASSERTION(
|
|
||||||
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
|
||||||
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
|
||||||
// Don't refer the following variables anymore unless tracking the
|
|
||||||
// change.
|
|
||||||
atNBSPReplaceableWithSP.Clear();
|
|
||||||
invisibleLeadingWhiteSpaceRangeOfNewLine.Clear();
|
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the `<br>` element is put immediately after an NBSP, it should be
|
} else {
|
||||||
// replaced with an ASCII white-space.
|
Result<EditorDOMPoint, nsresult>
|
||||||
else if (atNBSPReplaceableWithSP.IsInTextNode()) {
|
normalizeSurroundingWhiteSpacesResultOrError =
|
||||||
const EditorDOMPointInText atNBSPReplacedWithASCIIWhiteSpace =
|
WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt(
|
||||||
atNBSPReplaceableWithSP.AsInText();
|
aHTMLEditor, aPointToInsert);
|
||||||
if (!atNBSPReplacedWithASCIIWhiteSpace.IsEndOfContainer() &&
|
if (MOZ_UNLIKELY(normalizeSurroundingWhiteSpacesResultOrError.isErr())) {
|
||||||
atNBSPReplacedWithASCIIWhiteSpace.IsCharNBSP()) {
|
NS_WARNING(
|
||||||
AutoTrackDOMPoint trackPointToInsert(aHTMLEditor.RangeUpdaterRef(),
|
"WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed");
|
||||||
&pointToInsert);
|
return normalizeSurroundingWhiteSpacesResultOrError.propagateErr();
|
||||||
Result<InsertTextResult, nsresult> replaceTextResult =
|
}
|
||||||
aHTMLEditor.ReplaceTextWithTransaction(
|
pointToInsert = normalizeSurroundingWhiteSpacesResultOrError.unwrap();
|
||||||
MOZ_KnownLive(
|
if (NS_WARN_IF(!pointToInsert.IsSet())) {
|
||||||
*atNBSPReplacedWithASCIIWhiteSpace.ContainerAs<Text>()),
|
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||||
atNBSPReplacedWithASCIIWhiteSpace.Offset(), 1, u" "_ns);
|
|
||||||
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
|
|
||||||
NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed failed");
|
|
||||||
return replaceTextResult.propagateErr();
|
|
||||||
}
|
|
||||||
// Ignore caret suggestion because there was
|
|
||||||
// AutoTransactionsConserveSelection.
|
|
||||||
replaceTextResult.unwrap().IgnoreCaretPointSuggestion();
|
|
||||||
// Don't refer the following variables anymore unless tracking the
|
|
||||||
// change.
|
|
||||||
atNBSPReplaceableWithSP.Clear();
|
|
||||||
invisibleLeadingWhiteSpaceRangeOfNewLine.Clear();
|
|
||||||
invisibleTrailingWhiteSpaceRangeOfCurrentLine.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ class WhiteSpaceVisibilityKeeper final {
|
|||||||
const EditorDOMPoint& aPointToSplit,
|
const EditorDOMPoint& aPointToSplit,
|
||||||
const Element& aSplittingBlockElement);
|
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
|
* MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
|
||||||
* first line in aRightBlockElement into end of aLeftBlockElement which
|
* first line in aRightBlockElement into end of aLeftBlockElement which
|
||||||
@@ -347,6 +356,17 @@ class WhiteSpaceVisibilityKeeper final {
|
|||||||
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
|
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
|
||||||
const nsAString& aReplaceString);
|
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
|
* Delete leading or trailing invisible white-spaces around block boundaries
|
||||||
* or collapsed white-spaces in a white-space sequence if aPoint is around
|
* 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