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,75 +1281,52 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
||||||
|
|
||||||
// don't change my selection in subtransactions
|
// don't change my selection in subtransactions
|
||||||
AutoTransactionsConserveSelection dontChangeMySelection(*this);
|
AutoTransactionsConserveSelection dontChangeMySelection(*this);
|
||||||
int32_t pos = 0;
|
|
||||||
constexpr auto newlineStr = NS_LITERAL_STRING_FROM_CSTRING(LFSTR);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert);
|
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
|
// for efficiency, break out the pre case separately. This is because
|
||||||
// its a lot cheaper to search the input string for only newlines than
|
// its a lot cheaper to search the input string for only newlines than
|
||||||
// it is to search for both tabs and newlines.
|
// it is to search for both tabs and newlines.
|
||||||
if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
|
if (!isWhiteSpaceCollapsible || IsPlaintextMailComposer()) {
|
||||||
while (pos != -1 &&
|
uint32_t nextOffset = 0;
|
||||||
pos < AssertedCast<int32_t>(aInsertionString.Length())) {
|
while (nextOffset < aInsertionString.Length()) {
|
||||||
int32_t oldPos = pos;
|
const uint32_t lineStartOffset = nextOffset;
|
||||||
int32_t subStrLen;
|
const int32_t inclusiveNextLinefeedOffset =
|
||||||
pos = aInsertionString.FindChar(nsCRT::LF, oldPos);
|
aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
|
||||||
|
const uint32_t lineLength =
|
||||||
if (pos != -1) {
|
inclusiveNextLinefeedOffset != -1
|
||||||
subStrLen = pos - oldPos;
|
? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
|
||||||
// if first char is newline, then use just it
|
lineStartOffset
|
||||||
if (!subStrLen) {
|
: aInsertionString.Length() - lineStartOffset;
|
||||||
subStrLen = 1;
|
if (lineLength) {
|
||||||
}
|
// lineText does not include the preformatted line break.
|
||||||
} else {
|
const nsDependentSubstring lineText(aInsertionString, lineStartOffset,
|
||||||
subStrLen = aInsertionString.Length() - oldPos;
|
lineLength);
|
||||||
pos = aInsertionString.Length();
|
|
||||||
}
|
|
||||||
|
|
||||||
nsDependentSubstring subStr(aInsertionString, oldPos, subStrLen);
|
|
||||||
|
|
||||||
// is it a return?
|
|
||||||
if (subStr.Equals(newlineStr)) {
|
|
||||||
Result<CreateElementResult, nsresult> insertBRElementResult =
|
|
||||||
InsertBRElement(WithTransaction::Yes, currentPoint);
|
|
||||||
if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
|
|
||||||
NS_WARNING(
|
|
||||||
"HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
|
|
||||||
return insertBRElementResult.propagateErr();
|
|
||||||
}
|
|
||||||
CreateElementResult unwrappedInsertBRElementResult =
|
|
||||||
insertBRElementResult.unwrap();
|
|
||||||
// We don't want to update selection here because we've blocked
|
|
||||||
// InsertNodeTransaction updating selection with
|
|
||||||
// dontChangeMySelection.
|
|
||||||
unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
|
|
||||||
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
|
|
||||||
|
|
||||||
pos++;
|
|
||||||
RefPtr<Element> brElement =
|
|
||||||
unwrappedInsertBRElementResult.UnwrapNewNode();
|
|
||||||
if (brElement->GetNextSibling()) {
|
|
||||||
pointToInsert.Set(brElement->GetNextSibling());
|
|
||||||
} else {
|
|
||||||
pointToInsert.SetToEndOf(currentPoint.GetContainer());
|
|
||||||
}
|
|
||||||
// XXX In most cases, pointToInsert and currentPoint are same here.
|
|
||||||
// But if the <br> element has been moved to different point by
|
|
||||||
// mutation observer, those points become different.
|
|
||||||
currentPoint.SetAfter(brElement);
|
|
||||||
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 =
|
Result<InsertTextResult, nsresult> insertTextResult =
|
||||||
InsertTextWithTransaction(
|
InsertTextWithTransaction(
|
||||||
*document, subStr, currentPoint,
|
*document, lineText, currentPoint,
|
||||||
InsertTextTo::ExistingTextNodeIfAvailable);
|
GetInsertTextTo(inclusiveNextLinefeedOffset,
|
||||||
|
lineStartOffset));
|
||||||
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||||
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
|
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
|
||||||
return insertTextResult.propagateErr();
|
return insertTextResult.propagateErr();
|
||||||
|
|
@ -1364,104 +1341,75 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
||||||
} else {
|
} else {
|
||||||
pointToInsert = currentPoint;
|
pointToInsert = currentPoint;
|
||||||
}
|
}
|
||||||
|
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())) {
|
||||||
|
NS_WARNING(
|
||||||
|
"HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
|
||||||
|
return insertBRElementResult.propagateErr();
|
||||||
|
}
|
||||||
|
CreateElementResult unwrappedInsertBRElementResult =
|
||||||
|
insertBRElementResult.unwrap();
|
||||||
|
// We don't want to update selection here because we've blocked
|
||||||
|
// InsertNodeTransaction updating selection with
|
||||||
|
// dontChangeMySelection.
|
||||||
|
unwrappedInsertBRElementResult.IgnoreCaretPointSuggestion();
|
||||||
|
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
|
||||||
|
|
||||||
|
nextOffset = inclusiveNextLinefeedOffset + 1;
|
||||||
|
RefPtr<Element> brElement =
|
||||||
|
unwrappedInsertBRElementResult.UnwrapNewNode();
|
||||||
|
if (brElement->GetNextSibling()) {
|
||||||
|
pointToInsert.Set(brElement->GetNextSibling());
|
||||||
|
} else {
|
||||||
|
pointToInsert.SetToEndOf(currentPoint.GetContainer());
|
||||||
|
}
|
||||||
|
// XXX In most cases, pointToInsert and currentPoint are same here.
|
||||||
|
// But if the <br> element has been moved to different point by
|
||||||
|
// mutation observer, those points become different.
|
||||||
|
currentPoint.SetAfter(brElement);
|
||||||
|
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 {
|
} else {
|
||||||
constexpr auto tabStr = u"\t"_ns;
|
uint32_t nextOffset = 0;
|
||||||
constexpr auto spacesStr = u" "_ns;
|
while (nextOffset < aInsertionString.Length()) {
|
||||||
nsAutoString insertionString(aInsertionString); // For FindCharInSet().
|
const uint32_t lineStartOffset = nextOffset;
|
||||||
while (pos != -1 &&
|
const int32_t inclusiveNextLinefeedOffset =
|
||||||
pos < AssertedCast<int32_t>(insertionString.Length())) {
|
aInsertionString.FindChar(nsCRT::LF, lineStartOffset);
|
||||||
int32_t oldPos = pos;
|
const uint32_t lineLength =
|
||||||
int32_t subStrLen;
|
inclusiveNextLinefeedOffset != -1
|
||||||
pos = insertionString.FindCharInSet(u"\t\n", oldPos);
|
? static_cast<uint32_t>(inclusiveNextLinefeedOffset) -
|
||||||
|
lineStartOffset
|
||||||
|
: aInsertionString.Length() - lineStartOffset;
|
||||||
|
|
||||||
if (pos != -1) {
|
if (lineLength) {
|
||||||
subStrLen = pos - oldPos;
|
auto insertTextResult =
|
||||||
// if first char is newline, then use just it
|
[&]() MOZ_CAN_RUN_SCRIPT -> Result<InsertTextResult, nsresult> {
|
||||||
if (!subStrLen) {
|
// lineText does not include the preformatted line break.
|
||||||
subStrLen = 1;
|
const nsDependentSubstring lineText(aInsertionString,
|
||||||
}
|
lineStartOffset, lineLength);
|
||||||
} else {
|
if (!lineText.Contains(u'\t')) {
|
||||||
subStrLen = insertionString.Length() - oldPos;
|
return WhiteSpaceVisibilityKeeper::InsertText(
|
||||||
pos = insertionString.Length();
|
*this, lineText, currentPoint,
|
||||||
}
|
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
|
||||||
|
*editingHost);
|
||||||
nsDependentSubstring subStr(insertionString, oldPos, subStrLen);
|
}
|
||||||
|
nsAutoString formattedLineText(lineText);
|
||||||
// is it a tab?
|
formattedLineText.ReplaceSubstring(u"\t"_ns, u" "_ns);
|
||||||
if (subStr.Equals(tabStr)) {
|
return WhiteSpaceVisibilityKeeper::InsertText(
|
||||||
Result<InsertTextResult, nsresult> insertTextResult =
|
*this, formattedLineText, currentPoint,
|
||||||
WhiteSpaceVisibilityKeeper::InsertText(
|
GetInsertTextTo(inclusiveNextLinefeedOffset, lineStartOffset),
|
||||||
*this, spacesStr, currentPoint,
|
*editingHost);
|
||||||
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();
|
|
||||||
pos++;
|
|
||||||
if (insertTextResult.inspect().Handled()) {
|
|
||||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
|
||||||
.EndOfInsertedTextRef()
|
|
||||||
.To<EditorDOMPoint>();
|
|
||||||
MOZ_ASSERT(pointToInsert.IsSet());
|
|
||||||
} else {
|
|
||||||
pointToInsert = currentPoint;
|
|
||||||
MOZ_ASSERT(pointToInsert.IsSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// is it a return?
|
|
||||||
else if (subStr.Equals(newlineStr)) {
|
|
||||||
Result<CreateElementResult, nsresult> insertBRElementResult =
|
|
||||||
WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
|
|
||||||
*editingHost);
|
|
||||||
if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
|
|
||||||
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
|
|
||||||
return insertBRElementResult.propagateErr();
|
|
||||||
}
|
|
||||||
CreateElementResult unwrappedInsertBRElementResult =
|
|
||||||
insertBRElementResult.unwrap();
|
|
||||||
// TODO: Some methods called for handling non-preformatted text use
|
|
||||||
// ComputeEditingHost(). Therefore, they depend on the latest
|
|
||||||
// selection. So we cannot skip updating selection here.
|
|
||||||
nsresult rv = unwrappedInsertBRElementResult.SuggestCaretPointTo(
|
|
||||||
*this, {SuggestCaret::OnlyIfHasSuggestion,
|
|
||||||
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
|
||||||
SuggestCaret::AndIgnoreTrivialError});
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
|
|
||||||
return Err(rv);
|
|
||||||
}
|
|
||||||
NS_WARNING_ASSERTION(
|
|
||||||
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
|
||||||
"CreateElementResult::SuggestCaretPointTo() failed, but ignored");
|
|
||||||
pos++;
|
|
||||||
RefPtr<Element> newBRElement =
|
|
||||||
unwrappedInsertBRElementResult.UnwrapNewNode();
|
|
||||||
MOZ_DIAGNOSTIC_ASSERT(newBRElement);
|
|
||||||
if (newBRElement->GetNextSibling()) {
|
|
||||||
pointToInsert.Set(newBRElement->GetNextSibling());
|
|
||||||
} else {
|
|
||||||
pointToInsert.SetToEndOf(currentPoint.GetContainer());
|
|
||||||
}
|
|
||||||
currentPoint.SetAfter(newBRElement);
|
|
||||||
NS_WARNING_ASSERTION(currentPoint.IsSet(),
|
|
||||||
"Failed to set after the new <br> element");
|
|
||||||
// XXX If the newBRElement has been moved or removed by mutation
|
|
||||||
// observer, we hit this assert. We need to check if
|
|
||||||
// newBRElement is in expected point, though, we must have
|
|
||||||
// a lot of same bugs...
|
|
||||||
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())) {
|
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
|
||||||
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
|
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertText() failed");
|
||||||
return insertTextResult.propagateErr();
|
return insertTextResult.propagateErr();
|
||||||
|
|
@ -1473,12 +1421,56 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
||||||
pointToInsert = currentPoint = insertTextResult.unwrap()
|
pointToInsert = currentPoint = insertTextResult.unwrap()
|
||||||
.EndOfInsertedTextRef()
|
.EndOfInsertedTextRef()
|
||||||
.To<EditorDOMPoint>();
|
.To<EditorDOMPoint>();
|
||||||
MOZ_ASSERT(pointToInsert.IsSet());
|
|
||||||
} else {
|
} else {
|
||||||
pointToInsert = currentPoint;
|
pointToInsert = currentPoint;
|
||||||
MOZ_ASSERT(pointToInsert.IsSet());
|
}
|
||||||
|
if (inclusiveNextLinefeedOffset < 0) {
|
||||||
|
break; // We reached the last line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<CreateElementResult, nsresult> insertBRElementResult =
|
||||||
|
WhiteSpaceVisibilityKeeper::InsertBRElement(*this, currentPoint,
|
||||||
|
*editingHost);
|
||||||
|
if (MOZ_UNLIKELY(insertBRElementResult.isErr())) {
|
||||||
|
NS_WARNING("WhiteSpaceVisibilityKeeper::InsertBRElement() failed");
|
||||||
|
return insertBRElementResult.propagateErr();
|
||||||
|
}
|
||||||
|
CreateElementResult unwrappedInsertBRElementResult =
|
||||||
|
insertBRElementResult.unwrap();
|
||||||
|
// TODO: Some methods called for handling non-preformatted text use
|
||||||
|
// ComputeEditingHost(). Therefore, they depend on the latest
|
||||||
|
// selection. So we cannot skip updating selection here.
|
||||||
|
nsresult rv = unwrappedInsertBRElementResult.SuggestCaretPointTo(
|
||||||
|
*this, {SuggestCaret::OnlyIfHasSuggestion,
|
||||||
|
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
||||||
|
SuggestCaret::AndIgnoreTrivialError});
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
NS_WARNING("CreateElementResult::SuggestCaretPointTo() failed");
|
||||||
|
return Err(rv);
|
||||||
|
}
|
||||||
|
NS_WARNING_ASSERTION(
|
||||||
|
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
||||||
|
"CreateElementResult::SuggestCaretPointTo() failed, but ignored");
|
||||||
|
nextOffset = inclusiveNextLinefeedOffset + 1;
|
||||||
|
RefPtr<Element> newBRElement =
|
||||||
|
unwrappedInsertBRElementResult.UnwrapNewNode();
|
||||||
|
MOZ_DIAGNOSTIC_ASSERT(newBRElement);
|
||||||
|
if (newBRElement->GetNextSibling()) {
|
||||||
|
pointToInsert.Set(newBRElement->GetNextSibling());
|
||||||
|
} else {
|
||||||
|
pointToInsert.SetToEndOf(currentPoint.GetContainer());
|
||||||
|
}
|
||||||
|
currentPoint.SetAfter(newBRElement);
|
||||||
|
NS_WARNING_ASSERTION(currentPoint.IsSet(),
|
||||||
|
"Failed to set after the new <br> element");
|
||||||
|
// XXX If the newBRElement has been moved or removed by mutation
|
||||||
|
// observer, we hit this assert. We need to check if
|
||||||
|
// newBRElement is in expected point, though, we must have
|
||||||
|
// a lot of same bugs...
|
||||||
|
NS_WARNING_ASSERTION(
|
||||||
|
currentPoint == pointToInsert,
|
||||||
|
"Perhaps, newBRElement has been moved or removed unexpectedly");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue