Bug 1961521 - Make HTMLEditor::HandleInsertText() collapse Selection properly after deleting composition r=m_kato
When inserting text or deleting text is for updating composition string, `InsertTextWithTransaction()` does not suggest caret position because in the most cases, `CompositionTransaction` updating `Selection` properly. However, if it's deleting existing composition (i.e., replacing the composition with empty string), `HTMLEditor::HandleInsertText()` doesn't use `CompositionTransaction` to update composition. Instead, it uses the suggested caret position from `InsertTextWithTransaction()` even though it's always unset. Therefore, if the composition string follows some collapsible white-spaces and they are replaced, `Selection` is automatically moved to the start position of the replaced range and `HTMLEditor::HandleInsertText()` does not maintain the caret position. Therefore, this patch make it collapse `Selection` to end of the inserted text where the composition string was. Differential Revision: https://phabricator.services.mozilla.com/D246087
This commit is contained in:
@@ -1199,6 +1199,10 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||||||
}
|
}
|
||||||
InsertTextResult insertEmptyTextResult =
|
InsertTextResult insertEmptyTextResult =
|
||||||
insertEmptyTextResultOrError.unwrap();
|
insertEmptyTextResultOrError.unwrap();
|
||||||
|
// InsertTextWithTransaction() doesn not suggest caret position if it's
|
||||||
|
// called for IME composition. However, for the safety, let's ignore the
|
||||||
|
// caret position explicitly.
|
||||||
|
insertEmptyTextResult.IgnoreCaretPointSuggestion();
|
||||||
nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
|
nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(
|
||||||
insertEmptyTextResult.EndOfInsertedTextRef());
|
insertEmptyTextResult.EndOfInsertedTextRef());
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
@@ -1236,21 +1240,20 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
|
|||||||
if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
|
if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) {
|
||||||
NS_WARNING(
|
NS_WARNING(
|
||||||
"HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed");
|
"HTMLEditor::InsertPaddingBRElementIfNeeded(eNoStrip) failed");
|
||||||
insertEmptyTextResult.IgnoreCaretPointSuggestion();
|
|
||||||
return insertPaddingBRElementResultOrError.propagateErr();
|
return insertPaddingBRElementResultOrError.propagateErr();
|
||||||
}
|
}
|
||||||
insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion();
|
insertPaddingBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion();
|
||||||
rv = insertEmptyTextResult.SuggestCaretPointTo(
|
// Then, collapse caret after the empty text inserted position, i.e.,
|
||||||
*this, {SuggestCaret::OnlyIfHasSuggestion,
|
// whether the removed composition string was.
|
||||||
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
|
if (AllowsTransactionsToChangeSelection()) {
|
||||||
SuggestCaret::AndIgnoreTrivialError});
|
nsresult rv = CollapseSelectionTo(endOfInsertedText);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
|
||||||
NS_WARNING("CaretPoint::SuggestCaretPointTo() failed");
|
return Err(rv);
|
||||||
return Err(rv);
|
}
|
||||||
|
NS_WARNING_ASSERTION(
|
||||||
|
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
||||||
|
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
||||||
}
|
}
|
||||||
NS_WARNING_ASSERTION(
|
|
||||||
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
|
|
||||||
"CaretPoint::SuggestCaretPointTo() failed, but ignored");
|
|
||||||
return EditActionResult::HandledResult();
|
return EditActionResult::HandledResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11404,6 +11404,37 @@ function runPaddingLineBreakTest() {
|
|||||||
contenteditable.innerHTML = "";
|
contenteditable.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runCancelCompositionAfterCollapseWhileSpace() {
|
||||||
|
const selection = windowOfContenteditable.getSelection();
|
||||||
|
contenteditable.innerHTML = "<p>A </p>";
|
||||||
|
contenteditable.focus();
|
||||||
|
selection.collapse(
|
||||||
|
contenteditable.querySelector("[contenteditable] > p").firstChild,
|
||||||
|
"A ".length
|
||||||
|
);
|
||||||
|
const description = "runCancelCompositionAfterCollapseWhileSpace";
|
||||||
|
synthesizeCompositionChange({
|
||||||
|
composition: {string: "B", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
|
||||||
|
});
|
||||||
|
is(
|
||||||
|
contenteditable.innerHTML.replaceAll(" ", " "),
|
||||||
|
"<p>A B</p>",
|
||||||
|
`${description}: Composing string "B" should be inserted after the collapsible white-space`
|
||||||
|
);
|
||||||
|
synthesizeComposition({type: "compositioncommit", data: ""});
|
||||||
|
is(
|
||||||
|
contenteditable.innerHTML,
|
||||||
|
kNewWhiteSpaceNormalizerEnabled ? "<p>A </p>" : "<p>A <br></p>",
|
||||||
|
`${description}: Canceling the composition should keep the preceding collapsible white-space visible`
|
||||||
|
);
|
||||||
|
synthesizeKey("B");
|
||||||
|
is(
|
||||||
|
contenteditable.innerHTML,
|
||||||
|
"<p>A B</p>",
|
||||||
|
`${description}: Typing "B" after canceling composition should cause inserting it after the collpsible white-space`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function runTest()
|
async function runTest()
|
||||||
{
|
{
|
||||||
await SpecialPowers.pushPrefEnv({
|
await SpecialPowers.pushPrefEnv({
|
||||||
@@ -11455,6 +11486,7 @@ async function runTest()
|
|||||||
runBug1571375Test();
|
runBug1571375Test();
|
||||||
runBug1675313Test();
|
runBug1675313Test();
|
||||||
runCommitCompositionWithSpaceKey();
|
runCommitCompositionWithSpaceKey();
|
||||||
|
runCancelCompositionAfterCollapseWhileSpace();
|
||||||
runCompositionWithSelectionChange();
|
runCompositionWithSelectionChange();
|
||||||
runCompositionWithClick();
|
runCompositionWithClick();
|
||||||
runForceCommitTest();
|
runForceCommitTest();
|
||||||
|
|||||||
Reference in New Issue
Block a user