Bug 1940377 - part 1: Make WhiteSpaceVisibilityKeeper::InsertTextOrInsertOrUpdateCompositionString support blink compatible white-space sequence r=m_kato
First, it makes it deletes invisible white-spaces at the insertion point. This makes the further processing simpler. Second, it extends the range to update in `Text` which will contain the insertion string when it's not used for inserting nor updating composition string, as containing all surrounding white-spaces at the insertion point. Next, it normalizes the inserting string with the surrounding white-spaces with the same rules as the other browsers especially as Chrome. 1. a collapsible white-space at start or end of a `Text` is always an NBSP. 2. a collapsible white-space immediately before or after a preformatted line break is always an NBSP. 3. a collapsible white-space surrounded by non-collapsible chars is always an ASCII white-space. 4. collapsible white-spaces are pairs of an NBSP and an ASCII white-space. Then, we can make `HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal()` stop normalizing white-spaces when inserting text. Differential Revision: https://phabricator.services.mozilla.com/D239463
This commit is contained in:
@@ -3244,6 +3244,67 @@ nsresult EditorBase::ScrollSelectionFocusIntoView() const {
|
||||
return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
|
||||
}
|
||||
|
||||
EditorDOMPoint EditorBase::ComputePointToInsertText(
|
||||
const EditorDOMPoint& aPoint, InsertTextTo aInsertTextTo) const {
|
||||
if (aInsertTextTo == InsertTextTo::SpecifiedPoint) {
|
||||
return aPoint;
|
||||
}
|
||||
|
||||
if (IsTextEditor()) {
|
||||
// In some cases, the node may be the anonymous div element or a padding
|
||||
// <br> element for empty last line. Let's try to look for better insertion
|
||||
// point in the nearest text node if there is.
|
||||
return AsTextEditor()->FindBetterInsertionPoint(aPoint);
|
||||
}
|
||||
auto pointToInsert =
|
||||
aPoint.GetPointInTextNodeIfPointingAroundTextNode<EditorDOMPoint>();
|
||||
// If the candidate point is in a Text node which has only a preformatted
|
||||
// linefeed, we should not insert text into the node because it may have
|
||||
// been inserted by us and that's compatible behavior with Chrome.
|
||||
if (pointToInsert.IsInTextNode() &&
|
||||
HTMLEditUtils::TextHasOnlyOnePreformattedLinefeed(
|
||||
*pointToInsert.ContainerAs<Text>())) {
|
||||
if (pointToInsert.IsStartOfContainer()) {
|
||||
if (Text* const previousText = Text::FromNodeOrNull(
|
||||
pointToInsert.ContainerAs<Text>()->GetPreviousSibling())) {
|
||||
pointToInsert = EditorDOMPoint::AtEndOf(*previousText);
|
||||
} else {
|
||||
pointToInsert = pointToInsert.ParentPoint();
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(pointToInsert.IsEndOfContainer());
|
||||
if (Text* const nextText = Text::FromNodeOrNull(
|
||||
pointToInsert.ContainerAs<Text>()->GetNextSibling())) {
|
||||
pointToInsert = EditorDOMPoint(nextText, 0u);
|
||||
} else {
|
||||
pointToInsert = pointToInsert.AfterContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aInsertTextTo == InsertTextTo::AlwaysCreateNewTextNode) {
|
||||
NS_WARNING_ASSERTION(!pointToInsert.IsInTextNode() ||
|
||||
pointToInsert.IsStartOfContainer() ||
|
||||
pointToInsert.IsEndOfContainer(),
|
||||
"aPointToInsert is \"AlwaysCreateNewTextNode\", but "
|
||||
"specified point middle of a `Text`");
|
||||
if (!pointToInsert.IsInTextNode()) {
|
||||
return pointToInsert;
|
||||
}
|
||||
return pointToInsert.IsStartOfContainer()
|
||||
? EditorDOMPoint(pointToInsert.ContainerAs<Text>())
|
||||
: (pointToInsert.IsEndOfContainer()
|
||||
? EditorDOMPoint::After(
|
||||
*pointToInsert.ContainerAs<Text>())
|
||||
: pointToInsert);
|
||||
}
|
||||
if (aInsertTextTo == InsertTextTo::ExistingTextNodeIfAvailableAndNotStart) {
|
||||
return !(pointToInsert.IsInTextNode() && pointToInsert.IsStartOfContainer())
|
||||
? pointToInsert
|
||||
: EditorDOMPoint(pointToInsert.ContainerAs<Text>());
|
||||
}
|
||||
return pointToInsert;
|
||||
}
|
||||
|
||||
Result<InsertTextResult, nsresult> EditorBase::InsertTextWithTransaction(
|
||||
const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert,
|
||||
InsertTextTo aInsertTextTo) {
|
||||
@@ -3260,64 +3321,8 @@ Result<InsertTextResult, nsresult> EditorBase::InsertTextWithTransaction(
|
||||
return InsertTextResult();
|
||||
}
|
||||
|
||||
// In some cases, the node may be the anonymous div element or a padding
|
||||
// <br> element for empty last line. Let's try to look for better insertion
|
||||
// point in the nearest text node if there is.
|
||||
EditorDOMPoint pointToInsert = [&]() {
|
||||
if (IsTextEditor()) {
|
||||
return AsTextEditor()->FindBetterInsertionPoint(aPointToInsert);
|
||||
}
|
||||
auto pointToInsert =
|
||||
aPointToInsert
|
||||
.GetPointInTextNodeIfPointingAroundTextNode<EditorDOMPoint>();
|
||||
// If the candidate point is in a Text node which has only a preformatted
|
||||
// linefeed, we should not insert text into the node because it may have
|
||||
// been inserted by us and that's compatible behavior with Chrome.
|
||||
if (pointToInsert.IsInTextNode() &&
|
||||
HTMLEditUtils::TextHasOnlyOnePreformattedLinefeed(
|
||||
*pointToInsert.ContainerAs<Text>())) {
|
||||
if (pointToInsert.IsStartOfContainer()) {
|
||||
if (Text* const previousText = Text::FromNodeOrNull(
|
||||
pointToInsert.ContainerAs<Text>()->GetPreviousSibling())) {
|
||||
pointToInsert = EditorDOMPoint::AtEndOf(*previousText);
|
||||
} else {
|
||||
pointToInsert = pointToInsert.ParentPoint();
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(pointToInsert.IsEndOfContainer());
|
||||
if (Text* const nextText = Text::FromNodeOrNull(
|
||||
pointToInsert.ContainerAs<Text>()->GetNextSibling())) {
|
||||
pointToInsert = EditorDOMPoint(nextText, 0u);
|
||||
} else {
|
||||
pointToInsert = pointToInsert.AfterContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (aInsertTextTo == InsertTextTo::AlwaysCreateNewTextNode) {
|
||||
NS_WARNING_ASSERTION(!pointToInsert.IsInTextNode() ||
|
||||
pointToInsert.IsStartOfContainer() ||
|
||||
pointToInsert.IsEndOfContainer(),
|
||||
"aPointToInsert is \"AlwaysCreateNewTextNode\", but "
|
||||
"specified point middle of a `Text`");
|
||||
if (!pointToInsert.IsInTextNode()) {
|
||||
return pointToInsert;
|
||||
}
|
||||
return pointToInsert.IsStartOfContainer()
|
||||
? EditorDOMPoint(pointToInsert.ContainerAs<Text>())
|
||||
: (pointToInsert.IsEndOfContainer()
|
||||
? EditorDOMPoint::After(
|
||||
*pointToInsert.ContainerAs<Text>())
|
||||
: pointToInsert);
|
||||
}
|
||||
if (aInsertTextTo == InsertTextTo::ExistingTextNodeIfAvailableAndNotStart) {
|
||||
return !(pointToInsert.IsInTextNode() &&
|
||||
pointToInsert.IsStartOfContainer())
|
||||
? pointToInsert
|
||||
: EditorDOMPoint(pointToInsert.ContainerAs<Text>());
|
||||
}
|
||||
return pointToInsert;
|
||||
}();
|
||||
|
||||
EditorDOMPoint pointToInsert =
|
||||
ComputePointToInsertText(aPointToInsert, aInsertTextTo);
|
||||
if (ShouldHandleIMEComposition()) {
|
||||
if (!pointToInsert.IsInTextNode()) {
|
||||
// create a text node
|
||||
|
||||
@@ -1775,6 +1775,7 @@ class EditorBase : public nsIEditor,
|
||||
* available.
|
||||
*/
|
||||
enum class InsertTextTo {
|
||||
SpecifiedPoint,
|
||||
ExistingTextNodeIfAvailable,
|
||||
ExistingTextNodeIfAvailableAndNotStart,
|
||||
AlwaysCreateNewTextNode
|
||||
@@ -1784,6 +1785,12 @@ class EditorBase : public nsIEditor,
|
||||
const EditorDOMPoint& aPointToInsert,
|
||||
InsertTextTo aInsertTextTo);
|
||||
|
||||
/**
|
||||
* Compute insertion point from aPoint and aInsertTextTo.
|
||||
*/
|
||||
[[nodiscard]] EditorDOMPoint ComputePointToInsertText(
|
||||
const EditorDOMPoint& aPoint, InsertTextTo aInsertTextTo) const;
|
||||
|
||||
/**
|
||||
* Insert aStringToInsert to aPointToInsert.
|
||||
*/
|
||||
@@ -3055,8 +3062,10 @@ class EditorBase : public nsIEditor,
|
||||
// CollapseSelectionTo, DoReplaceText,
|
||||
// RangeUpdaterRef
|
||||
friend class SplitNodeTransaction; // ToGenericNSResult
|
||||
friend class WhiteSpaceVisibilityKeeper; // AutoTransactionsConserveSelection
|
||||
friend class nsIEditor; // mIsHTMLEditorClass
|
||||
friend class
|
||||
WhiteSpaceVisibilityKeeper; // AutoTransactionsConserveSelection,
|
||||
// ComputePointToInsertText
|
||||
friend class nsIEditor; // mIsHTMLEditorClass
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -631,6 +631,48 @@ class EditorDOMPointBase final {
|
||||
return AtEndOf(*aContainer.get(), aInterlinePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetToLastContentOf() sets this to the last child of aContainer or the last
|
||||
* character of aContainer.
|
||||
*/
|
||||
template <typename ContainerType>
|
||||
void SetToLastContentOf(const ContainerType* aContainer) {
|
||||
MOZ_ASSERT(aContainer);
|
||||
mParent = const_cast<ContainerType*>(aContainer);
|
||||
if (aContainer->IsContainerNode()) {
|
||||
MOZ_ASSERT(aContainer->GetChildCount());
|
||||
mChild = aContainer->GetLastChild();
|
||||
mOffset = mozilla::Some(aContainer->GetChildCount() - 1u);
|
||||
} else {
|
||||
MOZ_ASSERT(aContainer->Length());
|
||||
mChild = nullptr;
|
||||
mOffset = mozilla::Some(aContainer->Length() - 1u);
|
||||
}
|
||||
mIsChildInitialized = true;
|
||||
mInterlinePosition = InterlinePosition::Undefined;
|
||||
}
|
||||
template <typename ContainerType, template <typename> typename StrongPtr>
|
||||
MOZ_NEVER_INLINE_DEBUG void SetToLastContentOf(
|
||||
const StrongPtr<ContainerType>& aContainer) {
|
||||
SetToLastContentOf(aContainer.get());
|
||||
}
|
||||
template <typename ContainerType>
|
||||
MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf(
|
||||
const ContainerType& aContainer,
|
||||
InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
|
||||
SelfType point;
|
||||
point.SetToLastContentOf(&aContainer);
|
||||
point.mInterlinePosition = aInterlinePosition;
|
||||
return point;
|
||||
}
|
||||
template <typename ContainerType, template <typename> typename StrongPtr>
|
||||
MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf(
|
||||
const StrongPtr<ContainerType>& aContainer,
|
||||
InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
|
||||
MOZ_ASSERT(aContainer.get());
|
||||
return AtLastContentOf(*aContainer.get(), aInterlinePosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* SetAfter() sets mChild to next sibling of aChild.
|
||||
*/
|
||||
@@ -728,6 +770,13 @@ class EditorDOMPointBase final {
|
||||
result.RewindOffset();
|
||||
return result;
|
||||
}
|
||||
template <typename EditorDOMPointType = SelfType>
|
||||
EditorDOMPointType PreviousPointOrParentPoint() const {
|
||||
if (IsStartOfContainer()) {
|
||||
return ParentPoint<EditorDOMPointType>();
|
||||
}
|
||||
return PreviousPoint<EditorDOMPointType>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear() makes the instance not point anywhere.
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/OwningNonNull.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/StaticPrefs_editor.h"
|
||||
#include "mozilla/TextComposition.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
@@ -552,45 +553,47 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
|
||||
|
||||
// attempt to transform any unneeded nbsp's into spaces after doing various
|
||||
// operations
|
||||
switch (GetTopLevelEditSubAction()) {
|
||||
case EditSubAction::eDeleteSelectedContent:
|
||||
if (TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case EditSubAction::eInsertText:
|
||||
case EditSubAction::eInsertTextComingFromIME:
|
||||
case EditSubAction::eInsertLineBreak:
|
||||
case EditSubAction::eInsertParagraphSeparator:
|
||||
case EditSubAction::ePasteHTMLContent:
|
||||
case EditSubAction::eInsertHTMLSource: {
|
||||
// Due to the replacement of white-spaces in
|
||||
// WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(),
|
||||
// selection ranges may be changed since DOM ranges track the DOM
|
||||
// mutation by themselves. However, we want to keep selection as-is.
|
||||
// Therefore, we should restore `Selection` after replacing
|
||||
// white-spaces.
|
||||
AutoSelectionRestorer restoreSelection(this);
|
||||
// TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
|
||||
// white-spaces with NPSPs and then, we'll replace them with ASCII
|
||||
// white-spaces here. We should avoid this overwriting things as
|
||||
// far as possible because replacing characters in text nodes
|
||||
// causes running mutation event listeners which are really
|
||||
// expensive.
|
||||
// Adjust end of composition string if there is composition string.
|
||||
auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
|
||||
if (!pointToAdjust.IsInContentNode()) {
|
||||
// Otherwise, adjust current selection start point.
|
||||
pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
|
||||
if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
const RefPtr<Element> editingHost =
|
||||
ComputeEditingHost(LimitInBodyElement::No);
|
||||
if (MOZ_UNLIKELY(!editingHost)) {
|
||||
break;
|
||||
const bool needToNormalizeWhiteSpaces = [&]() {
|
||||
switch (GetTopLevelEditSubAction()) {
|
||||
case EditSubAction::eDeleteSelectedContent:
|
||||
return !TopLevelEditSubActionDataRef().mDidNormalizeWhitespaces;
|
||||
case EditSubAction::eInsertText:
|
||||
case EditSubAction::eInsertTextComingFromIME:
|
||||
return !StaticPrefs::
|
||||
editor_white_space_normalization_blink_compatible();
|
||||
case EditSubAction::eInsertLineBreak:
|
||||
case EditSubAction::eInsertParagraphSeparator:
|
||||
case EditSubAction::ePasteHTMLContent:
|
||||
case EditSubAction::eInsertHTMLSource:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
if (needToNormalizeWhiteSpaces) {
|
||||
// Due to the replacement of white-spaces in
|
||||
// WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt(), selection
|
||||
// ranges may be changed since DOM ranges track the DOM mutation by
|
||||
// themselves. However, we want to keep selection as-is. Therefore, we
|
||||
// should restore `Selection` after replacing white-spaces.
|
||||
AutoSelectionRestorer restoreSelection(this);
|
||||
// TODO: Temporarily, WhiteSpaceVisibilityKeeper replaces ASCII
|
||||
// white-spaces with NPSPs and then, we'll replace them with ASCII
|
||||
// white-spaces here. We should avoid this overwriting things as
|
||||
// far as possible because replacing characters in text nodes
|
||||
// causes running mutation event listeners which are really
|
||||
// expensive.
|
||||
// Adjust end of composition string if there is composition string.
|
||||
auto pointToAdjust = GetLastIMESelectionEndPoint<EditorDOMPoint>();
|
||||
if (!pointToAdjust.IsInContentNode()) {
|
||||
// Otherwise, adjust current selection start point.
|
||||
pointToAdjust = GetFirstSelectionStartPoint<EditorDOMPoint>();
|
||||
if (NS_WARN_IF(!pointToAdjust.IsInContentNode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
if (const RefPtr<Element> editingHost =
|
||||
ComputeEditingHost(LimitInBodyElement::No)) {
|
||||
if (EditorUtils::IsEditableContent(
|
||||
*pointToAdjust.ContainerAs<nsIContent>(), EditorType::HTML)) {
|
||||
AutoTrackDOMPoint trackPointToAdjust(RangeUpdaterRef(),
|
||||
@@ -654,10 +657,7 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
|
||||
"WhiteSpaceVisibilityKeeper::NormalizeVisibleWhiteSpacesAt() "
|
||||
"failed, but ignored");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust selection for insert text, html paste, and delete actions if
|
||||
@@ -1236,7 +1236,8 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
||||
*this, aInsertionString,
|
||||
compositionEndPoint.IsSet()
|
||||
? EditorDOMRange(pointToInsert, compositionEndPoint)
|
||||
: EditorDOMRange(pointToInsert));
|
||||
: EditorDOMRange(pointToInsert),
|
||||
aPurpose);
|
||||
if (MOZ_UNLIKELY(replaceTextResult.isErr())) {
|
||||
NS_WARNING("WhiteSpaceVisibilityKeeper::ReplaceText() failed");
|
||||
return replaceTextResult.propagateErr();
|
||||
@@ -2948,13 +2949,86 @@ HTMLEditor::GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
|
||||
HTMLEditor::GetCharPointType(nextCharPoint));
|
||||
}
|
||||
|
||||
// static
|
||||
void HTMLEditor::NormalizeAllWhiteSpaceSequences(
|
||||
nsString& aResult, const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData, Linefeed aLinefeed) {
|
||||
MOZ_ASSERT(!aResult.IsEmpty());
|
||||
|
||||
const auto IsCollapsibleChar = [&](char16_t aChar) {
|
||||
if (aChar == HTMLEditUtils::kNewLine) {
|
||||
return aLinefeed == Linefeed::Preformatted;
|
||||
}
|
||||
return nsCRT::IsAsciiSpace(aChar);
|
||||
};
|
||||
const auto IsCollapsibleCharOrNBSP = [&](char16_t aChar) {
|
||||
return aChar == HTMLEditUtils::kNBSP || IsCollapsibleChar(aChar);
|
||||
};
|
||||
|
||||
const uint32_t length = aResult.Length();
|
||||
for (uint32_t offset = 0; offset < length; offset++) {
|
||||
const char16_t ch = aResult[offset];
|
||||
if (!IsCollapsibleCharOrNBSP(ch)) {
|
||||
continue;
|
||||
}
|
||||
const CharPointData previousCharData = [&]() {
|
||||
if (offset) {
|
||||
const char16_t prevChar = aResult[offset - 1u];
|
||||
return CharPointData::InSameTextNode(
|
||||
prevChar == HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar);
|
||||
}
|
||||
return aPreviousCharPointData;
|
||||
}();
|
||||
const uint32_t endOffset = [&]() {
|
||||
for (const uint32_t i : IntegerRange(offset, length)) {
|
||||
if (IsCollapsibleCharOrNBSP(aResult[i])) {
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
return length;
|
||||
}();
|
||||
const CharPointData nextCharData = [&]() {
|
||||
if (endOffset < length) {
|
||||
const char16_t nextChar = aResult[endOffset];
|
||||
return CharPointData::InSameTextNode(
|
||||
nextChar == HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar);
|
||||
}
|
||||
return aNextCharPointData;
|
||||
}();
|
||||
HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
|
||||
aResult, offset, endOffset - offset, previousCharData, nextCharData);
|
||||
offset = endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void HTMLEditor::GenerateWhiteSpaceSequence(
|
||||
nsAString& aResult, uint32_t aLength,
|
||||
nsString& aResult, uint32_t aLength,
|
||||
const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData) {
|
||||
MOZ_ASSERT(aResult.IsEmpty());
|
||||
MOZ_ASSERT(aLength);
|
||||
|
||||
aResult.SetLength(aLength);
|
||||
HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
|
||||
aResult, 0u, aLength, aPreviousCharPointData, aNextCharPointData);
|
||||
}
|
||||
|
||||
// static
|
||||
void HTMLEditor::ReplaceStringWithNormalizedWhiteSpaceSequence(
|
||||
nsString& aResult, uint32_t aOffset, uint32_t aLength,
|
||||
const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData) {
|
||||
MOZ_ASSERT(!aResult.IsEmpty());
|
||||
MOZ_ASSERT(aLength);
|
||||
MOZ_ASSERT(aOffset < aResult.Length());
|
||||
MOZ_ASSERT(aOffset + aLength <= aResult.Length());
|
||||
|
||||
// For now, this method does not assume that result will be append to
|
||||
// white-space sequence in the text node.
|
||||
MOZ_ASSERT(aPreviousCharPointData.AcrossTextNodeBoundary() ||
|
||||
@@ -2971,38 +3045,39 @@ void HTMLEditor::GenerateWhiteSpaceSequence(
|
||||
// without preformatted style. However, Chrome has same issue too.
|
||||
if (aPreviousCharPointData.Type() == CharPointType::VisibleChar &&
|
||||
aNextCharPointData.Type() == CharPointType::VisibleChar) {
|
||||
aResult.Assign(HTMLEditUtils::kSpace);
|
||||
aResult.SetCharAt(HTMLEditUtils::kSpace, aOffset);
|
||||
return;
|
||||
}
|
||||
// If it's start or end of text, put an NBSP.
|
||||
if (aPreviousCharPointData.Type() == CharPointType::TextEnd ||
|
||||
aNextCharPointData.Type() == CharPointType::TextEnd) {
|
||||
aResult.Assign(HTMLEditUtils::kNBSP);
|
||||
aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset);
|
||||
return;
|
||||
}
|
||||
// If the character is next to a preformatted linefeed, we need to put
|
||||
// an NBSP for avoiding collapsed into the linefeed.
|
||||
if (aPreviousCharPointData.Type() == CharPointType::PreformattedLineBreak ||
|
||||
aNextCharPointData.Type() == CharPointType::PreformattedLineBreak) {
|
||||
aResult.Assign(HTMLEditUtils::kNBSP);
|
||||
aResult.SetCharAt(HTMLEditUtils::kNBSP, aOffset);
|
||||
return;
|
||||
}
|
||||
// Now, the white-space will be inserted to a white-space sequence, but not
|
||||
// end of text. We can put an ASCII white-space only when both sides are
|
||||
// not ASCII white-spaces.
|
||||
aResult.Assign(
|
||||
aResult.SetCharAt(
|
||||
aPreviousCharPointData.Type() == CharPointType::ASCIIWhiteSpace ||
|
||||
aNextCharPointData.Type() == CharPointType::ASCIIWhiteSpace
|
||||
? HTMLEditUtils::kNBSP
|
||||
: HTMLEditUtils::kSpace);
|
||||
: HTMLEditUtils::kSpace,
|
||||
aOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate pairs of NBSP and ASCII white-space.
|
||||
aResult.SetLength(aLength);
|
||||
bool appendNBSP = true; // Basically, starts with an NBSP.
|
||||
char16_t* lastChar = aResult.EndWriting() - 1;
|
||||
for (char16_t* iter = aResult.BeginWriting(); iter != lastChar; iter++) {
|
||||
char16_t* const lastChar = aResult.BeginWriting() + aOffset + aLength - 1;
|
||||
for (char16_t* iter = aResult.BeginWriting() + aOffset; iter != lastChar;
|
||||
iter++) {
|
||||
*iter = appendNBSP ? HTMLEditUtils::kNBSP : HTMLEditUtils::kSpace;
|
||||
appendNBSP = !appendNBSP;
|
||||
}
|
||||
@@ -3023,10 +3098,142 @@ void HTMLEditor::GenerateWhiteSpaceSequence(
|
||||
: HTMLEditUtils::kSpace;
|
||||
}
|
||||
|
||||
HTMLEditor::NormalizedStringToInsertText
|
||||
HTMLEditor::NormalizeWhiteSpacesToInsertText(
|
||||
const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
|
||||
NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const {
|
||||
MOZ_ASSERT(aPointToInsert.IsSet());
|
||||
|
||||
// If white-spaces are preformatted, we don't need to normalize white-spaces.
|
||||
if (EditorUtils::IsWhiteSpacePreformatted(
|
||||
*aPointToInsert.ContainerAs<nsIContent>())) {
|
||||
return NormalizedStringToInsertText(aStringToInsert, aPointToInsert);
|
||||
}
|
||||
|
||||
Text* const textNode = aPointToInsert.GetContainerAs<Text>();
|
||||
const nsTextFragment* const textFragment =
|
||||
textNode ? &textNode->TextFragment() : nullptr;
|
||||
const bool isNewLineCollapsible = !EditorUtils::IsNewLinePreformatted(
|
||||
*aPointToInsert.ContainerAs<nsIContent>());
|
||||
|
||||
// We don't want to make invisible things visible with this normalization.
|
||||
// Therefore, we need to know whether there are invisible leading and/or
|
||||
// trailing white-spaces in the `Text`.
|
||||
|
||||
// Then, compute visible white-space length before/after the insertion point.
|
||||
// Note that these lengths may contain invisible white-spaces.
|
||||
const uint32_t precedingWhiteSpaceLength = [&]() {
|
||||
if (!textNode || !aNormalizeSurroundingWhiteSpaces ||
|
||||
aPointToInsert.IsStartOfContainer()) {
|
||||
return 0u;
|
||||
}
|
||||
const auto nonWhiteSpaceOffset =
|
||||
HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
|
||||
*textNode, aPointToInsert.Offset(),
|
||||
{HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
|
||||
const uint32_t firstWhiteSpaceOffset =
|
||||
nonWhiteSpaceOffset ? *nonWhiteSpaceOffset + 1u : 0u;
|
||||
return aPointToInsert.Offset() - firstWhiteSpaceOffset;
|
||||
}();
|
||||
const uint32_t followingWhiteSpaceLength = [&]() {
|
||||
if (!textNode || !aNormalizeSurroundingWhiteSpaces ||
|
||||
aPointToInsert.IsEndOfContainer()) {
|
||||
return 0u;
|
||||
}
|
||||
MOZ_ASSERT(textFragment);
|
||||
const auto nonWhiteSpaceOffset =
|
||||
HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
|
||||
*textNode, aPointToInsert.Offset(),
|
||||
{HTMLEditUtils::WalkTextOption::TreatNBSPsCollapsible});
|
||||
MOZ_ASSERT(nonWhiteSpaceOffset.valueOr(textFragment->GetLength()) >=
|
||||
aPointToInsert.Offset());
|
||||
return nonWhiteSpaceOffset.valueOr(textFragment->GetLength()) -
|
||||
aPointToInsert.Offset();
|
||||
}();
|
||||
|
||||
// Now, we can know invisible white-space length in precedingWhiteSpaceLength
|
||||
// and followingWhiteSpaceLength.
|
||||
const uint32_t precedingInvisibleWhiteSpaceCount =
|
||||
textNode
|
||||
? HTMLEditUtils::GetInvisibleWhiteSpaceCount(
|
||||
*textNode, aPointToInsert.Offset() - precedingWhiteSpaceLength,
|
||||
precedingWhiteSpaceLength)
|
||||
: 0u;
|
||||
MOZ_ASSERT(precedingWhiteSpaceLength >= precedingInvisibleWhiteSpaceCount);
|
||||
const uint32_t newPrecedingWhiteSpaceLength =
|
||||
precedingWhiteSpaceLength - precedingInvisibleWhiteSpaceCount;
|
||||
const uint32_t followingInvisibleSpaceCount =
|
||||
textNode
|
||||
? HTMLEditUtils::GetInvisibleWhiteSpaceCount(
|
||||
*textNode, aPointToInsert.Offset(), followingWhiteSpaceLength)
|
||||
: 0u;
|
||||
MOZ_ASSERT(followingWhiteSpaceLength >= followingInvisibleSpaceCount);
|
||||
const uint32_t newFollowingWhiteSpaceLength =
|
||||
followingWhiteSpaceLength - followingInvisibleSpaceCount;
|
||||
|
||||
const nsAutoString stringToInsertWithSurroundingSpaces =
|
||||
[&]() -> nsAutoString {
|
||||
if (!newPrecedingWhiteSpaceLength && !newFollowingWhiteSpaceLength) {
|
||||
return nsAutoString(aStringToInsert);
|
||||
}
|
||||
nsAutoString str;
|
||||
str.SetCapacity(aStringToInsert.Length() + newPrecedingWhiteSpaceLength +
|
||||
newFollowingWhiteSpaceLength);
|
||||
for ([[maybe_unused]] auto unused :
|
||||
IntegerRange(newPrecedingWhiteSpaceLength)) {
|
||||
str.Append(' ');
|
||||
}
|
||||
str.Append(aStringToInsert);
|
||||
for ([[maybe_unused]] auto unused :
|
||||
IntegerRange(newFollowingWhiteSpaceLength)) {
|
||||
str.Append(' ');
|
||||
}
|
||||
return str;
|
||||
}();
|
||||
|
||||
const uint32_t insertionOffsetInTextNode =
|
||||
aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u;
|
||||
NormalizedStringToInsertText result(
|
||||
stringToInsertWithSurroundingSpaces, insertionOffsetInTextNode,
|
||||
insertionOffsetInTextNode - precedingWhiteSpaceLength, // replace start
|
||||
precedingWhiteSpaceLength + followingWhiteSpaceLength, // replace length
|
||||
newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength);
|
||||
|
||||
// Now, normalize the inserting string.
|
||||
// Note that if the caller does not want to normalize the following
|
||||
// white-spaces, we always need to guarantee that neither the first character
|
||||
// nor the last character of the insertion string is not collapsible, i.e., if
|
||||
// each one is a collapsible white-space, we need to replace them an NBSP to
|
||||
// keep the visibility of the collapsible white-spaces. Therefore, if
|
||||
// aNormalizeSurroundingWhiteSpaces is "No", we need to treat the insertion
|
||||
// string is the only characters in the `Text`.
|
||||
HTMLEditor::NormalizeAllWhiteSpaceSequences(
|
||||
result.mNormalizedString,
|
||||
CharPointData::InSameTextNode(
|
||||
!textFragment || !result.mReplaceStartOffset ||
|
||||
!aNormalizeSurroundingWhiteSpaces
|
||||
? CharPointType::TextEnd
|
||||
: (textFragment->CharAt(result.mReplaceStartOffset - 1u) ==
|
||||
HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar)),
|
||||
CharPointData::InSameTextNode(
|
||||
!textFragment ||
|
||||
result.mReplaceEndOffset >= textFragment->GetLength() ||
|
||||
!aNormalizeSurroundingWhiteSpaces
|
||||
? CharPointType::TextEnd
|
||||
: (textFragment->CharAt(result.mReplaceEndOffset) ==
|
||||
HTMLEditUtils::kNewLine
|
||||
? CharPointType::PreformattedLineBreak
|
||||
: CharPointType::VisibleChar)),
|
||||
isNewLineCollapsible ? Linefeed::Collapsible : Linefeed::Preformatted);
|
||||
return result;
|
||||
}
|
||||
|
||||
void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
|
||||
EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
|
||||
nsAString& aNormalizedWhiteSpacesInStartNode,
|
||||
nsAString& aNormalizedWhiteSpacesInEndNode) const {
|
||||
nsString& aNormalizedWhiteSpacesInStartNode,
|
||||
nsString& aNormalizedWhiteSpacesInEndNode) const {
|
||||
MOZ_ASSERT(aStartToDelete.IsSetAndValid());
|
||||
MOZ_ASSERT(aEndToDelete.IsSetAndValid());
|
||||
MOZ_ASSERT(aStartToDelete.EqualsOrIsBefore(aEndToDelete));
|
||||
|
||||
@@ -1245,6 +1245,131 @@ Maybe<EditorLineBreakType> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
|
||||
return unnecessaryLineBreak;
|
||||
}
|
||||
|
||||
uint32_t HTMLEditUtils::GetFirstVisibleCharOffset(const Text& aText) {
|
||||
const nsTextFragment& textFragment = aText.TextFragment();
|
||||
if (!textFragment.GetLength() || !EditorRawDOMPointInText(&aText, 0u)
|
||||
.IsCharCollapsibleASCIISpaceOrNBSP()) {
|
||||
return 0u;
|
||||
}
|
||||
const WSScanResult previousThingOfText =
|
||||
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
|
||||
WSRunScanner::Scan::All, EditorRawDOMPoint(&aText),
|
||||
BlockInlineCheck::UseComputedDisplayStyle);
|
||||
if (!previousThingOfText.ReachedLineBoundary()) {
|
||||
return 0u;
|
||||
}
|
||||
return HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(aText, 0u)
|
||||
.valueOr(textFragment.GetLength());
|
||||
}
|
||||
|
||||
uint32_t HTMLEditUtils::GetOffsetAfterLastVisibleChar(const Text& aText) {
|
||||
const nsTextFragment& textFragment = aText.TextFragment();
|
||||
if (!textFragment.GetLength()) {
|
||||
return 0u;
|
||||
}
|
||||
if (!EditorRawDOMPointInText::AtLastContentOf(aText)
|
||||
.IsCharCollapsibleASCIISpaceOrNBSP()) {
|
||||
return textFragment.GetLength();
|
||||
}
|
||||
const WSScanResult nextThingOfText =
|
||||
WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
|
||||
WSRunScanner::Scan::All, EditorRawDOMPoint::After(aText),
|
||||
BlockInlineCheck::UseComputedDisplayStyle);
|
||||
if (!nextThingOfText.ReachedLineBoundary()) {
|
||||
return textFragment.GetLength();
|
||||
}
|
||||
const Maybe<uint32_t> lastNonCollapsibleCharOffset =
|
||||
HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
|
||||
aText, textFragment.GetLength());
|
||||
if (lastNonCollapsibleCharOffset.isNothing()) {
|
||||
return 0u;
|
||||
}
|
||||
if (*lastNonCollapsibleCharOffset == textFragment.GetLength() - 1u) {
|
||||
return textFragment.GetLength();
|
||||
}
|
||||
const uint32_t firstTrailingWhiteSpaceOffset =
|
||||
*lastNonCollapsibleCharOffset + 1u;
|
||||
MOZ_ASSERT(firstTrailingWhiteSpaceOffset < textFragment.GetLength());
|
||||
if (nextThingOfText.ReachedBlockBoundary()) {
|
||||
return firstTrailingWhiteSpaceOffset;
|
||||
}
|
||||
// If followed by <br> or preformatted line break, one white-space is
|
||||
// rendered.
|
||||
return firstTrailingWhiteSpaceOffset + 1u;
|
||||
}
|
||||
|
||||
uint32_t HTMLEditUtils::GetInvisibleWhiteSpaceCount(
|
||||
const Text& aText, uint32_t aOffset /* = 0u */,
|
||||
uint32_t aLength /* = UINT32_MAX */) {
|
||||
const nsTextFragment& textFragment = aText.TextFragment();
|
||||
if (!aLength || textFragment.GetLength() <= aOffset) {
|
||||
return 0u;
|
||||
}
|
||||
const uint32_t endOffset = static_cast<uint32_t>(
|
||||
std::min(static_cast<uint64_t>(aOffset) + aLength,
|
||||
static_cast<uint64_t>(textFragment.GetLength())));
|
||||
const auto firstVisibleOffset = [&]() -> uint32_t {
|
||||
// If the white-space sequence follows a preformatted linebreak, ASCII
|
||||
// spaces at start are invisible.
|
||||
if (aOffset &&
|
||||
textFragment.CharAt(aOffset - 1u) == HTMLEditUtils::kNewLine) {
|
||||
MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(aText));
|
||||
for (const uint32_t offset : IntegerRange(aOffset, endOffset)) {
|
||||
if (textFragment.CharAt(offset) == HTMLEditUtils::kNBSP) {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
return endOffset; // all white-spaces are invisible.
|
||||
}
|
||||
if (aOffset) {
|
||||
return aOffset - 1u;
|
||||
}
|
||||
return HTMLEditUtils::GetFirstVisibleCharOffset(aText);
|
||||
}();
|
||||
if (firstVisibleOffset >= endOffset) {
|
||||
return endOffset - aOffset; // All white-spaces are invisible.
|
||||
}
|
||||
const auto afterLastVisibleOffset = [&]() -> uint32_t {
|
||||
// If the white-spaces are followed by a preformatted line break, ASCII
|
||||
// spaces at end are invisible.
|
||||
if (endOffset < textFragment.GetLength() &&
|
||||
textFragment.CharAt(endOffset) == HTMLEditUtils::kNewLine) {
|
||||
MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(aText));
|
||||
for (const uint32_t offset : Reversed(IntegerRange(aOffset, endOffset))) {
|
||||
if (textFragment.CharAt(offset) == HTMLEditUtils::kNBSP) {
|
||||
return offset + 1u;
|
||||
}
|
||||
}
|
||||
return aOffset; // all white-spaces are invisible.
|
||||
}
|
||||
if (endOffset < textFragment.GetLength() - 1u) {
|
||||
return endOffset;
|
||||
}
|
||||
return HTMLEditUtils::GetOffsetAfterLastVisibleChar(aText);
|
||||
}();
|
||||
if (aOffset >= afterLastVisibleOffset) {
|
||||
return endOffset - aOffset; // All white-spaces are invisible.
|
||||
}
|
||||
enum class PrevChar { NotChar, Space, NBSP };
|
||||
PrevChar prevChar = PrevChar::NotChar;
|
||||
uint32_t invisibleChars = 0u;
|
||||
for (const uint32_t offset : IntegerRange(aOffset, endOffset)) {
|
||||
if (textFragment.CharAt(offset) == HTMLEditUtils::kNBSP) {
|
||||
prevChar = PrevChar::NBSP;
|
||||
continue;
|
||||
}
|
||||
MOZ_ASSERT(
|
||||
EditorRawDOMPointInText(&aText, offset).IsCharCollapsibleASCIISpace());
|
||||
if (offset < firstVisibleOffset || offset >= afterLastVisibleOffset ||
|
||||
// white-space after another white-space is invisible
|
||||
prevChar == PrevChar::Space) {
|
||||
invisibleChars++;
|
||||
}
|
||||
prevChar = PrevChar::Space;
|
||||
}
|
||||
return invisibleChars;
|
||||
}
|
||||
|
||||
bool HTMLEditUtils::IsEmptyNode(nsPresContext* aPresContext,
|
||||
const nsINode& aNode,
|
||||
const EmptyCheckOptions& aOptions /* = {} */,
|
||||
|
||||
@@ -2190,12 +2190,16 @@ class HTMLEditUtils final {
|
||||
* GetInclusiveNextNonCollapsibleCharOffset() returns offset of inclusive next
|
||||
* character which is not collapsible white-space characters.
|
||||
*/
|
||||
template <typename PT, typename CT>
|
||||
static Maybe<uint32_t> GetInclusiveNextNonCollapsibleCharOffset(
|
||||
const EditorDOMPointInText& aPoint,
|
||||
const EditorDOMPointBase<PT, CT>& aPoint,
|
||||
const WalkTextOptions& aWalkTextOptions = {}) {
|
||||
static_assert(std::is_same<PT, RefPtr<Text>>::value ||
|
||||
std::is_same<PT, Text*>::value);
|
||||
MOZ_ASSERT(aPoint.IsSetAndValid());
|
||||
return GetInclusiveNextNonCollapsibleCharOffset(
|
||||
*aPoint.ContainerAs<Text>(), aPoint.Offset(), aWalkTextOptions);
|
||||
*aPoint.template ContainerAs<Text>(), aPoint.Offset(),
|
||||
aWalkTextOptions);
|
||||
}
|
||||
static Maybe<uint32_t> GetInclusiveNextNonCollapsibleCharOffset(
|
||||
const Text& aTextNode, uint32_t aOffset,
|
||||
@@ -2245,9 +2249,12 @@ class HTMLEditUtils final {
|
||||
* position. I.e., the character at the position must be a collapsible
|
||||
* white-space.
|
||||
*/
|
||||
template <typename PT, typename CT>
|
||||
static uint32_t GetFirstWhiteSpaceOffsetCollapsedWith(
|
||||
const EditorDOMPointInText& aPoint,
|
||||
const EditorDOMPointBase<PT, CT>& aPoint,
|
||||
const WalkTextOptions& aWalkTextOptions = {}) {
|
||||
static_assert(std::is_same<PT, RefPtr<Text>>::value ||
|
||||
std::is_same<PT, Text*>::value);
|
||||
MOZ_ASSERT(aPoint.IsSetAndValid());
|
||||
MOZ_ASSERT(!aPoint.IsEndOfContainer());
|
||||
MOZ_ASSERT_IF(
|
||||
@@ -2257,7 +2264,8 @@ class HTMLEditUtils final {
|
||||
!aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible),
|
||||
aPoint.IsCharCollapsibleASCIISpace());
|
||||
return GetFirstWhiteSpaceOffsetCollapsedWith(
|
||||
*aPoint.ContainerAs<Text>(), aPoint.Offset(), aWalkTextOptions);
|
||||
*aPoint.template ContainerAs<Text>(), aPoint.Offset(),
|
||||
aWalkTextOptions);
|
||||
}
|
||||
static uint32_t GetFirstWhiteSpaceOffsetCollapsedWith(
|
||||
const Text& aTextNode, uint32_t aOffset,
|
||||
@@ -2331,6 +2339,41 @@ class HTMLEditUtils final {
|
||||
return EditorDOMPointType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first visible char offset in aText. I.e., this returns invisible
|
||||
* white-space length at start of aText. If there is no visible char in
|
||||
* aText, this returns the text data length.
|
||||
* Note that WSRunScanner::GetFirstVisiblePoint() may return different `Text`
|
||||
* node point, but this does not scan following `Text` nodes even if aText
|
||||
* is completely invisible.
|
||||
*/
|
||||
[[nodiscard]] static uint32_t GetFirstVisibleCharOffset(const Text& aText);
|
||||
|
||||
/**
|
||||
* Get next offset of the last visible char in aText. I.e., this returns
|
||||
* the first offset of invisible trailing white-spaces. If there is no
|
||||
* invisible trailing white-spaces in aText, this returns 0.
|
||||
* Note that WSRunScanner::GetAfterLastVisiblePoint() may return different
|
||||
* `Text` node point, but this does not scan preceding `Text` nodes even if
|
||||
* aText is completely invisible.
|
||||
*/
|
||||
[[nodiscard]] static uint32_t GetOffsetAfterLastVisibleChar(
|
||||
const Text& aText);
|
||||
|
||||
/**
|
||||
* Get the number of invisible white-spaces in the white-space sequence. Note
|
||||
* that some invisible white-spaces may be after the first visible character.
|
||||
* E.g., "SP SP NBSP SP SP NBSP". If this Text follows a block boundary, the
|
||||
* first SPs are the leading invisible white-spaces, and the first NBSP is the
|
||||
* first visible character. However, following 2 SPs are collapsed to one.
|
||||
* Therefore, one of them is counted as an invisible white-space.
|
||||
*
|
||||
* Note that this assumes that all white-spaces starting from aOffset and
|
||||
* ending by aOffset + aLength are collapsible white-spaces including NBSPs.
|
||||
*/
|
||||
[[nodiscard]] static uint32_t GetInvisibleWhiteSpaceCount(
|
||||
const Text& aText, uint32_t aOffset = 0u, uint32_t aLength = UINT32_MAX);
|
||||
|
||||
/**
|
||||
* GetGoodCaretPointFor() returns a good point to collapse `Selection`
|
||||
* after handling edit action with aDirectionAndAmount.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "HTMLEditor.h"
|
||||
#include "HTMLEditHelpers.h"
|
||||
#include "HTMLEditorInlines.h"
|
||||
#include "HTMLEditorNestedClasses.h"
|
||||
|
||||
#include "AutoClonedRangeArray.h"
|
||||
#include "AutoSelectionRestorer.h"
|
||||
@@ -4311,6 +4312,57 @@ Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction(
|
||||
transaction->SuggestPointToPutCaret<EditorDOMPoint>());
|
||||
}
|
||||
|
||||
Result<InsertTextResult, nsresult>
|
||||
HTMLEditor::InsertOrReplaceTextWithTransaction(
|
||||
const EditorDOMPoint& aPointToInsert,
|
||||
const NormalizedStringToInsertText& aData) {
|
||||
MOZ_ASSERT(aPointToInsert.IsInContentNodeAndValid());
|
||||
MOZ_ASSERT_IF(aData.ReplaceLength(), aPointToInsert.IsInTextNode());
|
||||
|
||||
Result<InsertTextResult, nsresult> insertTextResultOrError =
|
||||
!aData.ReplaceLength()
|
||||
? InsertTextWithTransaction(aData.mNormalizedString, aPointToInsert,
|
||||
InsertTextTo::SpecifiedPoint)
|
||||
: ReplaceTextWithTransaction(
|
||||
MOZ_KnownLive(*aPointToInsert.ContainerAs<Text>()),
|
||||
aData.mReplaceStartOffset, aData.ReplaceLength(),
|
||||
aData.mNormalizedString);
|
||||
if (MOZ_UNLIKELY(insertTextResultOrError.isErr())) {
|
||||
NS_WARNING(!aData.ReplaceLength()
|
||||
? "HTMLEditor::InsertTextWithTransaction() failed"
|
||||
: "HTMLEditor::ReplaceTextWithTransaction() failed");
|
||||
return insertTextResultOrError;
|
||||
}
|
||||
InsertTextResult insertTextResult = insertTextResultOrError.unwrap();
|
||||
if (!aData.ReplaceLength()) {
|
||||
auto pointToPutCaret = [&]() -> EditorDOMPoint {
|
||||
return insertTextResult.HasCaretPointSuggestion()
|
||||
? insertTextResult.UnwrapCaretPoint()
|
||||
: insertTextResult.EndOfInsertedTextRef();
|
||||
}();
|
||||
return InsertTextResult(std::move(insertTextResult),
|
||||
std::move(pointToPutCaret));
|
||||
}
|
||||
insertTextResult.IgnoreCaretPointSuggestion();
|
||||
Text* const insertedTextNode =
|
||||
insertTextResult.EndOfInsertedTextRef().GetContainerAs<Text>();
|
||||
if (NS_WARN_IF(!insertedTextNode) ||
|
||||
NS_WARN_IF(!insertedTextNode->IsInComposedDoc()) ||
|
||||
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*insertedTextNode))) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
const uint32_t expectedEndOffset = aData.EndOffsetOfInsertedText();
|
||||
if (NS_WARN_IF(expectedEndOffset > insertedTextNode->TextDataLength())) {
|
||||
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
|
||||
}
|
||||
// We need to return end point of the insertion string instead of end of
|
||||
// replaced following white-spaces.
|
||||
EditorDOMPoint endOfNewString(insertedTextNode, expectedEndOffset);
|
||||
EditorDOMPoint pointToPutCaret = endOfNewString;
|
||||
return InsertTextResult(std::move(endOfNewString),
|
||||
CaretPoint(std::move(pointToPutCaret)));
|
||||
}
|
||||
|
||||
Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction(
|
||||
const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert,
|
||||
InsertTextTo aInsertTextTo) {
|
||||
|
||||
@@ -844,6 +844,18 @@ class HTMLEditor final : public EditorBase,
|
||||
uint32_t aLength,
|
||||
const nsAString& aStringToInsert);
|
||||
|
||||
struct NormalizedStringToInsertText;
|
||||
|
||||
/**
|
||||
* Insert text to aPointToInsert or replace text in the range stored by aData
|
||||
* in the text node specified by aPointToInsert with the normalized string
|
||||
* stored by aData. So, aPointToInsert must be in a `Text` node if
|
||||
* aData.ReplaceLength() is not 0.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertTextResult, nsresult>
|
||||
InsertOrReplaceTextWithTransaction(const EditorDOMPoint& aPointToInsert,
|
||||
const NormalizedStringToInsertText& aData);
|
||||
|
||||
/**
|
||||
* Insert aStringToInsert to aPointToInsert. If the point is not editable,
|
||||
* this returns error.
|
||||
@@ -2204,6 +2216,21 @@ class HTMLEditor final : public EditorBase,
|
||||
TreatEmptyTextNodes aTreatEmptyTextNodes,
|
||||
DeleteDirection aDeleteDirection, const Element& aEditingHost);
|
||||
|
||||
enum class NormalizeSurroundingWhiteSpaces : bool { No, Yes };
|
||||
friend constexpr bool operator!(NormalizeSurroundingWhiteSpaces aValue) {
|
||||
return !static_cast<bool>(aValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a normalized string. If aPointToInsert is in a `Text` node,
|
||||
* this returns a range in the `Text` to replace surrounding white-spaces at
|
||||
* aPointToInsert with the normalized string if white-spaces are collapsible
|
||||
* and aNormalizeSurroundingWhiteSpaces is "Yes".
|
||||
*/
|
||||
NormalizedStringToInsertText NormalizeWhiteSpacesToInsertText(
|
||||
const EditorDOMPoint& aPointToInsert, const nsAString& aStringToInsert,
|
||||
NormalizeSurroundingWhiteSpaces aNormalizeSurroundingWhiteSpaces) const;
|
||||
|
||||
/**
|
||||
* ExtendRangeToDeleteWithNormalizingWhiteSpaces() is a helper method of
|
||||
* DeleteTextAndNormalizeSurroundingWhiteSpaces(). This expands
|
||||
@@ -2227,8 +2254,8 @@ class HTMLEditor final : public EditorBase,
|
||||
*/
|
||||
void ExtendRangeToDeleteWithNormalizingWhiteSpaces(
|
||||
EditorDOMPointInText& aStartToDelete, EditorDOMPointInText& aEndToDelete,
|
||||
nsAString& aNormalizedWhiteSpacesInStartNode,
|
||||
nsAString& aNormalizedWhiteSpacesInEndNode) const;
|
||||
nsString& aNormalizedWhiteSpacesInStartNode,
|
||||
nsString& aNormalizedWhiteSpacesInEndNode) const;
|
||||
|
||||
/**
|
||||
* CharPointType let the following helper methods of
|
||||
@@ -2265,20 +2292,16 @@ class HTMLEditor final : public EditorBase,
|
||||
*/
|
||||
class MOZ_STACK_CLASS CharPointData final {
|
||||
public:
|
||||
CharPointData() = delete;
|
||||
|
||||
static CharPointData InDifferentTextNode(CharPointType aCharPointType) {
|
||||
CharPointData result;
|
||||
result.mIsInDifferentTextNode = true;
|
||||
result.mType = aCharPointType;
|
||||
return result;
|
||||
return {aCharPointType, true};
|
||||
}
|
||||
static CharPointData InSameTextNode(CharPointType aCharPointType) {
|
||||
CharPointData result;
|
||||
// Let's mark this as in different text node if given one indicates
|
||||
// that there is end of text because it means that adjacent content
|
||||
// from point of text node view is another element.
|
||||
result.mIsInDifferentTextNode = aCharPointType == CharPointType::TextEnd;
|
||||
result.mType = aCharPointType;
|
||||
return result;
|
||||
return {aCharPointType, aCharPointType == CharPointType::TextEnd};
|
||||
}
|
||||
|
||||
bool AcrossTextNodeBoundary() const { return mIsInDifferentTextNode; }
|
||||
@@ -2289,7 +2312,8 @@ class HTMLEditor final : public EditorBase,
|
||||
CharPointType Type() const { return mType; }
|
||||
|
||||
private:
|
||||
CharPointData() = default;
|
||||
CharPointData(CharPointType aType, bool aIsInDifferentTextNode)
|
||||
: mType(aType), mIsInDifferentTextNode(aIsInDifferentTextNode) {}
|
||||
|
||||
CharPointType mType;
|
||||
bool mIsInDifferentTextNode;
|
||||
@@ -2306,12 +2330,24 @@ class HTMLEditor final : public EditorBase,
|
||||
CharPointData GetInclusiveNextCharPointDataForNormalizingWhiteSpaces(
|
||||
const EditorDOMPointInText& aPoint) const;
|
||||
|
||||
enum class Linefeed : bool { Collapsible, Preformatted };
|
||||
|
||||
/**
|
||||
* Normalize all white-spaces in aResult. aPreviousCharPointData is used only
|
||||
* when the first character of aResult is an ASCII space or an NBSP.
|
||||
* aNextCharPointData is used only when the last character of aResult is an
|
||||
* ASCII space or an NBSP.
|
||||
*/
|
||||
static void NormalizeAllWhiteSpaceSequences(
|
||||
nsString& aResult, const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData, Linefeed aLinefeed);
|
||||
|
||||
/**
|
||||
* GenerateWhiteSpaceSequence() generates white-space sequence which won't
|
||||
* be collapsed.
|
||||
*
|
||||
* @param aResult [out] White space sequence which won't be
|
||||
* collapsed, but wrapable.
|
||||
* collapsed, but wrappable.
|
||||
* @param aLength Length of generating white-space sequence.
|
||||
* Must be 1 or larger.
|
||||
* @param aPreviousCharPointData
|
||||
@@ -2323,11 +2359,20 @@ class HTMLEditor final : public EditorBase,
|
||||
* different text nodes white-space.
|
||||
* @param aNextCharPointData Specify the next char point where it'll be
|
||||
* inserted. Same as aPreviousCharPointData,
|
||||
* this must node indidate white-space in same
|
||||
* this must node indicate white-space in same
|
||||
* text node.
|
||||
*/
|
||||
static void GenerateWhiteSpaceSequence(
|
||||
nsAString& aResult, uint32_t aLength,
|
||||
nsString& aResult, uint32_t aLength,
|
||||
const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData);
|
||||
|
||||
/**
|
||||
* Replace characters starting from aOffset in aResult with normalized
|
||||
* white-space sequence.
|
||||
*/
|
||||
static void ReplaceStringWithNormalizedWhiteSpaceSequence(
|
||||
nsString& aResult, uint32_t aOffset, uint32_t aLength,
|
||||
const CharPointData& aPreviousCharPointData,
|
||||
const CharPointData& aNextCharPointData);
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/OwningNonNull.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/dom/Text.h"
|
||||
#include "nsTextFragment.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -522,6 +524,168 @@ class MOZ_STACK_CLASS HTMLEditor::AutoListElementCreator final {
|
||||
const nsAutoString mBulletType;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* NormalizedStringToInsertText stores normalized insertion string with
|
||||
* normalized surrounding white-spaces if the insertion point is surrounded by
|
||||
* collapsible white-spaces. For deleting invisible (collapsed) white-spaces,
|
||||
* this also stores the replace range and new white-space length before and
|
||||
* after the inserting text.
|
||||
******************************************************************************/
|
||||
|
||||
struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final {
|
||||
NormalizedStringToInsertText(
|
||||
const nsAString& aStringToInsertWithoutSurroundingWhiteSpaces,
|
||||
const EditorDOMPoint& aPointToInsert)
|
||||
: mNormalizedString(aStringToInsertWithoutSurroundingWhiteSpaces),
|
||||
mReplaceStartOffset(
|
||||
aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u),
|
||||
mReplaceEndOffset(mReplaceStartOffset) {
|
||||
MOZ_ASSERT(aStringToInsertWithoutSurroundingWhiteSpaces.Length() ==
|
||||
InsertingTextLength());
|
||||
}
|
||||
|
||||
NormalizedStringToInsertText(
|
||||
const nsAString& aStringToInsertWithSurroundingWhiteSpaces,
|
||||
uint32_t aInsertOffset, uint32_t aReplaceStartOffset,
|
||||
uint32_t aReplaceLength,
|
||||
uint32_t aNewPrecedingWhiteSpaceLengthBeforeInsertionString,
|
||||
uint32_t aNewFollowingWhiteSpaceLengthAfterInsertionString)
|
||||
: mNormalizedString(aStringToInsertWithSurroundingWhiteSpaces),
|
||||
mReplaceStartOffset(aReplaceStartOffset),
|
||||
mReplaceEndOffset(mReplaceStartOffset + aReplaceLength),
|
||||
mReplaceLengthBefore(aInsertOffset - mReplaceStartOffset),
|
||||
mReplaceLengthAfter(aReplaceLength - mReplaceLengthBefore),
|
||||
mNewLengthBefore(aNewPrecedingWhiteSpaceLengthBeforeInsertionString),
|
||||
mNewLengthAfter(aNewFollowingWhiteSpaceLengthAfterInsertionString) {
|
||||
MOZ_ASSERT(aReplaceStartOffset <= aInsertOffset);
|
||||
MOZ_ASSERT(aReplaceStartOffset + aReplaceLength >= aInsertOffset);
|
||||
MOZ_ASSERT(aNewPrecedingWhiteSpaceLengthBeforeInsertionString +
|
||||
aNewFollowingWhiteSpaceLengthAfterInsertionString <
|
||||
mNormalizedString.Length());
|
||||
MOZ_ASSERT(mReplaceLengthBefore + mReplaceLengthAfter == ReplaceLength());
|
||||
MOZ_ASSERT(mReplaceLengthBefore >= mNewLengthBefore);
|
||||
MOZ_ASSERT(mReplaceLengthAfter >= mNewLengthAfter);
|
||||
}
|
||||
|
||||
NormalizedStringToInsertText GetMinimizedData(const Text& aText) const {
|
||||
if (mNormalizedString.IsEmpty() || !ReplaceLength()) {
|
||||
return *this;
|
||||
}
|
||||
const nsTextFragment& textFragment = aText.TextFragment();
|
||||
const uint32_t minimizedReplaceStart = [&]() {
|
||||
const auto firstDiffCharOffset =
|
||||
mNewLengthBefore ? textFragment.FindFirstDifferentCharOffset(
|
||||
PrecedingWhiteSpaces(), mReplaceStartOffset)
|
||||
: nsTextFragment::kNotFound;
|
||||
if (firstDiffCharOffset == nsTextFragment::kNotFound) {
|
||||
return
|
||||
// We don't need to insert new normalized white-spaces before the
|
||||
// inserting string,
|
||||
(mReplaceStartOffset + mReplaceLengthBefore)
|
||||
// but keep extending the replacing range for deleting invisible
|
||||
// white-spaces.
|
||||
- DeletingPrecedingInvisibleWhiteSpaces();
|
||||
}
|
||||
return firstDiffCharOffset;
|
||||
}();
|
||||
const uint32_t minimizedReplaceEnd = [&]() {
|
||||
const auto lastDiffCharOffset =
|
||||
mNewLengthAfter ? textFragment.RFindFirstDifferentCharOffset(
|
||||
FollowingWhiteSpaces(), mReplaceEndOffset)
|
||||
: nsTextFragment::kNotFound;
|
||||
if (lastDiffCharOffset == nsTextFragment::kNotFound) {
|
||||
return
|
||||
// We don't need to insert new normalized white-spaces after the
|
||||
// inserting string,
|
||||
(mReplaceEndOffset - mReplaceLengthAfter)
|
||||
// but keep extending the replacing range for deleting invisible
|
||||
// white-spaces.
|
||||
+ DeletingFollowingInvisibleWhiteSpaces();
|
||||
}
|
||||
return lastDiffCharOffset + 1u;
|
||||
}();
|
||||
if (minimizedReplaceStart == mReplaceStartOffset &&
|
||||
minimizedReplaceEnd == mReplaceEndOffset) {
|
||||
return *this;
|
||||
}
|
||||
const uint32_t newPrecedingWhiteSpaceLength =
|
||||
mNewLengthBefore - (minimizedReplaceStart - mReplaceStartOffset);
|
||||
const uint32_t newFollowingWhiteSpaceLength =
|
||||
mNewLengthAfter - (mReplaceEndOffset - minimizedReplaceEnd);
|
||||
return NormalizedStringToInsertText(
|
||||
Substring(mNormalizedString,
|
||||
mNewLengthBefore - newPrecedingWhiteSpaceLength,
|
||||
mNormalizedString.Length() -
|
||||
(mNewLengthBefore - newPrecedingWhiteSpaceLength) -
|
||||
(mNewLengthAfter - newFollowingWhiteSpaceLength)),
|
||||
OffsetToInsertText(), minimizedReplaceStart,
|
||||
minimizedReplaceEnd - minimizedReplaceStart,
|
||||
newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return offset to insert the given text.
|
||||
*/
|
||||
[[nodiscard]] uint32_t OffsetToInsertText() const {
|
||||
return mReplaceStartOffset + mReplaceLengthBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return inserting text length not containing the surrounding white-spaces.
|
||||
*/
|
||||
[[nodiscard]] uint32_t InsertingTextLength() const {
|
||||
return mNormalizedString.Length() - mNewLengthBefore - mNewLengthAfter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return end offset of inserted string after replacing the text with
|
||||
* mNormalizedString.
|
||||
*/
|
||||
[[nodiscard]] uint32_t EndOffsetOfInsertedText() const {
|
||||
return OffsetToInsertText() + InsertingTextLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length to replace with mNormalizedString. The result means that
|
||||
* it's the length of surrounding white-spaces at the insertion point.
|
||||
*/
|
||||
[[nodiscard]] uint32_t ReplaceLength() const {
|
||||
return mReplaceEndOffset - mReplaceStartOffset;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t DeletingPrecedingInvisibleWhiteSpaces() const {
|
||||
return mReplaceLengthBefore - mNewLengthBefore;
|
||||
}
|
||||
[[nodiscard]] uint32_t DeletingFollowingInvisibleWhiteSpaces() const {
|
||||
return mReplaceLengthAfter - mNewLengthAfter;
|
||||
}
|
||||
|
||||
[[nodiscard]] nsDependentSubstring PrecedingWhiteSpaces() const {
|
||||
return Substring(mNormalizedString, 0u, mNewLengthBefore);
|
||||
}
|
||||
[[nodiscard]] nsDependentSubstring FollowingWhiteSpaces() const {
|
||||
return Substring(mNormalizedString,
|
||||
mNormalizedString.Length() - mNewLengthAfter);
|
||||
}
|
||||
|
||||
// Normalizes string which should be inserted.
|
||||
nsAutoString mNormalizedString;
|
||||
// Start offset in the `Text` to replace.
|
||||
const uint32_t mReplaceStartOffset;
|
||||
// End offset in the `Text` to replace.
|
||||
const uint32_t mReplaceEndOffset;
|
||||
// If it needs to replace preceding and/or following white-spaces, these
|
||||
// members store the length of white-spaces which should be replaced
|
||||
// before/after the insertion point.
|
||||
const uint32_t mReplaceLengthBefore = 0u;
|
||||
const uint32_t mReplaceLengthAfter = 0u;
|
||||
// If it needs to replace preceding and/or following white-spaces, these
|
||||
// members store the new length of white-spaces before/after the insertion
|
||||
// string.
|
||||
const uint32_t mNewLengthBefore = 0u;
|
||||
const uint32_t mNewLengthAfter = 0u;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef HTMLEditorNestedClasses_h
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -225,6 +225,8 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
InsertLineBreak(LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor,
|
||||
const EditorDOMPoint& aPointToInsert);
|
||||
|
||||
using InsertTextFor = EditorBase::InsertTextFor;
|
||||
|
||||
/**
|
||||
* Insert aStringToInsert to aPointToInsert and makes any needed adjustments
|
||||
* to white-spaces around the insertion point.
|
||||
@@ -243,7 +245,7 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
return WhiteSpaceVisibilityKeeper::
|
||||
InsertTextOrInsertOrUpdateCompositionString(
|
||||
aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert),
|
||||
aInsertTextTo, TextIsCompositionString::No);
|
||||
aInsertTextTo, InsertTextFor::NormalText);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,13 +262,14 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
* collapsed and indicate the insertion point.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
|
||||
InsertOrUpdateCompositionString(
|
||||
HTMLEditor& aHTMLEditor, const nsAString& aCompositionString,
|
||||
const EditorDOMRange& aCompositionStringRange) {
|
||||
InsertOrUpdateCompositionString(HTMLEditor& aHTMLEditor,
|
||||
const nsAString& aCompositionString,
|
||||
const EditorDOMRange& aCompositionStringRange,
|
||||
InsertTextFor aPurpose) {
|
||||
MOZ_ASSERT(EditorBase::InsertingTextForComposition(aPurpose));
|
||||
return InsertTextOrInsertOrUpdateCompositionString(
|
||||
aHTMLEditor, aCompositionString, aCompositionStringRange,
|
||||
HTMLEditor::InsertTextTo::ExistingTextNodeIfAvailable,
|
||||
TextIsCompositionString::Yes);
|
||||
HTMLEditor::InsertTextTo::ExistingTextNodeIfAvailable, aPurpose);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -344,7 +347,20 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
|
||||
const nsAString& aReplaceString);
|
||||
|
||||
enum class TextIsCompositionString : bool { No, Yes };
|
||||
/**
|
||||
* Delete leading or trailing invisible white-spaces around block boundaries
|
||||
* or collapsed white-spaces in a white-space sequence if aPoint is around
|
||||
* them.
|
||||
*
|
||||
* @param aHTMLEditor The HTMLEditor.
|
||||
* @param aPoint Point must be in an editable content node.
|
||||
* @return If deleted some invisible white-spaces, returns the
|
||||
* removed point.
|
||||
* If this does nothing, returns unset point.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
|
||||
EnsureNoInvisibleWhiteSpaces(HTMLEditor& aHTMLEditor,
|
||||
const EditorDOMPoint& aPoint);
|
||||
|
||||
/**
|
||||
* Insert aStringToInsert to aRangeToBeReplaced.StartRef() with normalizing
|
||||
@@ -361,12 +377,14 @@ class WhiteSpaceVisibilityKeeper final {
|
||||
* @param aInsertTextTo Whether forcibly creates a new `Text` node in
|
||||
* specific condition or use existing `Text` if
|
||||
* available.
|
||||
* @param aPurpose Whether it's handling normal text input or
|
||||
* updating composition.
|
||||
*/
|
||||
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
|
||||
InsertTextOrInsertOrUpdateCompositionString(
|
||||
HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
|
||||
const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo,
|
||||
TextIsCompositionString aTextIsCompositionString);
|
||||
InsertTextFor aPurpose);
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
Reference in New Issue
Block a user