forked from mirrors/gecko-dev
Bug 1893351 - part 2: Make HTMLEditor::HandleInsertText stop inserting text into existing text nodes if it's a middle line of inserting text r=m_kato
When 2nd or later line, the method inserts one-line text to start of a `Text` node following `<br>` which is inserted by the method. Then, splits the `Text` node to insert another `<br>`. This creates a lot of unnecessary `SplitNodeTransaction`s and that causes a lot of copying memory operation to set the data of the right `Text` node. This patch makes the method creates a `Text` node when inserting a middle line of inserting text. Therefore, `SplitNodeTransaction` is created at most one (to split a `Text` node if the caller wants to insert a text middle of it). Depends on D211697 Differential Revision: https://phabricator.services.mozilla.com/D211698
This commit is contained in:
parent
08de69e4f7
commit
d8f302fe60
1 changed files with 148 additions and 156 deletions
|
|
@ -1281,37 +1281,71 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
|
||||
// don't change my selection in subtransactions
|
||||
AutoTransactionsConserveSelection dontChangeMySelection(*this);
|
||||
int32_t pos = 0;
|
||||
constexpr auto newlineStr = NS_LITERAL_STRING_FROM_CSTRING(LFSTR);
|
||||
|
||||
{
|
||||
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
|
||||
|
||||
const auto GetInsertTextTo = [](int32_t aInclusiveNextLinefeedOffset,
|
||||
uint32_t aLineStartOffset) {
|
||||
if (aInclusiveNextLinefeedOffset > 0) {
|
||||
return aLineStartOffset > 0
|
||||
// If we'll insert a <br> and we're inserting 2nd or later
|
||||
// line, we should always create new `Text` since it'll be
|
||||
// between 2 <br> elements.
|
||||
? InsertTextTo::AlwaysCreateNewTextNode
|
||||
// If we'll insert a <br> and we're inserting first line,
|
||||
// we should append text to preceding text node, but
|
||||
// we don't want to insert it to a a following text node
|
||||
// because of avoiding to split the `Text`.
|
||||
: InsertTextTo::ExistingTextNodeIfAvailableAndNotStart;
|
||||
}
|
||||
// If we're inserting the last line, the text should be inserted to
|
||||
// start of the following `Text` if there is or middle of the `Text`
|
||||
// at insertion position if we're inserting only the line.
|
||||
return InsertTextTo::ExistingTextNodeIfAvailable;
|
||||
};
|
||||
|
||||
// for efficiency, break out the pre case separately. This is because
|
||||
// its a lot cheaper to search the input string for only newlines than
|
||||
// it is to search for both tabs and newlines.
|
||||
if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
|
||||
while (pos != -1 &&
|
||||
pos < AssertedCast<int32_t>(aInsertionString.Length())) {
|
||||
int32_t oldPos = pos;
|
||||
int32_t subStrLen;
|
||||
pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
|
||||
|
||||
if (pos != -1) {
|
||||
subStrLen = pos - oldPos;
|
||||
// if first char is newline, then use just it
|
||||
if (!subStrLen) {
|
||||
subStrLen = 1;
|
||||
uint32_t nextOffset = 0;
|
||||
while (nextOffset < aInsertionString.Length()) {
|
||||
const uint32_t lineStartOffset = nextOffset;
|
||||
const int32_t inclusiveNextLinefeedOffset =
|
||||
aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
|
||||
const uint32_t lineLength =
|
||||
inclusiveNextLinefeedOffset != -1
|
||||
? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
|
||||
lineStartOffset
|
||||
: aInsertionString.Length() - lineStartOffset;
|
||||
if (lineLength) {
|
||||
// lineText does not include the preformatted line break.
|
||||
const nsDependentSubstring lineText(aInsertionString, lineStartOffset,
|
||||
lineLength);
|
||||
Result<InsertTextResult, nsresult> insertTextResult =
|
||||
InsertTextWithTransaction(
|
||||
*document, lineText, currentPoint,
|
||||
GetInsertTextTo(inclusiveNextLinefeedOffset,
|
||||
lineStartOffset));
|
||||
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
|
||||
return insertTextResult.propagateErr();
|
||||
}
|
||||
// Ignore the caret suggestion because of `dontChangeMySelection`
|
||||
// above.
|
||||
insertTextResult.inspect().IgnoreCaretPointSuggestion();
|
||||
if (insertTextResult.inspect().Handled()) {
|
||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
||||
.EndOfInsertedTextRef()
|
||||
.To<EditorDOMPoint>();
|
||||
} else {
|
||||
subStrLen = aInsertionString.Length() - oldPos;
|
||||
pos = aInsertionString.Length();
|
||||
pointToInsert = currentPoint;
|
||||
}
|
||||
|
||||
nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
|
||||
|
||||
// is it a return?
|
||||
if (subStr.Equals(newlineStr)) {
|
||||
if (inclusiveNextLinefeedOffset < 0) {
|
||||
break; // We reached the last line
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
|
||||
Result<CreateElementResult, nsresult> insertBRElementResult =
|
||||
InsertBRElement(WithTransaction::Yes, currentPoint);
|
||||
if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
|
||||
|
|
@ -1327,7 +1361,7 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
|
||||
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
|
||||
|
||||
pos++;
|
||||
nextOffset = inclusiveNextLinefeedOffset + 1;
|
||||
RefPtr<Element> brElement =
|
||||
unwrappedInsertBRElementResult.UnwrapNewNode();
|
||||
if (brElement->GetNextSibling()) {
|
||||
|
|
@ -1342,59 +1376,40 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
NS_WARNING_ASSERTION(currentPoint.IsSet(),
|
||||
"Failed to set after the <br> element");
|
||||
NS_WARNING_ASSERTION(currentPoint == pointToInsert,
|
||||
"Perhaps, <br> element position has been moved "
|
||||
"to different point "
|
||||
"by mutation observer");
|
||||
} else {
|
||||
Result<InsertTextResult, nsresult> insertTextResult =
|
||||
InsertTextWithTransaction(
|
||||
*document, subStr, currentPoint,
|
||||
InsertTextTo::ExistingTextNodeIfAvailable);
|
||||
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
|
||||
return insertTextResult.propagateErr();
|
||||
}
|
||||
// Ignore the caret suggestion because of `dontChangeMySelection`
|
||||
// above.
|
||||
insertTextResult.inspect().IgnoreCaretPointSuggestion();
|
||||
if (insertTextResult.inspect().Handled()) {
|
||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
||||
.EndOfInsertedTextRef()
|
||||
.To<EditorDOMPoint>();
|
||||
} else {
|
||||
pointToInsert = currentPoint;
|
||||
}
|
||||
}
|
||||
"Perhaps, <br> element position has been moved to "
|
||||
"different point by mutation observer");
|
||||
}
|
||||
} else {
|
||||
constexpr auto tabStr = u"\t"_ns;
|
||||
constexpr auto spacesStr = u" "_ns;
|
||||
nsAutoString insertionString(aInsertionString); // For FindCharInSet().
|
||||
while (pos != -1 &&
|
||||
pos < AssertedCast<int32_t>(insertionString.Length())) {
|
||||
int32_t oldPos = pos;
|
||||
int32_t subStrLen;
|
||||
pos = insertionString.FindCharInSet(u"\t\n", oldPos);
|
||||
uint32_t nextOffset = 0;
|
||||
while (nextOffset < aInsertionString.Length()) {
|
||||
const uint32_t lineStartOffset = nextOffset;
|
||||
const int32_t inclusiveNextLinefeedOffset =
|
||||
aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
|
||||
const uint32_t lineLength =
|
||||
inclusiveNextLinefeedOffset != -1
|
||||
? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
|
||||
lineStartOffset
|
||||
: aInsertionString.Length() - lineStartOffset;
|
||||
|
||||
if (pos != -1) {
|
||||
subStrLen = pos - oldPos;
|
||||
// if first char is newline, then use just it
|
||||
if (!subStrLen) {
|
||||
subStrLen = 1;
|
||||
if (lineLength) {
|
||||
auto insertTextResult =
|
||||
[&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
|
||||
// lineText does not include the preformatted line break.
|
||||
const nsDependentSubstring lineText(aInsertionString,
|
||||
lineStartOffset, lineLength);
|
||||
if (!lineText.Contains(u'\t')) {
|
||||
return WhiteSpaceVisibilityKeeper::InsertText(
|
||||
*this, lineText, currentPoint,
|
||||
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
|
||||
*editingHost);
|
||||
}
|
||||
} else {
|
||||
subStrLen = insertionString.Length() - oldPos;
|
||||
pos = insertionString.Length();
|
||||
}
|
||||
|
||||
nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
|
||||
|
||||
// is it a tab?
|
||||
if (subStr.Equals(tabStr)) {
|
||||
Result<InsertTextResult, nsresult> insertTextResult =
|
||||
WhiteSpaceVisibilityKeeper::InsertText(
|
||||
*this, spacesStr, currentPoint,
|
||||
InsertTextTo::ExistingTextNodeIfAvailable, *editingHost);
|
||||
nsAutoString formattedLineText(lineText);
|
||||
formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns);
|
||||
return WhiteSpaceVisibilityKeeper::InsertText(
|
||||
*this, formattedLineText, currentPoint,
|
||||
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
|
||||
*editingHost);
|
||||
}();
|
||||
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
|
||||
return insertTextResult.propagateErr();
|
||||
|
|
@ -1402,19 +1417,18 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
// Ignore the caret suggestion because of `dontChangeMySelection`
|
||||
// above.
|
||||
insertTextResult.inspect().IgnoreCaretPointSuggestion();
|
||||
pos++;
|
||||
if (insertTextResult.inspect().Handled()) {
|
||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
||||
.EndOfInsertedTextRef()
|
||||
.To<EditorDOMPoint>();
|
||||
MOZ_ASSERT(pointToInsert.IsSet());
|
||||
} else {
|
||||
pointToInsert = currentPoint;
|
||||
MOZ_ASSERT(pointToInsert.IsSet());
|
||||
}
|
||||
if (inclusiveNextLinefeedOffset < 0) {
|
||||
break; // We reached the last line
|
||||
}
|
||||
}
|
||||
// is it a return?
|
||||
else if (subStr.Equals(newlineStr)) {
|
||||
|
||||
Result<CreateElementResult, nsresult> insertBRElementResult =
|
||||
WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
|
||||
*editingHost);
|
||||
|
|
@ -1438,7 +1452,7 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
NS_WARNING_ASSERTION(
|
||||
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
||||
"CreateElementResult::SuggestCaretPointTo() failed, but ignored");
|
||||
pos++;
|
||||
nextOffset = inclusiveNextLinefeedOffset + 1;
|
||||
RefPtr<Element> newBRElement =
|
||||
unwrappedInsertBRElementResult.UnwrapNewNode();
|
||||
MOZ_DIAGNOSTIC_ASSERT(newBRElement);
|
||||
|
|
@ -1457,28 +1471,6 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||
NS_WARNING_ASSERTION(
|
||||
currentPoint == pointToInsert,
|
||||
"Perhaps, newBRElement has been moved or removed unexpectedly");
|
||||
} else {
|
||||
Result<InsertTextResult, nsresult> insertTextResult =
|
||||
WhiteSpaceVisibilityKeeper::InsertText(
|
||||
*this, subStr, currentPoint,
|
||||
InsertTextTo::ExistingTextNodeIfAvailable, *editingHost);
|
||||
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
|
||||
return insertTextResult.propagateErr();
|
||||
}
|
||||
// Ignore the caret suggestion because of `dontChangeMySelection`
|
||||
// above.
|
||||
insertTextResult.inspect().IgnoreCaretPointSuggestion();
|
||||
if (insertTextResult.inspect().Handled()) {
|
||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
||||
.EndOfInsertedTextRef()
|
||||
.To<EditorDOMPoint>();
|
||||
MOZ_ASSERT(pointToInsert.IsSet());
|
||||
} else {
|
||||
pointToInsert = currentPoint;
|
||||
MOZ_ASSERT(pointToInsert.IsSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue