Bug 1940653 - Make HTMLEditor::HandlerInsertText insert one Text when linefeeds are preformatted r=m_kato

The form of <https://discussions.apple.com/> handles newly inserted `Text` nodes
asynchronously after pasted.  Then, the text will be wrapped into `<p>`
elements.  However, the handler does not assume that multiple and consecutive
`Text` nodes are inserted by a pasting.  Therefore, they fail handling our
pasted text which was split to each line and each preformatted linefeed.

Chrome puts only one `Text` node at pasting multiline plaintext and following
this behavior fixes the issue in the form.  Therefore, we should follow the
behavior at least for now.

Differential Revision: https://phabricator.services.mozilla.com/D233619
This commit is contained in:
Masayuki Nakano
2025-01-10 12:14:58 +00:00
parent 0bc88f5a1d
commit fa19f63af9
4 changed files with 183 additions and 68 deletions

View File

@@ -1357,67 +1357,98 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
// 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()) {
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 {
pointToInsert = currentPoint;
}
if (inclusiveNextLinefeedOffset < 0) {
break; // We reached the last line
}
if (*lineBreakType == LineBreakType::Linefeed) {
// Both Chrome and us inserts a preformatted linefeed with its own
// `Text` node in various cases. However, when inserting multiline
// text, we should insert a `Text` because Chrome does so and the
// comment field in https://discussions.apple.com/ handles the new
// `Text` to split each line into a paragraph. At that time, it's
// not assumed that inserted text is split at every linefeed.
MOZ_ASSERT(*lineBreakType == LineBreakType::Linefeed);
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(
*document, aInsertionString, currentPoint,
InsertTextTo::ExistingTextNodeIfAvailable);
if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("HTMLEditor::InsertTextWithTransaction() failed");
return insertTextResult.propagateErr();
}
MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
InsertLineBreak(WithTransaction::Yes, *lineBreakType, currentPoint);
if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak("
"WithTransaction::Yes, %s) failed",
ToString(*lineBreakType).c_str())
.get());
return insertLineBreakResultOrError.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;
}
CreateLineBreakResult insertLineBreakResult =
insertLineBreakResultOrError.unwrap();
// We don't want to update selection here because we've blocked
// InsertNodeTransaction updating selection with
// dontChangeMySelection.
insertLineBreakResult.IgnoreCaretPointSuggestion();
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
} else {
MOZ_ASSERT(*lineBreakType == LineBreakType::BRElement);
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 {
pointToInsert = currentPoint;
}
if (inclusiveNextLinefeedOffset < 0) {
break; // We reached the last line
}
}
MOZ_ASSERT(inclusiveNextLinefeedOffset >= 0);
Result<CreateLineBreakResult, nsresult> insertLineBreakResultOrError =
InsertLineBreak(WithTransaction::Yes, *lineBreakType,
currentPoint);
if (MOZ_UNLIKELY(insertLineBreakResultOrError.isErr())) {
NS_WARNING(nsPrintfCString("HTMLEditor::InsertLineBreak("
"WithTransaction::Yes, %s) failed",
ToString(*lineBreakType).c_str())
.get());
return insertLineBreakResultOrError.propagateErr();
}
CreateLineBreakResult insertLineBreakResult =
insertLineBreakResultOrError.unwrap();
// We don't want to update selection here because we've blocked
// InsertNodeTransaction updating selection with
// dontChangeMySelection.
insertLineBreakResult.IgnoreCaretPointSuggestion();
MOZ_ASSERT(!AllowsTransactionsToChangeSelection());
nextOffset = inclusiveNextLinefeedOffset + 1;
pointToInsert = insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
nextOffset = inclusiveNextLinefeedOffset + 1;
pointToInsert =
insertLineBreakResult.AfterLineBreak<EditorDOMPoint>();
currentPoint.SetAfter(&insertLineBreakResult.LineBreakContentRef());
if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc()) ||
NS_WARN_IF(!currentPoint.IsSetAndValidInComposedDoc())) {
return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
}
} else {