Quelle window_composition_text_querycontent.xhtml
Sprache: unbekannt
|
|
Spracherkennung für: .xhtml vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Testing composition, text and query content events"
xmlns:html=" http://www.w3.org/1999/xhtml"
xmlns=" http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js" />
<panel id="panel" hidden="true" orient="vertical">
<vbox id="vbox">
<html:textarea id="textbox" cols="20" rows="4" style="font-size: 36px;"/>
</vbox>
</panel>
<body xmlns=" http://www.w3.org/1999/xhtml">
<div id="display">
<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
<textarea style="margin: 0; font-family: -moz-fixed;" id="textarea" cols="20" rows="4"></t extarea><br/>
<iframe id="iframe" width="300" height="150"
src="data:text/html,<textarea id='textarea' cols='20' rows='4'></textarea>"></iframe><br/>
<iframe id="iframe2" width="300" height="150"
src="data:text/html,<body onload='document.designMode=%22on%22'>body content</body>"></iframe><br/>
<iframe id="iframe3" width="300" height="150"
src="data:text/html,<body onload='document.designMode=%22on%22'>body content</body>"></iframe><br/>
<iframe id="iframe4" width="300" height="150"
src="data:text/html,<div contenteditable id='contenteditable'></div>"></iframe><br/>
<!--
NOTE: the width for the next two iframes is chosen to be small enough to make
the Show Password button (for type=password) be outside the viewport so that
it doesn't affect the rendering compared to the type=text control.
But still large enough to comfortably fit the input values we test.
-->
<iframe id="iframe5" style="width:10ch" height="50" src="data:text/html,<input id='input'>"></iframe>
<iframe id="iframe6" style="width:10ch" height="50" src="data:text/html,<input id='password' type='password'>"></iframe><br/>
<iframe id="iframe7" width="300" height="150"
src="data:text/html,<span contenteditable id='contenteditable'></span>"></iframe><br/>
<input id="input" type="text"/><br/>
<input id="password" type="password"/><br/>
</div>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
function ok(aCondition, aMessage)
{
window.arguments[0].SimpleTest.ok(aCondition, aMessage);
}
function is(aLeft, aRight, aMessage)
{
window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
}
function isnot(aLeft, aRight, aMessage)
{
window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
}
function isfuzzy(aLeft, aRight, aEpsilon, aMessage) {
window.arguments[0].SimpleTest.isfuzzy(aLeft, aRight, aEpsilon, aMessage);
}
function todo(aCondition, aMessage)
{
window.arguments[0].SimpleTest.todo(aCondition, aMessage);
}
function todo_is(aLeft, aRight, aMessage)
{
window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
}
function todo_isnot(aLeft, aRight, aMessage)
{
window.arguments[0].SimpleTest.todo_isnot(aLeft, aRight, aMessage);
}
function isSimilarTo(aLeft, aRight, aAllowedDifference, aMessage)
{
if (Math.abs(aLeft - aRight) <= aAllowedDifference) {
ok(true, aMessage);
} else {
ok(false, aMessage + ", got=" + aLeft + ", expected=" + (aRight - aAllowedDifference) + "~" + (aRight + aAllowedDifference));
}
}
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");
var textbox = document.getElementById("textbox");
var iframe = document.getElementById("iframe");
var iframe2 = document.getElementById("iframe2");
var iframe3 = document.getElementById("iframe3");
var contenteditable;
var windowOfContenteditable;
var contenteditableBySpan;
var windowOfContenteditableBySpan;
var input = document.getElementById("input");
var password = document.getElementById("password");
var textareaInFrame;
const nsITextInputProcessorCallback = Ci.nsITextInputProcessorCallback;
const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
const nsIWebNavigation = Ci.nsIWebNavigation;
const nsIDocShell = Ci.nsIDocShell;
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const kExpectInputBeforeCompositionEnd = SpecialPowers.getBoolPref("dom.input_events.dispatch_before_compositionend");
function waitForTick() {
return new Promise(resolve => { SimpleTest.executeSoon(resolve); });
}
async function waitForEventLoops(aTimes)
{
for (let i = 1; i < aTimes; i++) {
await waitForTick();
}
await new Promise(resolve => { setTimeout(resolve, 20); });
}
function getEditor(aNode)
{
return aNode.editor;
}
function getHTMLEditorIMESupport(aWindow)
{
return aWindow.docShell.editor;
}
const kIsWin = (navigator.platform.indexOf("Win") == 0);
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
const kLFLen = (kIsWin && false) ? 2 : 1;
const kLF = (kIsWin && false) ? "\r\n" : "\n";
function checkQueryContentResult(aResult, aMessage)
{
ok(aResult, aMessage + ": the result is null");
if (!aResult) {
return false;
}
ok(aResult.succeeded, aMessage + ": the query content failed");
return aResult.succeeded;
}
function checkContent(aExpectedText, aMessage, aID)
{
if (!aID) {
aID = "";
}
let textContent = synthesizeQueryTextContent(0, 100);
if (!checkQueryContentResult(textContent, aMessage +
": synthesizeQueryTextContent " + aID)) {
return false;
}
is(textContent.text, aExpectedText,
aMessage + ": composition string is wrong " + aID);
return textContent.text == aExpectedText;
}
function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
{
if (!aID) {
aID = "";
}
aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
let textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
if (!checkQueryContentResult(textContent, aMessage +
"synthesizeQueryTextContent " + aID)) {
return false;
}
is(textContent.offset, aExpectedOffset,
aMessage + "offset is wrong " + aID);
is(textContent.text, aExpectedText,
aMessage + "text is wrong " + aID);
return textContent.offset == aExpectedOffset &&
textContent.text == aExpectedText;
}
function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
{
if (!aID) {
aID = "";
}
let selectedText = synthesizeQuerySelectedText();
if (!checkQueryContentResult(selectedText, aMessage +
": synthesizeQuerySelectedText " + aID)) {
return false;
}
if (aExpectedOffset === null) {
is(
selectedText.notFound,
true,
`${aMessage}: selection should not be found ${aID}`
);
return selectedText.notFound;
}
is(
selectedText.notFound,
false,
`${aMessage}: selection should be found ${aID}`
);
if (selectedText.notFound) {
return false;
}
is(
selectedText.offset,
aExpectedOffset,
`${aMessage}: selection offset should be ${aExpectedOffset} ${aID}`
);
is(
selectedText.text,
aExpectedText,
`${aMessage}: selected text should be "${aExpectedText}" ${aID}`
);
return selectedText.offset == aExpectedOffset &&
selectedText.text == aExpectedText;
}
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":
selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT;
break;
case "SelectedRawClause":
selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT;
break;
case "ConvertedClause":
selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT;
break;
case "SelectedClause":
selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT;
break;
default:
ok(false, aMessage + ": invalid selection type, " + aSelectionType);
}
isnot(selectionType, 0, aMessage + ": wrong value");
let selectedText = synthesizeQuerySelectedText(selectionType);
if (!checkQueryContentResult(selectedText, aMessage +
": synthesizeQuerySelectedText " + aID)) {
return false;
}
notFound(
selectedText.notFound,
!aExpectedFound,
`${aMessage}: selection should ${
aExpectedFound ? "" : "not"
} be found ${aID}`);
if (selectedText.notFound) {
return selectedText.notFound == !aExpectedFound;
}
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;
}
function checkRect(aRect, aExpectedRect, aMessage)
{
is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
return aRect.left == aExpectedRect.left &&
aRect.top == aExpectedRect.top &&
aRect.width == aExpectedRect.width &&
aRect.height == aExpectedRect.height;
}
function checkRectFuzzy(aRect, aExpectedRect, aEpsilon, aMessage) {
isfuzzy(aRect.left, aExpectedRect.left, aEpsilon.left, aMessage + ": left is wrong");
isfuzzy(aRect.top, aExpectedRect.top, aEpsilon.top, aMessage + " top is wrong");
isfuzzy(aRect.width, aExpectedRect.width, aEpsilon.width, aMessage + ": width is wrong");
isfuzzy(aRect.height, aExpectedRect.height, aEpsilon.height, aMessage + ": height is wrong");
return (aRect.left >= aExpectedRect.left - aEpsilon.left &&
aRect.left <= aExpectedRect.left + aEpsilon.left) &&
(aRect.top >= aExpectedRect.top - aEpsilon.top &&
aRect.top <= aExpectedRect.top + aEpsilon.top) &&
(aRect.width >= aExpectedRect.width - aEpsilon.width &&
aRect.width <= aExpectedRect.width + aEpsilon.width) &&
(aRect.height >= aExpectedRect.height - aEpsilon.height &&
aRect.height <= aExpectedRect.height + aEpsilon.height);
}
function getRectArray(aQueryTextRectArrayResult) {
let rects = [];
for (let i = 0; ; i++) {
let rect = { left: {}, top: {}, width: {}, height: {} };
try {
aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
} catch (e) {
break;
}
rects.push({
left: rect.left.value,
top: rect.top.value,
width: rect.width.value,
height: rect.height.value,
});
}
return rects;
}
function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
{
for (let i = 1; i < aExpectedTextRectArray.length; ++i) {
let rect = { left: {}, top: {}, width: {}, height: {} };
try {
aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
} catch (e) {
ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
return false;
}
function toRect(aRect)
{
return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
}
if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
return false;
}
}
return true;
}
function checkRectContainsRect(aRect, aContainer, aMessage)
{
let container = { left: Math.ceil(aContainer.left),
top: Math.ceil(aContainer.top),
width: Math.floor(aContainer.width),
height: Math.floor(aContainer.height) };
let ret = container.left <= aRect.left &&
container.top <= aRect.top &&
container.left + container.width >= aRect.left + aRect.width &&
container.top + container.height >= aRect.top + aRect.height;
ret = ret && aMessage;
ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
container.top + ", width=" + container.width + ", height=" +
container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
", width=" + aRect.width + ", height=" + aRect.height + " }");
return ret;
}
// eslint-disable-next-line complexity
function runUndoRedoTest()
{
textarea.value = "";
textarea.focus();
// input raw characters
synthesizeCompositionChange(
{ "composition":
{ "string": "\u306D",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "," },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u306D\u3053",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "b" },
});
// convert
synthesizeCompositionChange(
{ "composition":
{ "string": "\u732B",
"clauses":
[
{ "length": 1,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: " " },
});
// commit
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
// input raw characters
synthesizeCompositionChange(
{ "composition":
{ "string": "\u307E",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "j" },
});
// cancel the composition
synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
// input raw characters
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3080",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "]" },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3080\u3059",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "r" },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3080\u3059\u3081",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 3, "length": 0 },
"key": { key: "/" },
});
// convert
synthesizeCompositionChange(
{ "composition":
{ "string": "\u5A18",
"clauses":
[
{ "length": 1,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: " " },
});
// commit
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
sendString(" meant");
synthesizeKey("KEY_Backspace");
synthesizeKey("s \"cat-girl\". She is a ");
// input raw characters
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3088",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "9" },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3088\u3046",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "4" },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3088\u3046\u304b",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 3, "length": 0 },
"key": { key: "t" },
});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3088\u3046\u304b\u3044",
"clauses":
[
{ "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 4, "length": 0 },
"key": { key: "e" },
});
// convert
synthesizeCompositionChange(
{ "composition":
{ "string": "\u5996\u602a",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: " " },
});
// commit
synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
synthesizeKey("KEY_Backspace", {repeat: 12});
let i = 0;
if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B\u5A18 mean",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B\u5A18 meant",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B\u5A18",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("\u732B",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
// XXX this is unexpected behavior, see bug 258291
if (!checkContent("\u732B",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true});
if (!checkContent("",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
// XXX this is unexpected behavior, see bug 258291
if (!checkContent("\u732B",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 meant",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 mean",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
return;
}
synthesizeKey("Z", {accelKey: true, shiftKey: true});
if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
"runUndoRedoTest", "#" + ++i) ||
!checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
// eslint-disable-next-line no-useless-return
return;
}
}
function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aTargetRanges, aDescription) {
if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
throw new Error(`${aDescription}: "${aEvent.type}" is not InputEvent`);
}
ok(InputEvent.isInstance(aEvent), `"${aEvent.type}" event should be dispatched with InputEvent interface: ${aDescription}`);
let cancelable = aEvent.type === "beforeinput" &&
aInputType !== "insertCompositionText" &&
aInputType !== "deleteCompositionText";
is(aEvent.cancelable, cancelable, `"${aEvent.type}" event should ${cancelable ? "be" : "be never"} cancelable: ${aDescription}`);
is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
is(aEvent.isComposing, aIsComposing, `isComposing of "${aEvent.type}" event should be ${aIsComposing}: ${aDescription}`);
is(aEvent.inputType, aInputType, `inputType of "${aEvent.type}" event should be "${aInputType}": ${aDescription}`);
is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
is(aEvent.dataTransfer, null, `dataTransfer of "${aEvent.type}" event should be null: ${aDescription}`);
let targetRanges = aEvent.getTargetRanges();
if (aTargetRanges.length === 0) {
is(targetRanges.length, 0,
`getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
} else {
is(targetRanges.length, aTargetRanges.length,
`getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
if (targetRanges.length == aTargetRanges.length) {
for (let i = 0; i < targetRanges.length; i++) {
is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
`startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
`startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
`endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
`endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
}
}
}
}
function checkTextInputEvent(aEvent, aData, aDescription) {
if (aEvent.type !== "textInput") {
throw new Error(`${aDescription}: "${aEvent.type}" is not TextEvent`);
}
ok(TextEvent.isInstance(aEvent), `"${aEvent.type}" event should be dispatched with TextEvent interface: ${aDescription}`);
is(aEvent.cancelable, true, `"${aEvent.type}" event should be cancelable: ${aDescription}`);
is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
}
function runCompositionCommitAsIsTest()
{
textarea.focus();
let result = [];
function clearResult()
{
result = [];
}
function handler(aEvent)
{
result.push(aEvent);
}
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("beforeinput", handler, true);
textarea.addEventListener("textInput", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
// compositioncommitasis with composing string.
(() => {
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: no textInput event should be fired before commit #1");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
`runCompositionCommitAsIsTest: ${kExpectInputBeforeCompositionEnd ? 6 : 5} events should be fired after dispatching compositioncommitasis #1`);
let index = -1;
is(result[++index].type, "text",
"runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
is(result[++index].type, "beforeinput",
"runCompositionCommitAsIsTest: beforeinput should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
is(result[++index].type, "textInput",
"runCompositionCommitAsIsText: textInput should be fired after dispatching compositioncommitasis because it's after the last beforeinput for the composition #1");
checkTextInputEvent(result[index], "\u3042",
"runCompositionCommitAsIsText: after dispatching compositioncommitasis #1");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
}
is(result[++index].type, "compositionend",
"runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
is(result[++index].type, "input",
"runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
checkInputEvent(result[index], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
})();
// compositioncommitasis with committed string.
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: a textInput event should be fired before commit #2");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, 2,
"runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
// XXX Do we need a "beforeinput" event here? Not sure.
is(result[0].type, "compositionend",
"runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
is(result[1].type, "input",
"runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
// compositioncommitasis with committed string.
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "KEY_Escape", type: "keydown" },
});
is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
todo_isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitAsIsTest: a textInput event should be fired immediately before commit #3");
clearResult();
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
is(result.length, 2,
"runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
// XXX Do we need a "beforeinput" event here? Not sure.
is(result[0].type, "compositionend",
"runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
is(result[1].type, "input",
"runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
checkInputEvent(result[1], false, "insertCompositionText", "", [],
"runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("beforeinput", handler, true);
textarea.removeEventListener("textInput", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
function runCompositionCommitTest()
{
textarea.focus();
let result = [];
function clearResult()
{
result = [];
}
function handler(aEvent)
{
result.push(aEvent);
}
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.addEventListener("beforeinput", handler, true);
textarea.addEventListener("textInput", handler, true);
textarea.addEventListener("input", handler, true);
textarea.addEventListener("text", handler, true);
// compositioncommit with different composing string.
(() => {
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #1");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 7 : 6} events should be fired after dispatching compositioncommit #1`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #1");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit because the preceding beforeinput is last one for the composition #1");
checkTextInputEvent(result[index], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #1");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #1");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
checkInputEvent(result[index], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #1");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
})();
// compositioncommit with different committed string when there is already committed string
(() => {
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #2");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 7 : 6} events should be fired after dispatching compositioncommit #2`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #2");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #2");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #2");
checkTextInputEvent(result[index], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #2");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #2");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
checkInputEvent(result[index], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #2");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
})();
// compositioncommit with empty composition string.
(() => {
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
is(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: no textInput event should be fired before commit #3");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 7 : 6} events should be fired after dispatching compositioncommit #3`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #3");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #3");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #3");
checkTextInputEvent(result[index], "\u3043",
"runCompositionCommitTest: after dispatching compositioncommit #3");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
checkInputEvent(result[index], true, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #3");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
checkInputEvent(result[index], false, "insertCompositionText", "\u3043", [],
"runCompositionCommitTest: after dispatching compositioncommit #3");
is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
})();
// inserting empty string with simple composition.
(() => {
textarea.value = "abc";
textarea.setSelectionRange(3, 3);
synthesizeComposition({ type: "compositionstart" });
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "" });
is(result.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 6 : 5} events should be fired when inserting empty string with composition`);
let index = -1;
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired when inserting empty string with composition");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired when inserting empty string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when inserting empty string with composition");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired when inserting empty string with composition");
checkTextInputEvent(result[index], "",
"runCompositionCommitTest: when inserting empty string with composition");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when inserting empty string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when inserting empty string with composition");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when inserting empty string with composition");
checkInputEvent(result[index], false, "insertCompositionText", "", [],
"runCompositionCommitTest: when inserting empty string with composition");
is(textarea.value, "abc",
"runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
})();
// replacing selection with empty string with simple composition.
(() => {
textarea.value = "abc";
textarea.setSelectionRange(0, 3);
synthesizeComposition({ type: "compositionstart" });
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "" });
is(result.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 6 : 5} events should be fired when replacing with empty string with composition`);
let index = -1;
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired when replacing with empty string with composition");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired when replacing with empty string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when replacing with empty string with composition");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired when replacing with empty string with composition");
checkTextInputEvent(result[index], "",
"runCompositionCommitTest: when replacing with empty string with composition");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when replacing with empty string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: when replacing with empty string with composition");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when replacing with empty string with composition");
checkInputEvent(result[index], false, "insertCompositionText", "", [],
"runCompositionCommitTest: when replacing with empty string with composition");
is(textarea.value, "",
"runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
})();
// replacing selection with same string with simple composition.
(() => {
textarea.value = "abc";
textarea.setSelectionRange(0, 3);
synthesizeComposition({ type: "compositionstart" });
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "abc" });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${
kExpectInputBeforeCompositionEnd ? 7 : 6
} events should be fired when replacing selection with same string with composition`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired when replacing selection with same string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "abc", [],
"runCompositionCommitTest: when replacing selection with same string with composition");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired when replacing selection with same string with composition");
checkTextInputEvent(result[index], "abc",
"runCompositionCommitTest: when replacing selection with same string with composition");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
checkInputEvent(result[index], true, "insertCompositionText", "abc", [],
"runCompositionCommitTest: when replacing selection with same string with composition");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
checkInputEvent(result[index], false, "insertCompositionText", "abc", [],
"runCompositionCommitTest: when replacing selection with same string with composition");
is(textarea.value, "abc",
"runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
})();
// compositioncommit with non-empty composition string.
(() => {
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 7 : 6} events should be fired after dispatching compositioncommit #4`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #4");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: after dispatching compositioncommit #4");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #4");
checkTextInputEvent(result[index], "",
"runCompositionCommitTest: after dispatching compositioncommit #4");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
checkInputEvent(result[index], true, "insertCompositionText", "", [],
"runCompositionCommitTest: after dispatching compositioncommit #4");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
checkInputEvent(result[index], false, "insertCompositionText", "", [],
"runCompositionCommitTest: after dispatching compositioncommit #4");
is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
})();
// compositioncommit immediately without compositionstart
(() => {
textarea.value = "";
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 7 : 6,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 7 : 6} events should be fired after dispatching compositioncommit #5`);
let index = -1;
is(result[++index].type, "compositionupdate",
"runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #5");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #5");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #5");
checkTextInputEvent(result[index], "\u3042",
"runCompositionCommitTest: after dispatching compositioncommit #5");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #5");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
checkInputEvent(result[index], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #5");
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
})();
// compositioncommit with same composition string.
(() => {
textarea.value = "";
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
is(result.length, kExpectInputBeforeCompositionEnd ? 6 : 5,
`runCompositionCommitTest: ${kExpectInputBeforeCompositionEnd ? 6 : 5} events should be fired after dispatching compositioncommit #6`);
let index = -1;
is(result[++index].type, "text",
"runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
is(result[++index].type, "beforeinput",
"runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #6");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #6");
is(result[++index].type, "textInput",
"runCompositionCommitTest: textInput should be fired after dispatching compositioncommit #6");
checkTextInputEvent(result[index], "\u3042",
"runCompositionCommitTest: after dispatching compositioncommit #6");
if (kExpectInputBeforeCompositionEnd) {
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
checkInputEvent(result[index], true, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #6");
}
is(result[++index].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
is(result[++index].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
checkInputEvent(result[index], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #6");
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
})();
// compositioncommit with same composition string when there is committed string
textarea.value = "";
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3042",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "KEY_Enter", type: "keydown" },
});
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
todo_isnot(result.findIndex(value => value.type == "textInput"), -1,
"runCompositionCommitTest: a textInput event should be fired before immediately commit #6");
clearResult();
synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
is(result.length, 2,
"runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
// XXX Do we need a "beforeinput" event here? Not sure.
is(result[0].type, "compositionend",
"runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
is(result[1].type, "input",
"runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
"runCompositionCommitTest: after dispatching compositioncommit #7");
is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
textarea.removeEventListener("beforeinput", handler, true);
textarea.removeEventListener("textInput", handler, true);
textarea.removeEventListener("input", handler, true);
textarea.removeEventListener("text", handler, true);
}
// eslint-disable-next-line complexity
async function runCompositionTest()
{
textarea.value = "";
textarea.focus();
let caretRects = [];
let caretRect = synthesizeQueryCaretRect(0);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #0")) {
return;
}
caretRects[0] = caretRect;
// input first character
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "o" },
});
if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
!checkSelection(1, "", "runCompositionTest", "#1-1")) {
return;
}
caretRect = synthesizeQueryCaretRect(1);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-1")) {
return;
}
caretRects[1] = caretRect;
// input second character
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
});
if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
!checkSelection(2, "", "runCompositionTest", "#1-2")) {
return;
}
caretRect = synthesizeQueryCaretRect(2);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-2")) {
return;
}
caretRects[2] = caretRect;
isnot(caretRects[2].left, caretRects[1].left,
"runCompositionTest: caret isn't moved (#1-2)");
is(caretRects[2].top, caretRects[1].top,
"runCompositionTest: caret is moved to another line (#1-2)");
is(caretRects[2].width, caretRects[1].width,
"runCompositionTest: caret width is wrong (#1-2)");
is(caretRects[2].height, caretRects[1].height,
"runCompositionTest: caret width is wrong (#1-2)");
// input third character
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 3, "length": 0 },
"key": { key: "/" },
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
!checkSelection(3, "", "runCompositionTest", "#1-3")) {
return;
}
caretRect = synthesizeQueryCaretRect(3);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3")) {
return;
}
caretRects[3] = caretRect;
isnot(caretRects[3].left, caretRects[2].left,
"runCompositionTest: caret isn't moved (#1-3)");
is(caretRects[3].top, caretRects[2].top,
"runCompositionTest: caret is moved to another line (#1-3)");
is(caretRects[3].width, caretRects[2].width,
"runCompositionTest: caret width is wrong (#1-3)");
is(caretRects[3].height, caretRects[2].height,
"runCompositionTest: caret height is wrong (#1-3)");
// moves the caret left
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "KEY_ArrowLeft" },
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
!checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
return;
}
caretRect = synthesizeQueryCaretRect(2);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
return;
}
is(caretRect.left, caretRects[2].left,
"runCompositionTest: caret rects are different (#1-3-1, left)");
is(caretRect.top, caretRects[2].top,
"runCompositionTest: caret rects are different (#1-3-1, top)");
// by bug 335359, the caret width depends on the right side's character.
is(caretRect.width, caretRects[2].width + Math.round(window.devicePixelRatio),
"runCompositionTest: caret rects are different (#1-3-1, width)");
is(caretRect.height, caretRects[2].height,
"runCompositionTest: caret rects are different (#1-3-1, height)");
// moves the caret left
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "KEY_ArrowLeft" },
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
!checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
return;
}
caretRect = synthesizeQueryCaretRect(1);
if (!checkQueryContentResult(caretRect,
"runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
return;
}
is(caretRect.left, caretRects[1].left,
"runCompositionTest: caret rects are different (#1-3-2, left)");
is(caretRect.top, caretRects[1].top,
"runCompositionTest: caret rects are different (#1-3-2, top)");
// by bug 335359, the caret width depends on the right side's character.
is(caretRect.width, caretRects[1].width + Math.round(window.devicePixelRatio),
"runCompositionTest: caret rects are different (#1-3-2, width)");
is(caretRect.height, caretRects[1].height,
"runCompositionTest: caret rects are different (#1-3-2, height)");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093",
"clauses":
[
{ "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 4, "length": 0 },
"key": { key: "y" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
!checkSelection(4, "", "runCompositionTest", "#1-4")) {
return;
}
// backspace
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 3, "length": 0 },
"key": { key: "KEY_Backspace" },
});
if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
!checkSelection(3, "", "runCompositionTest", "#1-5")) {
return;
}
// re-input
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093",
"clauses":
[
{ "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 4, "length": 0 },
"key": { key: "y" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
!checkSelection(4, "", "runCompositionTest", "#1-6")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055",
"clauses":
[
{ "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 5, "length": 0 },
"key": { key: "x" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
!checkSelection(5, "", "runCompositionTest", "#1-7")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
"clauses":
[
{ "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 6, "length": 0 },
"key": { key: "e" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
!checkSelection(6, "", "runCompositionTest", "#1-8")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
"clauses":
[
{ "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 7, "length": 0 },
"key": { key: "b" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
!checkSelection(7, "", "runCompositionTest", "#1-8")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
"clauses":
[
{ "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 8, "length": 0 },
"key": { key: "4" },
});
if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
"runCompositionTest", "#1-9") ||
!checkSelection(8, "", "runCompositionTest", "#1-9")) {
return;
}
// convert
synthesizeCompositionChange(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"clauses":
[
{ "length": 4,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
{ "length": 2,
"attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
]
},
"caret": { "start": 4, "length": 0 },
"key": { key: " " },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"runCompositionTest", "#1-10") ||
!checkSelection(4, "", "runCompositionTest", "#1-10")) {
return;
}
// change the selected clause
synthesizeCompositionChange(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"clauses":
[
{ "length": 4,
"attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
{ "length": 2,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
]
},
"caret": { "start": 6, "length": 0 },
"key": { key: "KEY_ArrowLeft", shiftKey: true },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
"runCompositionTest", "#1-11") ||
!checkSelection(6, "", "runCompositionTest", "#1-11")) {
return;
}
// reset clauses
synthesizeCompositionChange(
{ "composition":
{ "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"clauses":
[
{ "length": 5,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
{ "length": 3,
"attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
]
},
"caret": { "start": 5, "length": 0 },
"key": { key: "KEY_ArrowRight" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"runCompositionTest", "#1-12") ||
!checkSelection(5, "", "runCompositionTest", "#1-12")) {
return;
}
let textRect1 = synthesizeQueryTextRect(0, 1);
let textRect2 = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(textRect1,
"runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
!checkQueryContentResult(textRect2,
"runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
return;
}
// commit the composition string
synthesizeComposition({ type: "compositioncommitasis" });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
"runCompositionTest", "#1-13") ||
!checkSelection(8, "", "runCompositionTest", "#1-13")) {
return;
}
let textRect3 = synthesizeQueryTextRect(0, 1);
let textRect4 = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(textRect3,
"runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
!checkQueryContentResult(textRect4,
"runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
return;
}
checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");
// restart composition and input characters
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3057",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "d" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
"runCompositionTest", "#2-1") ||
!checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
return;
}
let textRect3QueriedWithRelativeOffset = synthesizeQueryTextRect(-8, 1, true);
let textRect4QueriedWithRelativeOffset = synthesizeQueryTextRect(-8 + 1, 1, true);
checkRect(textRect3QueriedWithRelativeOffset, textRect3, "runCompositionTest: textRect #2-1-2");
checkRect(textRect4QueriedWithRelativeOffset, textRect4, "runCompositionTest: textRect #2-1-3");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3058",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "r" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
"runCompositionTest", "#2-2") ||
!checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3058\u3087",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
"runCompositionTest", "#2-3") ||
!checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3058\u3087\u3046",
"clauses":
[
{ "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 3, "length": 0 },
"key": { key: "4" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
"runCompositionTest", "#2-4") ||
!checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
return;
}
// commit the composition string
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
"runCompositionTest", "#2-4") ||
!checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
return;
}
// set selection
const selectionSetTest = await synthesizeSelectionSet(4, 7, false);
ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");
if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
return;
}
// start composition with selection
synthesizeCompositionChange(
{ "composition":
{ "string": "\u304A",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "6" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
"runCompositionTest", "#3-2") ||
!checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
return;
}
// remove the composition string
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "KEY_Backspace" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#3-3") ||
!checkSelection(4, "", "runCompositionTest", "#3-3")) {
return;
}
// re-input the composition string
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3046",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "4" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
"runCompositionTest", "#3-4") ||
!checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
return;
}
// cancel the composition
synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#3-5") ||
!checkSelection(4, "", "runCompositionTest", "#3-5")) {
return;
}
// bug 271815, some Chinese IMEs for Linux make empty composition string
// and compty clause information when it lists up Chinese characters on
// its candidate window.
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "a" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#4-1") ||
!checkSelection(4, "", "runCompositionTest", "#4-1")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "b" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#4-2") ||
!checkSelection(4, "", "runCompositionTest", "#4-2")) {
return;
}
synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#4-3") ||
!checkSelection(5, "", "runCompositionTest", "#4-3")) {
return;
}
// testing the canceling case
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "a" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#4-5") ||
!checkSelection(5, "", "runCompositionTest", "#4-5")) {
return;
}
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#4-6") ||
!checkSelection(5, "", "runCompositionTest", "#4-6")) {
return;
}
// testing whether the empty composition string deletes selected string.
synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "a" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#4-8") ||
!checkSelection(4, "", "runCompositionTest", "#4-8")) {
return;
}
synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
"runCompositionTest", "#4-9") ||
!checkSelection(5, "", "runCompositionTest", "#4-9")) {
return;
}
synthesizeKey("KEY_Backspace");
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#4-11") ||
!checkSelection(4, "", "runCompositionTest", "#4-11")) {
return;
}
// bug 23558, ancient Japanese IMEs on Window may send empty text event
// twice at canceling composition.
synthesizeCompositionChange(
{ "composition":
{ "string": "\u6700",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "a" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#5-1") ||
!checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
return;
}
synthesizeCompositionChange(
{ "composition":
{ "string": "",
"clauses":
[
{ "length": 0, "attr": 0 }
]
},
"caret": { "start": 0, "length": 0 },
"key": { key: "KEY_Backspace", type: "keydown" },
});
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#5-2") ||
!checkSelection(4, "", "runCompositionTest", "#5-2")) {
return;
}
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#5-3") ||
!checkSelection(4, "", "runCompositionTest", "#5-3")) {
return;
}
// Undo tests for the testcases for bug 23558 and bug 271815
synthesizeKey("z", { accelKey: true });
// XXX this is unexpected behavior, see bug 258291
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#6-1") ||
!checkSelection(4, "", "runCompositionTest", "#6-1")) {
return;
}
synthesizeKey("z", { accelKey: true });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
"runCompositionTest", "#6-2") ||
!checkSelection(5, "", "runCompositionTest", "#6-2")) {
return;
}
synthesizeKey("z", { accelKey: true });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#6-3") ||
!checkSelection(4, "", "runCompositionTest", "#6-3")) {
return;
}
synthesizeKey("z", { accelKey: true });
// XXX this is unexpected behavior, see bug 258291
if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
"runCompositionTest", "#6-4") ||
!checkSelection(5, "", "runCompositionTest", "#6-4")) {
return;
}
synthesizeKey("z", { accelKey: true });
if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
"runCompositionTest", "#6-5") ||
!checkSelection(4, "", "runCompositionTest", "#6-5")) {
// eslint-disable-next-line no-useless-return
return;
}
}
function runCompositionEventTest()
{
const kDescription = "runCompositionEventTest: ";
const kEvents = ["compositionstart", "compositionupdate", "compositionend",
"input"];
input.value = "";
input.focus();
let windowEventCounts = [], windowEventData = [], windowEventLocale = [];
let inputEventCounts = [], inputEventData = [], inputEventLocale = [];
let preventDefault = false;
let stopPropagation = false;
function initResults()
{
for (let i = 0; i < kEvents.length; i++) {
windowEventCounts[kEvents[i]] = 0;
windowEventData[kEvents[i]] = "";
windowEventLocale[kEvents[i]] = "";
inputEventCounts[kEvents[i]] = 0;
inputEventData[kEvents[i]] = "";
inputEventLocale[kEvents[i]] = "";
}
}
function compositionEventHandlerForWindow(aEvent)
{
windowEventCounts[aEvent.type]++;
windowEventData[aEvent.type] = aEvent.data;
windowEventLocale[aEvent.type] = aEvent.locale;
if (preventDefault) {
aEvent.preventDefault();
}
if (stopPropagation) {
aEvent.stopPropagation();
}
}
function formEventHandlerForWindow(aEvent)
{
ok(aEvent.isTrusted, "input events must be trusted events");
windowEventCounts[aEvent.type]++;
windowEventData[aEvent.type] = input.value;
}
function compositionEventHandlerForInput(aEvent)
{
inputEventCounts[aEvent.type]++;
inputEventData[aEvent.type] = aEvent.data;
inputEventLocale[aEvent.type] = aEvent.locale;
if (preventDefault) {
aEvent.preventDefault();
}
if (stopPropagation) {
aEvent.stopPropagation();
}
}
function formEventHandlerForInput(aEvent)
{
inputEventCounts[aEvent.type]++;
inputEventData[aEvent.type] = input.value;
}
window.addEventListener("compositionstart", compositionEventHandlerForWindow,
true, true);
window.addEventListener("compositionend", compositionEventHandlerForWindow,
true, true);
window.addEventListener("compositionupdate", compositionEventHandlerForWindow,
true, true);
window.addEventListener("input", formEventHandlerForWindow,
true, true);
input.addEventListener("compositionstart", compositionEventHandlerForInput,
true, true);
input.addEventListener("compositionend", compositionEventHandlerForInput,
true, true);
input.addEventListener("compositionupdate", compositionEventHandlerForInput,
true, true);
input.addEventListener("input", formEventHandlerForInput,
true, true);
// test for normal case
initResults();
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "o" },
});
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by window #1");
is(windowEventData.compositionstart, "",
kDescription + "data of compositionstart isn't empty (window) #1");
is(windowEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (window) #1");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by input #1");
is(inputEventData.compositionstart, "",
kDescription + "data of compositionstart isn't empty (input) #1");
is(inputEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (input) #1");
is(windowEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by window #1");
is(windowEventData.compositionupdate, "\u3089",
kDescription + "data of compositionupdate doesn't match (window) #1");
is(windowEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (window) #1");
is(inputEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by input #1");
is(inputEventData.compositionupdate, "\u3089",
kDescription + "data of compositionupdate doesn't match (input) #1");
is(inputEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (input) #1");
is(windowEventCounts.compositionend, 0,
kDescription + "compositionend has been handled by window #1");
is(inputEventCounts.compositionend, 0,
kDescription + "compositionend has been handled by input #1");
is(windowEventCounts.input, 1,
kDescription + "input hasn't been handled by window #1");
is(windowEventData.input, "\u3089",
kDescription + "value of input element wasn't modified (window) #1");
is(inputEventCounts.input, 1,
kDescription + "input hasn't been handled by input #1");
is(inputEventData.input, "\u3089",
kDescription + "value of input element wasn't modified (input) #1");
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089\u30FC",
"clauses":
[
{ "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 2, "length": 0 },
"key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
});
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart has been handled more than once by window #2");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart has been handled more than once by input #2");
is(windowEventCounts.compositionupdate, 2,
kDescription + "compositionupdate hasn't been handled by window #2");
is(windowEventData.compositionupdate, "\u3089\u30FC",
kDescription + "data of compositionupdate doesn't match (window) #2");
is(windowEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (window) #2");
is(inputEventCounts.compositionupdate, 2,
kDescription + "compositionupdate hasn't been handled by input #2");
is(inputEventData.compositionupdate, "\u3089\u30FC",
kDescription + "data of compositionupdate doesn't match (input) #2");
is(inputEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (input) #2");
is(windowEventCounts.compositionend, 0,
kDescription + "compositionend has been handled during composition by window #2");
is(inputEventCounts.compositionend, 0,
kDescription + "compositionend has been handled during composition by input #2");
is(windowEventCounts.input, 2,
kDescription + "input hasn't been handled by window #2");
is(windowEventData.input, "\u3089\u30FC",
kDescription + "value of input element wasn't modified (window) #2");
is(inputEventCounts.input, 2,
kDescription + "input hasn't been handled by input #2");
is(inputEventData.input, "\u3089\u30FC",
kDescription + "value of input element wasn't modified (input) #2");
// text event shouldn't cause composition update, e.g., at committing.
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart has been handled more than once by window #3");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart has been handled more than once by input #3");
is(windowEventCounts.compositionupdate, 2,
kDescription + "compositionupdate has been fired unexpectedly on window #3");
is(inputEventCounts.compositionupdate, 2,
kDescription + "compositionupdate has been fired unexpectedly on input #3");
is(windowEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by window #3");
is(windowEventData.compositionend, "\u3089\u30FC",
kDescription + "data of compositionend doesn't match (window) #3");
is(windowEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (window) #3");
is(inputEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by input #3");
is(inputEventData.compositionend, "\u3089\u30FC",
kDescription + "data of compositionend doesn't match (input) #3");
is(inputEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (input) #3");
is(windowEventCounts.input, kExpectInputBeforeCompositionEnd ? 4 : 3,
kDescription + "input hasn't been handled by window #3");
is(windowEventData.input, "\u3089\u30FC",
kDescription + "value of input element wasn't modified (window) #3");
is(inputEventCounts.input, kExpectInputBeforeCompositionEnd ? 4 : 3,
kDescription + "input hasn't been handled by input #3");
is(inputEventData.input, "\u3089\u30FC",
kDescription + "value of input element wasn't modified (input) #3");
// select the second character, then, data of composition start should be
// the selected character.
initResults();
synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
synthesizeCompositionChange(
{ "composition":
{ "string": "\u3089",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "o" },
});
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by window #4");
is(windowEventData.compositionstart, "\u30FC",
kDescription + "data of compositionstart is empty (window) #4");
is(windowEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (window) #4");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by input #4");
is(inputEventData.compositionstart, "\u30FC",
kDescription + "data of compositionstart is empty (input) #4");
is(inputEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (input) #4");
is(windowEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by window #4");
is(windowEventData.compositionupdate, "\u3089",
kDescription + "data of compositionupdate doesn't match (window) #4");
is(windowEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (window) #4");
is(inputEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by input #4");
is(inputEventData.compositionupdate, "\u3089",
kDescription + "data of compositionupdate doesn't match (input) #4");
is(inputEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (input) #4");
is(windowEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by window #4");
is(windowEventData.compositionend, "\u3089",
kDescription + "data of compositionend doesn't match (window) #4");
is(windowEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (window) #4");
is(inputEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by input #4");
is(inputEventData.compositionend, "\u3089",
kDescription + "data of compositionend doesn't match (input) #4");
is(inputEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (input) #4");
is(windowEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by window #4");
is(windowEventData.input, "\u3089\u3089",
kDescription + "value of input element wasn't modified (window) #4");
is(inputEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by input #4");
is(inputEventData.input, "\u3089\u3089",
kDescription + "value of input element wasn't modified (input) #4");
// preventDefault() should effect nothing.
preventDefault = true;
initResults();
synthesizeKey("a", { accelKey: true }); // Select All
synthesizeCompositionChange(
{ "composition":
{ "string": "\u306D",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "," },
});
synthesizeComposition({ type: "compositioncommitasis" });
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by window #5");
is(windowEventData.compositionstart, "\u3089\u3089",
kDescription + "data of compositionstart is empty (window) #5");
is(windowEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (window) #5");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by input #5");
is(inputEventData.compositionstart, "\u3089\u3089",
kDescription + "data of compositionstart is empty (input) #5");
is(inputEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty (input) #5");
is(windowEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by window #5");
is(windowEventData.compositionupdate, "\u306D",
kDescription + "data of compositionupdate doesn't match (window) #5");
is(windowEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (window) #5");
is(inputEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by input #5");
is(inputEventData.compositionupdate, "\u306D",
kDescription + "data of compositionupdate doesn't match (input) #5");
is(inputEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty (input) #5");
is(windowEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by window #5");
is(windowEventData.compositionend, "\u306D",
kDescription + "data of compositionend doesn't match (window) #5");
is(windowEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (window) #5");
is(inputEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by input #5");
is(inputEventData.compositionend, "\u306D",
kDescription + "data of compositionend doesn't match (input) #5");
is(inputEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty (input) #5");
is(windowEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by window #5");
is(windowEventData.input, "\u306D",
kDescription + "value of input element wasn't modified (window) #5");
is(inputEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by input #5");
is(inputEventData.input, "\u306D",
kDescription + "value of input element wasn't modified (input) #5");
preventDefault = false;
// stopPropagation() should effect nothing (except event count)
stopPropagation = true;
initResults();
synthesizeKey("a", { accelKey: true }); // Select All
synthesizeCompositionChange(
{ "composition":
{ "string": "\u306E",
"clauses":
[
{ "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
]
},
"caret": { "start": 1, "length": 0 },
"key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
});
synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by window #6");
is(windowEventData.compositionstart, "\u306D",
kDescription + "data of compositionstart is empty #6");
is(windowEventLocale.compositionstart, "",
kDescription + "locale of compositionstart isn't empty #6");
is(inputEventCounts.compositionstart, 0,
kDescription + "compositionstart has been handled by input #6");
is(windowEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by window #6");
is(windowEventData.compositionupdate, "\u306E",
kDescription + "data of compositionupdate doesn't match #6");
is(windowEventLocale.compositionupdate, "",
kDescription + "locale of compositionupdate isn't empty #6");
is(inputEventCounts.compositionupdate, 0,
kDescription + "compositionupdate has been handled by input #6");
is(windowEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by window #6");
is(windowEventData.compositionend, "\u306E",
kDescription + "data of compositionend doesn't match #6");
is(windowEventLocale.compositionend, "",
kDescription + "locale of compositionend isn't empty #6");
is(inputEventCounts.compositionend, 0,
kDescription + "compositionend has been handled by input #6");
is(windowEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by window #6");
is(windowEventData.input, "\u306E",
kDescription + "value of input element wasn't modified (window) #6");
is(inputEventCounts.input, kExpectInputBeforeCompositionEnd ? 3 : 2,
kDescription + "input hasn't been handled by input #6");
is(inputEventData.input, "\u306E",
kDescription + "value of input element wasn't modified (input) #6");
stopPropagation = false;
// create event and dispatch it.
initResults();
input.value = "value of input";
synthesizeKey("a", { accelKey: true }); // Select All
let compositionstart = document.createEvent("CompositionEvent");
compositionstart.initCompositionEvent("compositionstart",
true, true, document.defaultView,
"start data", "start locale");
is(compositionstart.type, "compositionstart",
kDescription + "type doesn't match #7");
is(compositionstart.data, "start data",
kDescription + "data doesn't match #7");
is(compositionstart.locale, "start locale",
kDescription + "locale doesn't match #7");
is(compositionstart.detail, 0,
kDescription + "detail isn't 0 #7");
input.dispatchEvent(compositionstart);
is(windowEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by window #7");
is(windowEventData.compositionstart, "start data",
kDescription + "data of compositionstart was changed (window) #7");
is(windowEventLocale.compositionstart, "start locale",
kDescription + "locale of compositionstart was changed (window) #7");
is(inputEventCounts.compositionstart, 1,
kDescription + "compositionstart hasn't been handled by input #7");
is(inputEventData.compositionstart, "start data",
kDescription + "data of compositionstart was changed (input) #7");
is(inputEventLocale.compositionstart, "start locale",
kDescription + "locale of compositionstart was changed (input) #7");
is(input.value, "value of input",
kDescription + "input value was changed #7");
let compositionupdate1 = document.createEvent("compositionevent");
compositionupdate1.initCompositionEvent("compositionupdate",
true, false, document.defaultView,
"composing string", "composing locale");
is(compositionupdate1.type, "compositionupdate",
kDescription + "type doesn't match #8");
is(compositionupdate1.data, "composing string",
kDescription + "data doesn't match #8");
is(compositionupdate1.locale, "composing locale",
kDescription + "locale doesn't match #8");
is(compositionupdate1.detail, 0,
kDescription + "detail isn't 0 #8");
input.dispatchEvent(compositionupdate1);
is(windowEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by window #8");
is(windowEventData.compositionupdate, "composing string",
kDescription + "data of compositionupdate was changed (window) #8");
is(windowEventLocale.compositionupdate, "composing locale",
kDescription + "locale of compositionupdate was changed (window) #8");
is(inputEventCounts.compositionupdate, 1,
kDescription + "compositionupdate hasn't been handled by input #8");
is(inputEventData.compositionupdate, "composing string",
kDescription + "data of compositionupdate was changed (input) #8");
is(inputEventLocale.compositionupdate, "composing locale",
kDescription + "locale of compositionupdate was changed (input) #8");
is(input.value, "value of input",
kDescription + "input value was changed #8");
let compositionupdate2 = document.createEvent("compositionEvent");
compositionupdate2.initCompositionEvent("compositionupdate",
true, false, document.defaultView,
"commit string", "commit locale");
is(compositionupdate2.type, "compositionupdate",
kDescription + "type doesn't match #9");
is(compositionupdate2.data, "commit string",
kDescription + "data doesn't match #9");
is(compositionupdate2.locale, "commit locale",
kDescription + "locale doesn't match #9");
is(compositionupdate2.detail, 0,
kDescription + "detail isn't 0 #9");
input.dispatchEvent(compositionupdate2);
is(windowEventCounts.compositionupdate, 2,
kDescription + "compositionupdate hasn't been handled by window #9");
is(windowEventData.compositionupdate, "commit string",
kDescription + "data of compositionupdate was changed (window) #9");
is(windowEventLocale.compositionupdate, "commit locale",
kDescription + "locale of compositionupdate was changed (window) #9");
is(inputEventCounts.compositionupdate, 2,
kDescription + "compositionupdate hasn't been handled by input #9");
is(inputEventData.compositionupdate, "commit string",
kDescription + "data of compositionupdate was changed (input) #9");
is(inputEventLocale.compositionupdate, "commit locale",
kDescription + "locale of compositionupdate was changed (input) #9");
is(input.value, "value of input",
kDescription + "input value was changed #9");
let compositionend = document.createEvent("Compositionevent");
compositionend.initCompositionEvent("compositionend",
true, false, document.defaultView,
"end data", "end locale");
is(compositionend.type, "compositionend",
kDescription + "type doesn't match #10");
is(compositionend.data, "end data",
kDescription + "data doesn't match #10");
is(compositionend.locale, "end locale",
kDescription + "locale doesn't match #10");
is(compositionend.detail, 0,
kDescription + "detail isn't 0 #10");
input.dispatchEvent(compositionend);
is(windowEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by window #10");
is(windowEventData.compositionend, "end data",
kDescription + "data of compositionend was changed (window) #10");
is(windowEventLocale.compositionend, "end locale",
kDescription + "locale of compositionend was changed (window) #10");
is(inputEventCounts.compositionend, 1,
kDescription + "compositionend hasn't been handled by input #10");
is(inputEventData.compositionend, "end data",
kDescription + "data of compositionend was changed (input) #10");
is(inputEventLocale.compositionend, "end locale",
kDescription + "locale of compositionend was changed (input) #10");
is(input.value, "value of input",
kDescription + "input value was changed #10");
window.removeEventListener("compositionstart",
compositionEventHandlerForWindow, true);
window.removeEventListener("compositionend",
compositionEventHandlerForWindow, true);
window.removeEventListener("compositionupdate",
compositionEventHandlerForWindow, true);
window.removeEventListener("input",
formEventHandlerForWindow, true);
input.removeEventListener("compositionstart",
compositionEventHandlerForInput, true);
input.removeEventListener("compositionend",
compositionEventHandlerForInput, true);
input.removeEventListener("compositionupdate",
compositionEventHandlerForInput, true);
input.removeEventListener("input",
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()
{
contenteditable.focus();
contenteditable.innerHTML = "<p>abc</p><p>def</p>";
// \n 0 123 4 567
// \r\n 01 234 56 789
let description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
// "a"
let a = synthesizeQueryTextRect(kLFLen, 1);
if (!checkQueryContentResult(a, description + "rect for 'a'")) {
return;
}
// "b"
let b = synthesizeQueryTextRect(kLFLen + 1, 1);
if (!checkQueryContentResult(b, description + "rect for 'b'")) {
return;
}
is(b.top, a.top, description + "'a' and 'b' should be at same top");
isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
is(b.height, a.height, description + "'a' and 'b' should be same height");
// "c"
let c = synthesizeQueryTextRect(kLFLen + 2, 1);
if (!checkQueryContentResult(c, description + "rect for 'c'")) {
return;
}
is(c.top, b.top, description + "'b' and 'c' should be at same top");
isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
is(c.height, b.height, description + "'b' and 'c' should be same height");
// "abc" as array
let abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
!checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
return;
}
// 2nd <p> (can be computed with the rect of 'c')
let p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
return;
}
is(p2.top, c.top, description + "'c' and a line breaker caused by 2nd <p> should be at same top");
isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd <p> should be at similar to right of 'c'");
is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");
// 2nd <p> as array
let p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
!checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
let p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
return;
}
is(p2_2.top, p2.top, description + "'\\r' and '\\n' should be at same top");
is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
let p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
!checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <p> should match with each query text rect result")) {
return;
}
}
// "d"
let d = synthesizeQueryTextRect(kLFLen * 2 + 3, 1);
if (!checkQueryContentResult(d, description + "rect for 'd'")) {
return;
}
isGreaterThan(d.top, a.top + a.height, description + "top of 'd' should be greater than bottom of 'a'");
is(d.left, a.left, description + "'a' and 'd' should be same at same left");
is(d.height, a.height, description + "'a' and 'd' should be same height");
// "e"
let e = synthesizeQueryTextRect(kLFLen * 2 + 4, 1);
if (!checkQueryContentResult(e, description + "rect for 'e'")) {
return;
}
is(e.top, d.top, description + "'d' and 'd' should be at same top");
isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
is(e.height, d.height, description + "'d' and 'e' should be same height");
// "f"
let f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
if (!checkQueryContentResult(f, description + "rect for 'f'")) {
return;
}
is(f.top, e.top, description + "'e' and 'f' should be at same top");
isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
is(f.height, e.height, description + "'e' and 'f' should be same height");
// "def" as array
let defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
!checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
return;
}
// next of "f" (can be computed with rect of 'f')
let next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
return;
}
is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
// next of "f" as array
let next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
!checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
return;
}
// too big offset for the editor
let tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
return;
}
is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
// too big offset for the editors as array
let tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
// \n 0 123 4 567 8 9
// \r\n 01 234 56 789 01 23
description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
// "f"
f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
if (!checkQueryContentResult(f, description + "rect for 'f'")) {
return;
}
is(f.top, e.top, description + "'e' and 'f' should be at same top");
is(f.height, e.height, description + "'e' and 'f' should be same height");
isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
// 3rd <p> (can be computed with rect of 'f')
let p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
return;
}
is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
// 3rd <p> as array
let p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
!checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
return;
}
is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
!checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
return;
}
}
// <br> in 3rd <p>
let br = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(br, description + "rect for <br> in 3rd <p>")) {
return;
}
isGreaterThan(br.top, d.top + d.height, description + "a line breaker caused by <br> in 3rd <p> should be greater than bottom of 'd'");
isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");
// <br> in 3rd <p> as array
let brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
!checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
let br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
return;
}
is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
let br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
!checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> in 3rd <p> should match with each query text rect result")) {
return;
}
}
// next of <br> in 3rd <p>
let next_br = synthesizeQueryTextRect(kLFLen * 4 + 6, 1);
if (!checkQueryContentResult(next_br, description + "rect for next of <br> in 3rd <p>")) {
return;
}
is(next_br.top, br.top, description + "next of <br> and <br> should be at same top");
is(next_br.left, br.left, description + "next of <br> and <br> should be at same left");
is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
is(next_br.width, br.width, description + "next of <br> and <br> should be same width");
// next of <br> in 3rd <p> as array
let next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
!checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of <br> in 3rd <p> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
return;
}
is(tooBigOffset.top, next_br.top, description + "too big offset and next of 3rd <p> should be at same top");
is(tooBigOffset.left, next_br.left, description + "too big offset and next of 3rd <p> should be at same left");
is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
// \n 0 123 4 567 8
// \r\n 01 234 56 789 0
description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
// "f"
f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
if (!checkQueryContentResult(f, description + "rect for 'f'")) {
return;
}
is(f.top, e.top, description + "'e' and 'f' should be at same top");
isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
is(f.height, e.height, description + "'e' and 'f' should be same height");
// 3rd <p> (can be computed with rect of 'f')
p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
return;
}
is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");
// 3rd <p> as array
p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
!checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
return;
}
if (kLFLen > 1) {
// \n of \r\n
let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
return;
}
is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
// \n of \r\n as array
let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
!checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
return;
}
}
// next of 3rd <p>
let next_p3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(next_p3, description + "rect for next of 3rd <p>")) {
return;
}
isGreaterThan(next_p3.top, d.top + d.height, description + "top of next of 3rd <p> should equal to or be bigger than bottom of 'd'");
isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");
// next of 3rd <p> as array
let next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
!checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
return;
}
// too big offset for the editor
tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
return;
}
is(tooBigOffset.top, next_p3.top, description + "too big offset and next of 3rd <p> should be at same top");
is(tooBigOffset.left, next_p3.left, description + "too big offset and next of 3rd <p> should be at same left");
is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");
// too big offset for the editors as array
tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
!checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
return;
}
contenteditable.innerHTML = "abc<br>def";
// \n 0123 456
// \r\n 01234 567
description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
// "a"
a = synthesizeQueryTextRect(0, 1);
if (!checkQueryContentResult(a, description + "rect for 'a'")) {
return;
}
// "b"
b = synthesizeQueryTextRect(1, 1);
if (!checkQueryContentResult(b, description + "rect for 'b'")) {
return;
}
is(b.top, a.top, description + "'a' and 'b' should be at same top");
isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
is(b.height, a.height, description + "'a' and 'b' should be same height");
// "c"
c = synthesizeQueryTextRect(2, 1);
if (!checkQueryContentResult(c, description + "rect for 'c'")) {
return;
}
is(c.top, b.top, description + "'b' and 'c' should be at same top");
isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
is(c.height, b.height, description + "'b' and 'c' should be same height");
// "abc" as array
abcAsArray = synthesizeQueryTextRectArray(0, 3);
if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
!checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
return;
}
// <br> (can be computed with the rect of 'c')
br = synthesizeQueryTextRect(3, 1);
if (!checkQueryContentResult(br, description + "rect for <br>")) {
return;
}
is(br.top, c.top, description + "'c' and a line breaker caused by <br> should be at same top");
isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by <br> should be at similar to right of 'c'");
is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");
// <br> as array
brAsArray = synthesizeQueryTextRectArray(3, 1);
if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
!checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) { | |