Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: dom/base/test/chrome.toml
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test text change notification when inserting text containing line breaks</title>
<script>
"use strict";
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
await SpecialPowers.pushPrefEnv({
set: [["test.ime_content_observer.assert_invalid_cache", true]],
});
const editingHost = document.querySelector("div[contenteditable]");
const TIP = SpecialPowers.Cc["@mozilla.org/text-input-processor;1"].createInstance(
SpecialPowers.Ci.nsITextInputProcessor
);
let textChanges = [];
function recTextChanges(aTIP, aNotification) {
switch (aNotification.type) {
case "request-to-commit":
aTIP.commitComposition();
break;
case "request-to-cancel":
aTIP.cancelComposition();
break;
case "notify-text-change":
textChanges.push({
offset: aNotification.offset,
removedLength: aNotification.removedLength,
addedLength: aNotification.addedLength
});
break;
}
return true;
}
editingHost.focus();
// Wait for initializing the HTMLEditor.
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
TIP.beginInputTransactionForTests(window, recTextChanges);
function stringifyTextChanges(aChanges) {
if (!aChanges.length) {
return "[]";
}
function stringifyTextChange(aChange) {
return `{ offset: ${aChange.offset}, removedLength: ${aChange.removedLength}, addedLength: ${aChange.addedLength} }`;
}
let result = "";
for (const change of aChanges) {
if (result == "") {
result = "[ ";
} else {
result += ", ";
}
result += stringifyTextChange(change);
}
return result + " ]";
}
for (const whiteSpace of ["normal", "pre"]) {
editingHost.style.whiteSpace = whiteSpace;
for (const insertionOffset of [0, 1, 2, 3]) {
for (const insertingText of ["\nabc\ndef",
"abc\ndef",
"abc\ndef\n",
"\n\nabc",
"abc\n\ndef"]) {
editingHost.textContent = "XYZ";
getSelection().collapse(editingHost.firstChild, insertionOffset);
await new Promise(resolve => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
});
textChanges = [];
document.execCommand("insertText", false, insertingText);
await new Promise(resolve => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
});
const middleOfText = insertionOffset > 0 && insertionOffset < "XYZ".length;
const expectSplit = middleOfText && whiteSpace == "normal";
const firstLineLength = insertingText.indexOf("\n");
const rightTextLength = expectSplit
// First, the `Text` is split if the linefeed is not preformatted.
// At this time, the right half is removed from the `Text` and then,
// added it with new `Text`. Therefore, there is non-zero removed
// length.
? "XYZ".length - insertionOffset
// However, if the linefeeds are preformatted or
// collapsed at start or end of a `Text`, we don't split the `Text`.
: 0;
is(
stringifyTextChanges(textChanges),
stringifyTextChanges([
{
offset: insertionOffset,
removedLength: rightTextLength,
addedLength: insertingText.length + rightTextLength,
},
]),
`white-space:${whiteSpace}: inserting text is "${
insertingText.replaceAll("\n", "\\n")
}" to offset ${insertionOffset} in the Text`
);
textChanges = [];
document.execCommand("undo");
await new Promise(resolve => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
});
const expectJoin = expectSplit;
is(
stringifyTextChanges(textChanges),
stringifyTextChanges([
{
// At undoing, the offset is always same as the inserted offset because we keep the
// left node at joining nodes so that simply removing the inserted data.
offset: insertionOffset,
// Removed length should be the length of inserted text and the text length of the
// right node at split because its data is once removed to move into the left `Text`.
removedLength: insertingText.length + rightTextLength,
// Added length should be the moved data length from the right node at split.
addedLength: rightTextLength,
},
]),
`white-space:${whiteSpace}: undoing inserted text is "${
insertingText.replaceAll("\n", "\\n")
}" to offset ${insertionOffset} in the Text`
);
}
}
}
SimpleTest.finish();
});
</script>
</head>
<body>
<div contenteditable></div>
</body>
</html>