зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1758420 - part 1: Let `TextComposition` know where is changed in storing text node r=m_kato
`IMEContentObserver` observes the text node change which contains the current composition string. Therefore, it can let `TextComposition` know where is updated by web apps and adjust offset and length in the text node. Differential Revision: https://phabricator.services.mozilla.com/D141193
This commit is contained in:
Родитель
c6aaf4f760
Коммит
5c4fed5f9d
|
@ -792,6 +792,15 @@ void IMEContentObserver::CharacterDataChanged(
|
|||
// node.
|
||||
}
|
||||
|
||||
// Let TextComposition have a change to update composition string range in
|
||||
// the text node if the change is caused by the web apps.
|
||||
if (mWidget && !IsEditorHandlingEventForComposition()) {
|
||||
if (RefPtr<TextComposition> composition =
|
||||
IMEStateManager::GetTextCompositionFor(mWidget)) {
|
||||
composition->OnCharacterDataChanged(*aContent->AsText(), aInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!NeedsTextChangeNotification() ||
|
||||
!nsContentUtils::IsInSameAnonymousTree(mRootContent, aContent)) {
|
||||
return;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "IMEStateManager.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIMutationObserver.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/EditorBase.h"
|
||||
|
@ -87,6 +88,82 @@ void TextComposition::Destroy() {
|
|||
// this being destroyed for cleaning up the stuff.
|
||||
}
|
||||
|
||||
void TextComposition::OnCharacterDataChanged(
|
||||
Text& aText, const CharacterDataChangeInfo& aInfo) {
|
||||
if (mContainerTextNode != &aText ||
|
||||
mCompositionStartOffsetInTextNode == UINT32_MAX ||
|
||||
mCompositionLengthInTextNode == UINT32_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore changes after composition string.
|
||||
if (aInfo.mChangeStart >=
|
||||
mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the change ends before the composition string, we need only to adjust
|
||||
// the start offset.
|
||||
if (aInfo.mChangeEnd <= mCompositionStartOffsetInTextNode) {
|
||||
MOZ_ASSERT(aInfo.LengthOfRemovedText() <=
|
||||
mCompositionStartOffsetInTextNode);
|
||||
mCompositionStartOffsetInTextNode -= aInfo.LengthOfRemovedText();
|
||||
mCompositionStartOffsetInTextNode += aInfo.mReplaceLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is caused by a splitting text node, the composition string
|
||||
// may be split out to the new right node. In the case,
|
||||
// CompositionTransaction::DoTransaction handles it with warking the
|
||||
// following text nodes. Therefore, we should NOT shrink the composing
|
||||
// range for avoind breaking the fix of bug 1310912. Although the handling
|
||||
// looks buggy so that we need to move the handling into here later.
|
||||
if (aInfo.mDetails &&
|
||||
aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the change removes/replaces the last character of the composition
|
||||
// string, we should shrink the composition range before the change start.
|
||||
// Then, the replace string will be never updated by coming composition
|
||||
// updates.
|
||||
if (aInfo.mChangeEnd >=
|
||||
mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) {
|
||||
// If deleting the first character of the composition string, collapse IME
|
||||
// selection temporarily. Updating composition string will insert new
|
||||
// composition string there.
|
||||
if (aInfo.mChangeStart <= mCompositionStartOffsetInTextNode) {
|
||||
mCompositionStartOffsetInTextNode = aInfo.mChangeStart;
|
||||
mCompositionLengthInTextNode = 0u;
|
||||
return;
|
||||
}
|
||||
// If some characters in the composition still stay, composition range
|
||||
// should be shrunken.
|
||||
MOZ_ASSERT(aInfo.mChangeStart > mCompositionStartOffsetInTextNode);
|
||||
mCompositionLengthInTextNode =
|
||||
aInfo.mChangeStart - mCompositionStartOffsetInTextNode;
|
||||
return;
|
||||
}
|
||||
|
||||
// If removed range starts in the composition string, we need only adjust
|
||||
// the length to make composition range contain the replace string.
|
||||
if (aInfo.mChangeStart >= mCompositionStartOffsetInTextNode) {
|
||||
MOZ_ASSERT(aInfo.LengthOfRemovedText() <= mCompositionLengthInTextNode);
|
||||
mCompositionLengthInTextNode -= aInfo.LengthOfRemovedText();
|
||||
mCompositionLengthInTextNode += aInfo.mReplaceLength;
|
||||
return;
|
||||
}
|
||||
|
||||
// If preceding characers of the composition string is also removed, new
|
||||
// composition start will be there and new composition ends at current
|
||||
// position.
|
||||
const uint32_t removedLengthInCompositionString =
|
||||
aInfo.mChangeEnd - mCompositionStartOffsetInTextNode;
|
||||
mCompositionStartOffsetInTextNode = aInfo.mChangeStart;
|
||||
mCompositionLengthInTextNode -= removedLengthInCompositionString;
|
||||
mCompositionLengthInTextNode += aInfo.mReplaceLength;
|
||||
}
|
||||
|
||||
bool TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const {
|
||||
return !Destroyed() && aWidget && !aWidget->Destroyed() &&
|
||||
mPresContext->GetPresShell() &&
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
#include "mozilla/dom/BrowserParent.h"
|
||||
#include "mozilla/dom/Text.h"
|
||||
|
||||
class nsRange;
|
||||
|
||||
struct CharacterDataChangeInfo;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class EditorBase;
|
||||
|
@ -274,6 +278,13 @@ class TextComposition final {
|
|||
// composition in new text node.
|
||||
}
|
||||
|
||||
/**
|
||||
* OnCharacterDataChanged() is called when IMEContentObserver receives
|
||||
* character data change notifications.
|
||||
*/
|
||||
void OnCharacterDataChanged(Text& aText,
|
||||
const CharacterDataChangeInfo& aInfo);
|
||||
|
||||
private:
|
||||
// Private destructor, to discourage deletion outside of Release():
|
||||
~TextComposition() {
|
||||
|
|
|
@ -125,6 +125,13 @@ NS_IMETHODIMP CompositionTransaction::DoTransaction() {
|
|||
// If composition string is split to multiple text nodes, we should put
|
||||
// whole new composition string to the first text node and remove the
|
||||
// compostion string in other nodes.
|
||||
// TODO: This should be handled by `TextComposition` because this assumes
|
||||
// that composition string has never touched by JS. However, it
|
||||
// would occur if the web app is a corrabolation software which
|
||||
// multiple users can modify anyware in an editor.
|
||||
// TODO: And if composition starts from a following text node, the offset
|
||||
// here is outdated and it will cause inserting composition string
|
||||
// **before** the proper point from point of view of the users.
|
||||
uint32_t replaceableLength = textNode->TextLength() - mOffset;
|
||||
ErrorResult error;
|
||||
editorBase->DoReplaceText(textNode, mOffset, mReplaceLength,
|
||||
|
|
|
@ -22,96 +22,217 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1310912
|
|||
<script class="testbody" type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
let editor = document.querySelector("div[contenteditable]");
|
||||
const editor = document.querySelector("div[contenteditable]");
|
||||
|
||||
editor.focus();
|
||||
let sel = window.getSelection();
|
||||
const sel = window.getSelection();
|
||||
sel.collapse(editor.childNodes[0], editor.textContent.length);
|
||||
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "DEF",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 3, length: 0 },
|
||||
});
|
||||
is(editor.textContent, "ABCDEF", "composing text should be set");
|
||||
(function testInsertEmptyTextNodeWhenCaretIsAtEndOfComposition() {
|
||||
const description =
|
||||
"testInsertEmptyTextNodeWhenCaretIsAtEndOfComposition: ";
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "DEF",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 3, length: 0 },
|
||||
});
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCDEF",
|
||||
`${description} Composing text "DEF" should be inserted at end of the text node`
|
||||
);
|
||||
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "GHI",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 0, length: 0 },
|
||||
});
|
||||
is(editor.textContent, "ABCGHI", "composing text should be replaced");
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABCDEF",
|
||||
`${
|
||||
description
|
||||
} First text node should have both preceding text and the composing text`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[1].data,
|
||||
"",
|
||||
`${description} Second text node should be empty`
|
||||
);
|
||||
})();
|
||||
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "JKL",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 0, length: 0 },
|
||||
});
|
||||
is(editor.textContent, "ABCJKL", "composing text should be replaced");
|
||||
(function testInsertEmptyTextNodeWhenCaretIsAtStartOfComposition() {
|
||||
const description =
|
||||
"testInsertEmptyTextNodeWhenCaretIsAtStartOfComposition: ";
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "GHI",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 0, length: 0 },
|
||||
});
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCGHI",
|
||||
`${description} Composing text should be replaced with new one`
|
||||
);
|
||||
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "MNO",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 1, length: 0 },
|
||||
});
|
||||
is(editor.textContent, "ABCMNO", "composing text should be replaced");
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABC",
|
||||
`${
|
||||
description
|
||||
} First text node should have only the preceding text of the composition`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[1].data,
|
||||
"",
|
||||
`${description} Second text node should have be empty`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[2].data,
|
||||
"GHI",
|
||||
`${description} Third text node should have only composing text`
|
||||
);
|
||||
})();
|
||||
|
||||
// Normal selection is the caret, therefore, inserting empty text node
|
||||
// creates the following DOM tree:
|
||||
// <div contenteditable>
|
||||
// |- #text ("ABCM")
|
||||
// |- #text ("")
|
||||
// +- #text ("NO")
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
is(editor.childNodes[0].data, "ABCM",
|
||||
"First text node should only have \"M\" of the composition string");
|
||||
is(editor.childNodes[1].data, "",
|
||||
"Second text node should be the inserted empty text node");
|
||||
is(editor.childNodes[2].data, "NO",
|
||||
"Third text node should have the remaining composition string");
|
||||
todo_is(editor.childNodes[3].nodeName, "BR",
|
||||
"Forth node is empty text node, but I don't where this comes from");
|
||||
(function testInsertEmptyTextNodeWhenCaretIsAtStartOfCompositionAgain() {
|
||||
const description =
|
||||
"testInsertEmptyTextNodeWhenCaretIsAtStartOfCompositionAgain: ";
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "JKL",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 0, length: 0 },
|
||||
});
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCJKL",
|
||||
`${description} Composing text should be replaced`
|
||||
);
|
||||
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABC",
|
||||
`${
|
||||
description
|
||||
} First text node should have only the preceding text of the composition`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[1].data,
|
||||
"",
|
||||
`${description} Second text node should have be empty`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[2].data,
|
||||
"JKL",
|
||||
`${description} Third text node should have only composing text`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testInsertEmptyTextNodeWhenCaretIsAtMiddleOfComposition() {
|
||||
const description =
|
||||
"testInsertEmptyTextNodeWhenCaretIsAtMiddleOfComposition: ";
|
||||
synthesizeCompositionChange({
|
||||
composition: {
|
||||
string: "MNO",
|
||||
clauses: [
|
||||
{ length: 3, attr: COMPOSITION_ATTR_CONVERTED_CLAUSE },
|
||||
],
|
||||
},
|
||||
caret: { start: 1, length: 0 },
|
||||
});
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCMNO",
|
||||
`${description} Composing text should be replaced`
|
||||
);
|
||||
|
||||
// Normal selection is the caret, therefore, inserting empty text node
|
||||
// creates the following DOM tree:
|
||||
// <div contenteditable>
|
||||
// |- #text ("ABCM")
|
||||
// |- #text ("")
|
||||
// +- #text ("NO")
|
||||
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(""));
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABCM",
|
||||
`${
|
||||
description
|
||||
} First text node should have the preceding text and composing string before the split point`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[1].data,
|
||||
"",
|
||||
`${description} Second text node should be empty`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[2].data,
|
||||
"NO",
|
||||
`${
|
||||
description
|
||||
} Third text node should have the remaining composing string`
|
||||
);
|
||||
todo_is(editor.childNodes[3].nodeName, "BR",
|
||||
"Forth node is empty text node, but I don't where this comes from");
|
||||
})();
|
||||
|
||||
// Then, committing composition makes the commit string into the first
|
||||
// text node and makes the following text nodes empty.
|
||||
// XXX I don't know whether the empty text nodes should be removed or not
|
||||
// at this moment.
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(editor.textContent, "ABCMNO",
|
||||
"composing text should be committed");
|
||||
is(editor.childNodes[0].data, "ABCMNO",
|
||||
"First text node should have the committed string");
|
||||
(function testCommitComposition() {
|
||||
const description = "testCommitComposition: ";
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCMNO",
|
||||
`${description} Composing text should be committed as-is`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABCMNO",
|
||||
`${description} First text node should have the committed string`
|
||||
);
|
||||
})();
|
||||
|
||||
synthesizeKey("Z", { accelKey: true });
|
||||
is(editor.textContent, "ABC",
|
||||
"text should be undone (commit string should've gone");
|
||||
is(editor.childNodes[0].data, "ABC",
|
||||
"First text node should have the committed string after undone");
|
||||
(function testUndoComposition() {
|
||||
const description = "testUndoComposition: ";
|
||||
synthesizeKey("Z", { accelKey: true });
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABC",
|
||||
`${description} Text should be undone (commit string should've gone)`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABC",
|
||||
`${description} First text node should have all text`
|
||||
);
|
||||
})();
|
||||
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true });
|
||||
is(editor.textContent, "ABCMNO",
|
||||
"text should be redone (commit string should've be back");
|
||||
is(editor.childNodes[0].data, "ABCMNO",
|
||||
"First text node should have the committed string after redone");
|
||||
(function testUndoAgain() {
|
||||
const description = "testUndoAgain: ";
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true });
|
||||
is(
|
||||
editor.textContent,
|
||||
"ABCMNO",
|
||||
`${description} Text should be redone (commit string should've be back)`
|
||||
);
|
||||
is(
|
||||
editor.childNodes[0].data,
|
||||
"ABCMNO",
|
||||
`${description} First text node should have all text`
|
||||
);
|
||||
})();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
@ -101,6 +101,46 @@ function isGreaterThan(aLeft, aRight, aMessage)
|
|||
ok(aLeft > aRight, aMessage + ", got=" + aLeft + ", expected minimum value=" + aRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* synthesizeSimpleCompositionChange synthesizes a composition which has only
|
||||
* one clause and put caret end of it.
|
||||
*
|
||||
* @param aComposition string or object. If string, it's treated as
|
||||
* composition string whose attribute is
|
||||
* COMPOSITION_ATTR_RAW_CLAUSE.
|
||||
* If object, it must have .string whose type is "string".
|
||||
* Additionally, .attr can be specified if you'd like to
|
||||
* use the other attribute instead of
|
||||
* COMPOSITION_ATTR_RAW_CLAUSE.
|
||||
*/
|
||||
function synthesizeSimpleCompositionChange(aComposition, aWindow, aCallback) {
|
||||
const comp = (() => {
|
||||
if (typeof aComposition == "string") {
|
||||
return { string: aComposition, attr: COMPOSITION_ATTR_RAW_CLAUSE };
|
||||
}
|
||||
return {
|
||||
string: aComposition.string,
|
||||
attr: aComposition.attr === undefined
|
||||
? COMPOSITION_ATTR_RAW_CLAUSE
|
||||
: aComposition.attr
|
||||
};
|
||||
})();
|
||||
synthesizeCompositionChange(
|
||||
{
|
||||
composition: {
|
||||
string: comp.string,
|
||||
clauses: [
|
||||
{ length: comp.string.length, attr: comp.attr },
|
||||
],
|
||||
},
|
||||
caret: { start: comp.string.length, length: 0 },
|
||||
},
|
||||
aWindow,
|
||||
aCallback
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
var div = document.getElementById("div");
|
||||
var textarea = document.getElementById("textarea");
|
||||
var panel = document.getElementById("panel");
|
||||
|
@ -234,12 +274,24 @@ function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
|
|||
selectedText.text == aExpectedText;
|
||||
}
|
||||
|
||||
function checkIMESelection(aSelectionType, aExpectedFound, aExpectedOffset, aExpectedText, aMessage, aID)
|
||||
{
|
||||
function checkIMESelection(
|
||||
aSelectionType,
|
||||
aExpectedFound,
|
||||
aExpectedOffset,
|
||||
aExpectedText,
|
||||
aMessage,
|
||||
aID,
|
||||
aToDo = {}
|
||||
) {
|
||||
if (!aID) {
|
||||
aID = "";
|
||||
}
|
||||
aMessage += " (" + aSelectionType + ")";
|
||||
let {
|
||||
notFound = is,
|
||||
offset = is,
|
||||
text = is,
|
||||
} = aToDo;
|
||||
let selectionType = 0;
|
||||
switch (aSelectionType) {
|
||||
case "RawClause":
|
||||
|
@ -263,16 +315,26 @@ function checkIMESelection(aSelectionType, aExpectedFound, aExpectedOffset, aExp
|
|||
": synthesizeQuerySelectedText " + aID)) {
|
||||
return false;
|
||||
}
|
||||
is(selectedText.notFound, !aExpectedFound,
|
||||
aMessage + ": selection should " + (aExpectedFound ? "" : "not") + " be found " + aID);
|
||||
notFound(
|
||||
selectedText.notFound,
|
||||
!aExpectedFound,
|
||||
`${aMessage}: selection should ${
|
||||
aExpectedFound ? "" : "not"
|
||||
} be found ${aID}`);
|
||||
if (selectedText.notFound) {
|
||||
return selectedText.notFound == !aExpectedFound;
|
||||
}
|
||||
|
||||
is(selectedText.offset, aExpectedOffset,
|
||||
aMessage + ": selection offset is wrong " + aID);
|
||||
is(selectedText.text, aExpectedText,
|
||||
aMessage + ": selected text is wrong " + aID);
|
||||
offset(
|
||||
selectedText.offset,
|
||||
aExpectedOffset,
|
||||
`${aMessage}: selection offset is wrong ${aID}`
|
||||
);
|
||||
text(
|
||||
selectedText.text,
|
||||
aExpectedText,
|
||||
`${aMessage}: selected text is wrong ${aID}`
|
||||
);
|
||||
return selectedText.offset == aExpectedOffset &&
|
||||
selectedText.text == aExpectedText;
|
||||
}
|
||||
|
@ -2559,6 +2621,813 @@ function runCompositionEventTest()
|
|||
formEventHandlerForInput, true);
|
||||
}
|
||||
|
||||
function runCompositionTestWhoseTextNodeModified() {
|
||||
const selection = windowOfContenteditable.getSelection();
|
||||
|
||||
(function testInsertTextBeforeComposition() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testInsertTextBeforeComposition:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>def</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "def".length);
|
||||
// Insert composition to the end of a text node
|
||||
synthesizeSimpleCompositionChange("g");
|
||||
is(
|
||||
textNode.data,
|
||||
"defg",
|
||||
`${description} Composition should be inserted to end of the text node`
|
||||
);
|
||||
|
||||
// Insert a character before the composition string
|
||||
textNode.insertData(0, "c");
|
||||
is(
|
||||
textNode.data,
|
||||
"cdefg",
|
||||
`${
|
||||
description
|
||||
} Composition should be shifted when a character is inserted before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}cdef`.length,
|
||||
"g",
|
||||
`${
|
||||
description
|
||||
} IME selection should be shifted when a character is inserted before it`
|
||||
);
|
||||
|
||||
// Update composition string (appending a character)
|
||||
synthesizeSimpleCompositionChange("gh");
|
||||
is(
|
||||
textNode.data,
|
||||
"cdefgh",
|
||||
`${
|
||||
description
|
||||
} Composition should be updated correctly after inserted a character before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}cdef`.length,
|
||||
"gh",
|
||||
`${
|
||||
description
|
||||
} IME selection should be extended correctly at updating composition after inserted a character before it`
|
||||
);
|
||||
|
||||
// Insert another character before the composition
|
||||
textNode.insertData(0, "b");
|
||||
is(
|
||||
textNode.data,
|
||||
"bcdefgh",
|
||||
`${
|
||||
description
|
||||
} Composition should be shifted when a character is inserted again before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}bcdef`.length,
|
||||
"gh",
|
||||
`${
|
||||
description
|
||||
} IME selection should be shifted when a character is inserted again before it`
|
||||
);
|
||||
|
||||
// Update the composition string again (appending another character)
|
||||
synthesizeSimpleCompositionChange("ghi");
|
||||
is(
|
||||
textNode.data,
|
||||
"bcdefghi",
|
||||
`${
|
||||
description
|
||||
} Composition should be updated correctly after inserted 2 characters before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}bcdef`.length,
|
||||
"ghi",
|
||||
`${
|
||||
description
|
||||
} IME selection should be extended correctly at updating composition after inserted 2 characters before it`
|
||||
);
|
||||
|
||||
// Insert a new character before the composition string
|
||||
textNode.insertData(0, "a");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefghi",
|
||||
`${
|
||||
description
|
||||
} Composition should be shifted when a character is inserted again and again before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}abcdef`.length,
|
||||
"ghi",
|
||||
`${
|
||||
description
|
||||
} IME selection should be shifted when a character is inserted again and again before it`
|
||||
);
|
||||
|
||||
// Commit the composition string
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefghi",
|
||||
`${
|
||||
description
|
||||
} Composition should be committed as is`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abcdefghi".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
|
||||
// Undo the commit
|
||||
synthesizeKey("z", { accelKey: true });
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdef",
|
||||
`${
|
||||
description
|
||||
} Composition should be undone correctly`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abcdef".length,
|
||||
`${
|
||||
description
|
||||
} Selection should be collapsed at where the composition was after undoing`
|
||||
);
|
||||
|
||||
// Redo the commit
|
||||
synthesizeKey("z", { accelKey: true, shiftKey: true });
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefghi",
|
||||
`${
|
||||
description
|
||||
} Composition should be redone correctly`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abcdefghi".length,
|
||||
`${
|
||||
description
|
||||
} focus offset of Selection should be at end of the commit string after redoing`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testInsertTextImmediatelyBeforeComposition() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyBeforeComposition:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>d</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, 0);
|
||||
// Insert composition at start of the text node
|
||||
synthesizeSimpleCompositionChange("b");
|
||||
is(
|
||||
textNode.data,
|
||||
"bd",
|
||||
`${description} Composition should be inserted to start of the text node`
|
||||
);
|
||||
|
||||
// Insert a character before the composition string
|
||||
textNode.insertData(0, "a");
|
||||
is(
|
||||
textNode.data,
|
||||
"abd",
|
||||
`${
|
||||
description
|
||||
} Composition should be shifted when a character is inserted immediately before it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"b",
|
||||
`${
|
||||
description
|
||||
} IME selection should be shifted when a character is inserted immediately before it`,
|
||||
"",
|
||||
{ offset: todo_is, text: todo_is }
|
||||
);
|
||||
|
||||
// Update the composition string after inserting character immediately before it
|
||||
synthesizeSimpleCompositionChange("bc");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcd",
|
||||
`${description} Composition should be updated after the inserted character`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"bc",
|
||||
`${
|
||||
description
|
||||
} IME selection should be set at the composition string after the inserted character`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abcd",
|
||||
`${
|
||||
description
|
||||
} Composition should be committed after the inserted character`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abc".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testInsertTextImmediatelyAfterComposition() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyAfterComposition:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>a</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "a".length);
|
||||
// Insert composition at end of the text node
|
||||
synthesizeSimpleCompositionChange("b");
|
||||
is(
|
||||
textNode.data,
|
||||
"ab",
|
||||
`${description} Composition should be inserted to start of the text node`
|
||||
);
|
||||
|
||||
// Insert a character after the composition string
|
||||
textNode.insertData("ab".length, "d");
|
||||
is(
|
||||
textNode.data,
|
||||
"abd",
|
||||
`${
|
||||
description
|
||||
} Composition should stay when a character is inserted immediately after it`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"b",
|
||||
`${
|
||||
description
|
||||
} IME selection should stay when a character is inserted immediately after it`
|
||||
);
|
||||
|
||||
// Update the composition string after inserting character immediately after it
|
||||
synthesizeSimpleCompositionChange("bc");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcd",
|
||||
`${description} Composition should be updated before the inserted character`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"bc",
|
||||
`${
|
||||
description
|
||||
} IME selection should be set at the composition string before the inserted character`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abcd",
|
||||
`${
|
||||
description
|
||||
} Composition should be committed before the inserted character`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abc".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
// Inserting/replacing text before the last character of composition string
|
||||
// should be contained by the composition, i.e., updated by next composition
|
||||
// update. This is Chrome compatible.
|
||||
(function testInsertTextMiddleOfComposition() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testInsertTextMiddleOfComposition:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>a</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "a".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("bd");
|
||||
is(
|
||||
textNode.data,
|
||||
"abd",
|
||||
`${description} Composition should be inserted to end of the text node`
|
||||
);
|
||||
|
||||
// Insert a character before the composition string
|
||||
textNode.insertData("ab".length, "c");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcd",
|
||||
`${
|
||||
description
|
||||
} Inserted string should inserted into the middle of composition string`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"bcd",
|
||||
`${
|
||||
description
|
||||
} IME selection should be extended when a character is inserted into middle of it`
|
||||
);
|
||||
|
||||
// Update the composition string after inserting character into it
|
||||
synthesizeSimpleCompositionChange("BD");
|
||||
is(
|
||||
textNode.data,
|
||||
"aBD",
|
||||
`${
|
||||
description
|
||||
} Composition should be replace the range containing the inserted character`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"BD",
|
||||
`${
|
||||
description
|
||||
} IME selection should be set at the updated composition string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"aBD",
|
||||
`${
|
||||
description
|
||||
} Composition should be committed without the inserted character`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"aBD".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testReplaceFirstCharOfCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceFirstCharOfCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("ab".length, "c".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"abXYZdefg",
|
||||
`${description} First character of the composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"XYZde",
|
||||
`${description} IME selection should contain the replace string`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEfg",
|
||||
`${description} Composition should update the replace string too`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should update the replace string too`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEfg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
// Although Chrome commits composition if all composition string is removed,
|
||||
// let's keep composition for making TSF stable...
|
||||
(function testReplaceAllCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceAllCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted to the text node`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("ab".length, "cde".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"abXYZfg",
|
||||
`${description} Composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"",
|
||||
`${
|
||||
description
|
||||
} IME selection should be collapsed before the replace string`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZfg",
|
||||
`${description} Composition should be inserted again`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should not contain the replace string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZfg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testReplaceCompositionStringAndSurroundedCharacters() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceCompositionStringAndSurroundedCharacters:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted to the text node`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("a".length, "bcdef".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"aXYZg",
|
||||
`${description} Composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"",
|
||||
`${
|
||||
description
|
||||
} IME selection should be collapsed before the replace string`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"aCDEXYZg",
|
||||
`${description} Composition should be inserted again`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should not contain the replace string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"aCDEXYZg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"aCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
// If start boundary characters are replaced, the replace string should be
|
||||
// contained into the composition range. This is Chrome compatible.
|
||||
(function testReplaceStartBoundaryOfCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceStartBoundaryOfCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted to the text node`
|
||||
);
|
||||
|
||||
// Replace some text
|
||||
textNode.replaceData("a".length, "bc".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"aXYZdefg",
|
||||
`${
|
||||
description
|
||||
} Start of the composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"XYZde",
|
||||
`${description} IME selection should contain the replace string`
|
||||
);
|
||||
|
||||
// Update the replace string and remaining composition.
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"aCDEfg",
|
||||
`${description} Composition should update the replace string too`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}a`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should contain the replace string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"aCDEfg",
|
||||
`${
|
||||
description
|
||||
} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"aCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
// If start boundary characters are replaced, the replace string should NOT
|
||||
// be contained in the composition range. This is Chrome compatible.
|
||||
(function testReplaceEndBoundaryOfCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceEndBoundaryOfCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted to the text node`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("abcd".length, "ef".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdXYZg",
|
||||
`${
|
||||
description
|
||||
} End half of the composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"cd",
|
||||
`${
|
||||
description
|
||||
} IME selection should be shrunken to the non-replaced part`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZg",
|
||||
`${description} Only the remaining composition string should be updated`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should NOT include the replace string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
// If the last character of composition is replaced, i.e., it should NOT be
|
||||
// treated as a part of composition string. This is Chrome compatible.
|
||||
(function testReplaceLastCharOfCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceLastCharOfCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("abcd".length, "e".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdXYZfg",
|
||||
`${description} Last character of the composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"cd",
|
||||
`${description} IME selection should be shrunken`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZfg",
|
||||
`${description} Composition should NOT update the replace string`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should not contain the replace string`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEXYZfg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
|
||||
(function testReplaceMiddleCharOfCompositionString() {
|
||||
const description =
|
||||
"runCompositionTestWhoseTextNodeModified: testReplaceMiddleCharOfCompositionString:";
|
||||
contenteditable.focus();
|
||||
contenteditable.innerHTML = "<p>abfg</p>";
|
||||
const textNode = contenteditable.firstChild.firstChild;
|
||||
selection.collapse(textNode, "ab".length);
|
||||
// Insert composition at middle of the text node
|
||||
synthesizeSimpleCompositionChange("cde");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcdefg",
|
||||
`${description} Composition should be inserted`
|
||||
);
|
||||
|
||||
// Replace the composition string
|
||||
textNode.replaceData("abc".length, "d".length, "XYZ");
|
||||
is(
|
||||
textNode.data,
|
||||
"abcXYZefg",
|
||||
`${
|
||||
description
|
||||
} Middle character of the composition should be replaced`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"cXYZe",
|
||||
`${description} IME selection should be extended by the replace string`
|
||||
);
|
||||
|
||||
// Update the composition string after replaced
|
||||
synthesizeSimpleCompositionChange("CDE");
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEfg",
|
||||
`${description} Composition should update the replace string`
|
||||
);
|
||||
checkIMESelection(
|
||||
"RawClause",
|
||||
true,
|
||||
`${kLF}ab`.length,
|
||||
"CDE",
|
||||
`${description} IME selection should be shrunken after update`
|
||||
);
|
||||
|
||||
// Commit it
|
||||
synthesizeComposition({ type: "compositioncommitasis" });
|
||||
is(
|
||||
textNode.data,
|
||||
"abCDEfg",
|
||||
`${description} Composition should be committed`
|
||||
);
|
||||
is(
|
||||
selection.focusOffset,
|
||||
"abCDE".length,
|
||||
`${description} Selection should be collapsed at end of the commit string`
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
function runQueryTextRectInContentEditableTest()
|
||||
{
|
||||
|
@ -9801,6 +10670,7 @@ async function runTest()
|
|||
runCompositionCommitAsIsTest();
|
||||
runCompositionCommitTest();
|
||||
runCompositionEventTest();
|
||||
runCompositionTestWhoseTextNodeModified();
|
||||
runQueryTextRectInContentEditableTest();
|
||||
runCharAtPointTest(textarea, "textarea in the document");
|
||||
runCharAtPointAtOutsideTest();
|
||||
|
|
Загрузка…
Ссылка в новой задаче