Backed out changeset c8d01bb9a6a2 (bug 1866098) for causing bp-nu build bustages...
[gecko.git] / widget / tests / window_composition_text_querycontent.xhtml
blob083d2e3b46d9ad48f0bbab43a4b6519707601992
1 <?xml version="1.0"?>
2 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
3 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
4 type="text/css"?>
5 <window title="Testing composition, text and query content events"
6 xmlns:html="http://www.w3.org/1999/xhtml"
7 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
9 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
10 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
11 <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js" />
13 <panel id="panel" hidden="true" orient="vertical">
14 <vbox id="vbox">
15 <html:textarea id="textbox" cols="20" rows="4" style="font-size: 36px;"/>
16 </vbox>
17 </panel>
19 <body xmlns="http://www.w3.org/1999/xhtml">
20 <div id="display">
21 <div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
22 <textarea style="margin: 0; font-family: -moz-fixed;" id="textarea" cols="20" rows="4"></textarea><br/>
23 <iframe id="iframe" width="300" height="150"
24 src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
25 <iframe id="iframe2" width="300" height="150"
26 src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
27 <iframe id="iframe3" width="300" height="150"
28 src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
29 <iframe id="iframe4" width="300" height="150"
30 src="data:text/html,&lt;div contenteditable id='contenteditable'&gt;&lt;/div&gt;"></iframe><br/>
31 <!--
32 NOTE: the width for the next two iframes is chosen to be small enough to make
33 the Show Password button (for type=password) be outside the viewport so that
34 it doesn't affect the rendering compared to the type=text control.
35 But still large enough to comfortably fit the input values we test.
36 -->
37 <iframe id="iframe5" style="width:10ch" height="50" src="data:text/html,&lt;input id='input'&gt;"></iframe>
38 <iframe id="iframe6" style="width:10ch" height="50" src="data:text/html,&lt;input id='password' type='password'&gt;"></iframe><br/>
39 <iframe id="iframe7" width="300" height="150"
40 src="data:text/html,&lt;span contenteditable id='contenteditable'&gt;&lt;/span&gt;"></iframe><br/>
41 <input id="input" type="text"/><br/>
42 <input id="password" type="password"/><br/>
43 </div>
44 <div id="content" style="display: none">
46 </div>
47 <pre id="test">
48 </pre>
49 </body>
51 <script class="testbody" type="application/javascript">
52 <![CDATA[
54 function ok(aCondition, aMessage)
56 window.arguments[0].SimpleTest.ok(aCondition, aMessage);
59 function is(aLeft, aRight, aMessage)
61 window.arguments[0].SimpleTest.is(aLeft, aRight, aMessage);
64 function isnot(aLeft, aRight, aMessage)
66 window.arguments[0].SimpleTest.isnot(aLeft, aRight, aMessage);
69 function isfuzzy(aLeft, aRight, aEpsilon, aMessage) {
70 window.arguments[0].SimpleTest.isfuzzy(aLeft, aRight, aEpsilon, aMessage);
73 function todo(aCondition, aMessage)
75 window.arguments[0].SimpleTest.todo(aCondition, aMessage);
78 function todo_is(aLeft, aRight, aMessage)
80 window.arguments[0].SimpleTest.todo_is(aLeft, aRight, aMessage);
83 function todo_isnot(aLeft, aRight, aMessage)
85 window.arguments[0].SimpleTest.todo_isnot(aLeft, aRight, aMessage);
88 function isSimilarTo(aLeft, aRight, aAllowedDifference, aMessage)
90 if (Math.abs(aLeft - aRight) <= aAllowedDifference) {
91 ok(true, aMessage);
92 } else {
93 ok(false, aMessage + ", got=" + aLeft + ", expected=" + (aRight - aAllowedDifference) + "~" + (aRight + aAllowedDifference));
97 function isGreaterThan(aLeft, aRight, aMessage)
99 ok(aLeft > aRight, aMessage + ", got=" + aLeft + ", expected minimum value=" + aRight);
103 * synthesizeSimpleCompositionChange synthesizes a composition which has only
104 * one clause and put caret end of it.
106 * @param aComposition string or object. If string, it's treated as
107 * composition string whose attribute is
108 * COMPOSITION_ATTR_RAW_CLAUSE.
109 * If object, it must have .string whose type is "string".
110 * Additionally, .attr can be specified if you'd like to
111 * use the other attribute instead of
112 * COMPOSITION_ATTR_RAW_CLAUSE.
114 function synthesizeSimpleCompositionChange(aComposition, aWindow, aCallback) {
115 const comp = (() => {
116 if (typeof aComposition == "string") {
117 return { string: aComposition, attr: COMPOSITION_ATTR_RAW_CLAUSE };
119 return {
120 string: aComposition.string,
121 attr: aComposition.attr === undefined
122 ? COMPOSITION_ATTR_RAW_CLAUSE
123 : aComposition.attr
125 })();
126 synthesizeCompositionChange(
128 composition: {
129 string: comp.string,
130 clauses: [
131 { length: comp.string.length, attr: comp.attr },
134 caret: { start: comp.string.length, length: 0 },
136 aWindow,
137 aCallback
142 var div = document.getElementById("div");
143 var textarea = document.getElementById("textarea");
144 var panel = document.getElementById("panel");
145 var textbox = document.getElementById("textbox");
146 var iframe = document.getElementById("iframe");
147 var iframe2 = document.getElementById("iframe2");
148 var iframe3 = document.getElementById("iframe3");
149 var contenteditable;
150 var windowOfContenteditable;
151 var contenteditableBySpan;
152 var windowOfContenteditableBySpan;
153 var input = document.getElementById("input");
154 var password = document.getElementById("password");
155 var textareaInFrame;
157 const nsITextInputProcessorCallback = Ci.nsITextInputProcessorCallback;
158 const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
159 const nsIWebNavigation = Ci.nsIWebNavigation;
160 const nsIDocShell = Ci.nsIDocShell;
161 const { AppConstants } = ChromeUtils.importESModule(
162 "resource://gre/modules/AppConstants.sys.mjs"
165 function waitForTick() {
166 return new Promise(resolve => { SimpleTest.executeSoon(resolve); });
169 async function waitForEventLoops(aTimes)
171 for (let i = 1; i < aTimes; i++) {
172 await waitForTick();
174 await new Promise(resolve => { setTimeout(resolve, 20); });
177 function getEditor(aNode)
179 return aNode.editor;
182 function getHTMLEditorIMESupport(aWindow)
184 return aWindow.docShell.editor;
187 const kIsWin = (navigator.platform.indexOf("Win") == 0);
188 const kIsMac = (navigator.platform.indexOf("Mac") == 0);
190 const kLFLen = (kIsWin && !AppConstants.EARLY_BETA_OR_EARLIER) ? 2 : 1;
191 const kLF = (kIsWin && !AppConstants.EARLY_BETA_OR_EARLIER) ? "\r\n" : "\n";
193 function checkQueryContentResult(aResult, aMessage)
195 ok(aResult, aMessage + ": the result is null");
196 if (!aResult) {
197 return false;
199 ok(aResult.succeeded, aMessage + ": the query content failed");
200 return aResult.succeeded;
203 function checkContent(aExpectedText, aMessage, aID)
205 if (!aID) {
206 aID = "";
208 let textContent = synthesizeQueryTextContent(0, 100);
209 if (!checkQueryContentResult(textContent, aMessage +
210 ": synthesizeQueryTextContent " + aID)) {
211 return false;
213 is(textContent.text, aExpectedText,
214 aMessage + ": composition string is wrong " + aID);
215 return textContent.text == aExpectedText;
218 function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
220 if (!aID) {
221 aID = "";
223 aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
224 let textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
225 if (!checkQueryContentResult(textContent, aMessage +
226 "synthesizeQueryTextContent " + aID)) {
227 return false;
229 is(textContent.offset, aExpectedOffset,
230 aMessage + "offset is wrong " + aID);
231 is(textContent.text, aExpectedText,
232 aMessage + "text is wrong " + aID);
233 return textContent.offset == aExpectedOffset &&
234 textContent.text == aExpectedText;
237 function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
239 if (!aID) {
240 aID = "";
242 let selectedText = synthesizeQuerySelectedText();
243 if (!checkQueryContentResult(selectedText, aMessage +
244 ": synthesizeQuerySelectedText " + aID)) {
245 return false;
247 if (aExpectedOffset === null) {
249 selectedText.notFound,
250 true,
251 `${aMessage}: selection should not be found ${aID}`
253 return selectedText.notFound;
257 selectedText.notFound,
258 false,
259 `${aMessage}: selection should be found ${aID}`
261 if (selectedText.notFound) {
262 return false;
265 selectedText.offset,
266 aExpectedOffset,
267 `${aMessage}: selection offset should be ${aExpectedOffset} ${aID}`
270 selectedText.text,
271 aExpectedText,
272 `${aMessage}: selected text should be "${aExpectedText}" ${aID}`
274 return selectedText.offset == aExpectedOffset &&
275 selectedText.text == aExpectedText;
278 function checkIMESelection(
279 aSelectionType,
280 aExpectedFound,
281 aExpectedOffset,
282 aExpectedText,
283 aMessage,
284 aID,
285 aToDo = {}
287 if (!aID) {
288 aID = "";
290 aMessage += " (" + aSelectionType + ")";
291 let {
292 notFound = is,
293 offset = is,
294 text = is,
295 } = aToDo;
296 let selectionType = 0;
297 switch (aSelectionType) {
298 case "RawClause":
299 selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT;
300 break;
301 case "SelectedRawClause":
302 selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT;
303 break;
304 case "ConvertedClause":
305 selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT;
306 break;
307 case "SelectedClause":
308 selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT;
309 break;
310 default:
311 ok(false, aMessage + ": invalid selection type, " + aSelectionType);
313 isnot(selectionType, 0, aMessage + ": wrong value");
314 let selectedText = synthesizeQuerySelectedText(selectionType);
315 if (!checkQueryContentResult(selectedText, aMessage +
316 ": synthesizeQuerySelectedText " + aID)) {
317 return false;
319 notFound(
320 selectedText.notFound,
321 !aExpectedFound,
322 `${aMessage}: selection should ${
323 aExpectedFound ? "" : "not"
324 } be found ${aID}`);
325 if (selectedText.notFound) {
326 return selectedText.notFound == !aExpectedFound;
329 offset(
330 selectedText.offset,
331 aExpectedOffset,
332 `${aMessage}: selection offset is wrong ${aID}`
334 text(
335 selectedText.text,
336 aExpectedText,
337 `${aMessage}: selected text is wrong ${aID}`
339 return selectedText.offset == aExpectedOffset &&
340 selectedText.text == aExpectedText;
343 function checkRect(aRect, aExpectedRect, aMessage)
345 is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
346 is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
347 is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
348 is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
349 return aRect.left == aExpectedRect.left &&
350 aRect.top == aExpectedRect.top &&
351 aRect.width == aExpectedRect.width &&
352 aRect.height == aExpectedRect.height;
355 function checkRectFuzzy(aRect, aExpectedRect, aEpsilon, aMessage) {
356 isfuzzy(aRect.left, aExpectedRect.left, aEpsilon.left, aMessage + ": left is wrong");
357 isfuzzy(aRect.top, aExpectedRect.top, aEpsilon.top, aMessage + " top is wrong");
358 isfuzzy(aRect.width, aExpectedRect.width, aEpsilon.width, aMessage + ": width is wrong");
359 isfuzzy(aRect.height, aExpectedRect.height, aEpsilon.height, aMessage + ": height is wrong");
360 return (aRect.left >= aExpectedRect.left - aEpsilon.left &&
361 aRect.left <= aExpectedRect.left + aEpsilon.left) &&
362 (aRect.top >= aExpectedRect.top - aEpsilon.top &&
363 aRect.top <= aExpectedRect.top + aEpsilon.top) &&
364 (aRect.width >= aExpectedRect.width - aEpsilon.width &&
365 aRect.width <= aExpectedRect.width + aEpsilon.width) &&
366 (aRect.height >= aExpectedRect.height - aEpsilon.height &&
367 aRect.height <= aExpectedRect.height + aEpsilon.height);
370 function getRectArray(aQueryTextRectArrayResult) {
371 let rects = [];
372 for (let i = 0; ; i++) {
373 let rect = { left: {}, top: {}, width: {}, height: {} };
374 try {
375 aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
376 } catch (e) {
377 break;
379 rects.push({
380 left: rect.left.value,
381 top: rect.top.value,
382 width: rect.width.value,
383 height: rect.height.value,
386 return rects;
389 function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
391 for (let i = 1; i < aExpectedTextRectArray.length; ++i) {
392 let rect = { left: {}, top: {}, width: {}, height: {} };
393 try {
394 aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
395 } catch (e) {
396 ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
397 return false;
399 function toRect(aRect)
401 return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
403 if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
404 return false;
407 return true;
410 function checkRectContainsRect(aRect, aContainer, aMessage)
412 let container = { left: Math.ceil(aContainer.left),
413 top: Math.ceil(aContainer.top),
414 width: Math.floor(aContainer.width),
415 height: Math.floor(aContainer.height) };
417 let ret = container.left <= aRect.left &&
418 container.top <= aRect.top &&
419 container.left + container.width >= aRect.left + aRect.width &&
420 container.top + container.height >= aRect.top + aRect.height;
421 ret = ret && aMessage;
422 ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
423 container.top + ", width=" + container.width + ", height=" +
424 container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
425 ", width=" + aRect.width + ", height=" + aRect.height + " }");
426 return ret;
429 // eslint-disable-next-line complexity
430 function runUndoRedoTest()
432 textarea.value = "";
433 textarea.focus();
435 // input raw characters
436 synthesizeCompositionChange(
437 { "composition":
438 { "string": "\u306D",
439 "clauses":
441 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
444 "caret": { "start": 1, "length": 0 },
445 "key": { key: "," },
448 synthesizeCompositionChange(
449 { "composition":
450 { "string": "\u306D\u3053",
451 "clauses":
453 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
456 "caret": { "start": 2, "length": 0 },
457 "key": { key: "b" },
460 // convert
461 synthesizeCompositionChange(
462 { "composition":
463 { "string": "\u732B",
464 "clauses":
466 { "length": 1,
467 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
470 "caret": { "start": 1, "length": 0 },
471 "key": { key: " " },
474 // commit
475 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
477 // input raw characters
478 synthesizeCompositionChange(
479 { "composition":
480 { "string": "\u307E",
481 "clauses":
483 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
486 "caret": { "start": 1, "length": 0 },
487 "key": { key: "j" },
490 // cancel the composition
491 synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
493 // input raw characters
494 synthesizeCompositionChange(
495 { "composition":
496 { "string": "\u3080",
497 "clauses":
499 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
502 "caret": { "start": 1, "length": 0 },
503 "key": { key: "]" },
506 synthesizeCompositionChange(
507 { "composition":
508 { "string": "\u3080\u3059",
509 "clauses":
511 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
514 "caret": { "start": 2, "length": 0 },
515 "key": { key: "r" },
518 synthesizeCompositionChange(
519 { "composition":
520 { "string": "\u3080\u3059\u3081",
521 "clauses":
523 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
526 "caret": { "start": 3, "length": 0 },
527 "key": { key: "/" },
530 // convert
531 synthesizeCompositionChange(
532 { "composition":
533 { "string": "\u5A18",
534 "clauses":
536 { "length": 1,
537 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
540 "caret": { "start": 1, "length": 0 },
541 "key": { key: " " },
544 // commit
545 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
547 sendString(" meant");
548 synthesizeKey("KEY_Backspace");
549 synthesizeKey("s \"cat-girl\". She is a ");
551 // input raw characters
552 synthesizeCompositionChange(
553 { "composition":
554 { "string": "\u3088",
555 "clauses":
557 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
560 "caret": { "start": 1, "length": 0 },
561 "key": { key: "9" },
564 synthesizeCompositionChange(
565 { "composition":
566 { "string": "\u3088\u3046",
567 "clauses":
569 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
572 "caret": { "start": 2, "length": 0 },
573 "key": { key: "4" },
576 synthesizeCompositionChange(
577 { "composition":
578 { "string": "\u3088\u3046\u304b",
579 "clauses":
581 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
584 "caret": { "start": 3, "length": 0 },
585 "key": { key: "t" },
588 synthesizeCompositionChange(
589 { "composition":
590 { "string": "\u3088\u3046\u304b\u3044",
591 "clauses":
593 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
596 "caret": { "start": 4, "length": 0 },
597 "key": { key: "e" },
600 // convert
601 synthesizeCompositionChange(
602 { "composition":
603 { "string": "\u5996\u602a",
604 "clauses":
606 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
609 "caret": { "start": 2, "length": 0 },
610 "key": { key: " " },
613 // commit
614 synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
616 synthesizeKey("KEY_Backspace", {repeat: 12});
618 let i = 0;
619 if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
620 "runUndoRedoTest", "#" + ++i) ||
621 !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
622 return;
625 synthesizeKey("Z", {accelKey: true});
627 if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
628 "runUndoRedoTest", "#" + ++i) ||
629 !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
630 return;
633 synthesizeKey("Z", {accelKey: true});
635 if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
636 "runUndoRedoTest", "#" + ++i) ||
637 !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
638 return;
641 synthesizeKey("Z", {accelKey: true});
643 if (!checkContent("\u732B\u5A18 mean",
644 "runUndoRedoTest", "#" + ++i) ||
645 !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
646 return;
649 synthesizeKey("Z", {accelKey: true});
651 if (!checkContent("\u732B\u5A18 meant",
652 "runUndoRedoTest", "#" + ++i) ||
653 !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
654 return;
657 synthesizeKey("Z", {accelKey: true});
659 if (!checkContent("\u732B\u5A18",
660 "runUndoRedoTest", "#" + ++i) ||
661 !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
662 return;
665 synthesizeKey("Z", {accelKey: true});
667 if (!checkContent("\u732B",
668 "runUndoRedoTest", "#" + ++i) ||
669 !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
670 return;
673 synthesizeKey("Z", {accelKey: true});
675 // XXX this is unexpected behavior, see bug 258291
676 if (!checkContent("\u732B",
677 "runUndoRedoTest", "#" + ++i) ||
678 !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
679 return;
682 synthesizeKey("Z", {accelKey: true});
684 if (!checkContent("",
685 "runUndoRedoTest", "#" + ++i) ||
686 !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
687 return;
690 synthesizeKey("Z", {accelKey: true});
692 if (!checkContent("",
693 "runUndoRedoTest", "#" + ++i) ||
694 !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
695 return;
698 synthesizeKey("Z", {accelKey: true, shiftKey: true});
700 if (!checkContent("\u732B",
701 "runUndoRedoTest", "#" + ++i) ||
702 !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
703 return;
706 synthesizeKey("Z", {accelKey: true, shiftKey: true});
708 // XXX this is unexpected behavior, see bug 258291
709 if (!checkContent("\u732B",
710 "runUndoRedoTest", "#" + ++i) ||
711 !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
712 return;
715 synthesizeKey("Z", {accelKey: true, shiftKey: true});
717 if (!checkContent("\u732B\u5A18",
718 "runUndoRedoTest", "#" + ++i) ||
719 !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
720 return;
723 synthesizeKey("Z", {accelKey: true, shiftKey: true});
725 if (!checkContent("\u732B\u5A18 meant",
726 "runUndoRedoTest", "#" + ++i) ||
727 !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
728 return;
731 synthesizeKey("Z", {accelKey: true, shiftKey: true});
733 if (!checkContent("\u732B\u5A18 mean",
734 "runUndoRedoTest", "#" + ++i) ||
735 !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
736 return;
739 synthesizeKey("Z", {accelKey: true, shiftKey: true});
741 if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
742 "runUndoRedoTest", "#" + ++i) ||
743 !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
744 return;
747 synthesizeKey("Z", {accelKey: true, shiftKey: true});
749 if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
750 "runUndoRedoTest", "#" + ++i) ||
751 !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
752 return;
755 synthesizeKey("Z", {accelKey: true, shiftKey: true});
757 if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
758 "runUndoRedoTest", "#" + ++i) ||
759 !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
760 return;
763 synthesizeKey("Z", {accelKey: true, shiftKey: true});
765 if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
766 "runUndoRedoTest", "#" + ++i) ||
767 !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
768 // eslint-disable-next-line no-useless-return
769 return;
773 function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aTargetRanges, aDescription) {
774 if (aEvent.type !== "input" && aEvent.type !== "beforeinput") {
775 throw new Error(`${aDescription}: "${aEvent.type}" is not InputEvent`);
777 ok(InputEvent.isInstance(aEvent), `"${aEvent.type}" event should be dispatched with InputEvent interface: ${aDescription}`);
778 let cancelable = aEvent.type === "beforeinput" &&
779 aInputType !== "insertCompositionText" &&
780 aInputType !== "deleteCompositionText";
781 is(aEvent.cancelable, cancelable, `"${aEvent.type}" event should ${cancelable ? "be" : "be never"} cancelable: ${aDescription}`);
782 is(aEvent.bubbles, true, `"${aEvent.type}" event should always bubble: ${aDescription}`);
783 is(aEvent.isComposing, aIsComposing, `isComposing of "${aEvent.type}" event should be ${aIsComposing}: ${aDescription}`);
784 is(aEvent.inputType, aInputType, `inputType of "${aEvent.type}" event should be "${aInputType}": ${aDescription}`);
785 is(aEvent.data, aData, `data of "${aEvent.type}" event should be ${aData}: ${aDescription}`);
786 is(aEvent.dataTransfer, null, `dataTransfer of "${aEvent.type}" event should be null: ${aDescription}`);
787 let targetRanges = aEvent.getTargetRanges();
788 if (aTargetRanges.length === 0) {
789 is(targetRanges.length, 0,
790 `getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
791 } else {
792 is(targetRanges.length, aTargetRanges.length,
793 `getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
794 if (targetRanges.length == aTargetRanges.length) {
795 for (let i = 0; i < targetRanges.length; i++) {
796 is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
797 `startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
798 is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
799 `startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
800 is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
801 `endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
802 is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
803 `endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
809 function runCompositionCommitAsIsTest()
811 textarea.focus();
813 let result = [];
814 function clearResult()
816 result = [];
819 function handler(aEvent)
821 result.push(aEvent);
824 textarea.addEventListener("compositionupdate", handler, true);
825 textarea.addEventListener("compositionend", handler, true);
826 textarea.addEventListener("beforeinput", handler, true);
827 textarea.addEventListener("input", handler, true);
828 textarea.addEventListener("text", handler, true);
830 // compositioncommitasis with composing string.
831 textarea.value = "";
832 synthesizeCompositionChange(
833 { "composition":
834 { "string": "\u3042",
835 "clauses":
837 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
840 "caret": { "start": 1, "length": 0 },
841 "key": { key: "a" },
843 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
845 clearResult();
846 synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
848 is(result.length, 4,
849 "runCompositionCommitAsIsTest: 4 events should be fired after dispatching compositioncommitasis #1");
850 is(result[0].type, "text",
851 "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
852 is(result[1].type, "beforeinput",
853 "runCompositionCommitAsIsTest: beforeinput should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
854 checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
855 "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
856 is(result[2].type, "compositionend",
857 "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
858 is(result[3].type, "input",
859 "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
860 checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
861 "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
862 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
864 // compositioncommitasis with committed string.
865 textarea.value = "";
866 synthesizeCompositionChange(
867 { "composition":
868 { "string": "\u3042",
869 "clauses":
871 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
874 "caret": { "start": 1, "length": 0 },
875 "key": { key: "a" },
877 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
878 synthesizeCompositionChange(
879 { "composition":
880 { "string": "\u3042",
881 "clauses":
883 { "length": 0, "attr": 0 }
886 "caret": { "start": 1, "length": 0 },
887 "key": { key: "KEY_Enter", type: "keydown" },
889 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
891 clearResult();
892 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
894 is(result.length, 2,
895 "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
896 // XXX Do we need a "beforeinput" event here? Not sure.
897 is(result[0].type, "compositionend",
898 "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
899 is(result[1].type, "input",
900 "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
901 checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
902 "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
903 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
905 // compositioncommitasis with committed string.
906 textarea.value = "";
907 synthesizeCompositionChange(
908 { "composition":
909 { "string": "\u3042",
910 "clauses":
912 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
915 "caret": { "start": 1, "length": 0 },
916 "key": { key: "a" },
918 is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
919 synthesizeCompositionChange(
920 { "composition":
921 { "string": "",
922 "clauses":
924 { "length": 0, "attr": 0 }
927 "caret": { "start": 0, "length": 0 },
928 "key": { key: "KEY_Escape", type: "keydown" },
930 is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
932 clearResult();
933 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
935 is(result.length, 2,
936 "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
937 // XXX Do we need a "beforeinput" event here? Not sure.
938 is(result[0].type, "compositionend",
939 "runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
940 is(result[1].type, "input",
941 "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
942 checkInputEvent(result[1], false, "insertCompositionText", "", [],
943 "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
944 is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
946 textarea.removeEventListener("compositionupdate", handler, true);
947 textarea.removeEventListener("compositionend", handler, true);
948 textarea.removeEventListener("beforeinput", handler, true);
949 textarea.removeEventListener("input", handler, true);
950 textarea.removeEventListener("text", handler, true);
953 function runCompositionCommitTest()
955 textarea.focus();
957 let result = [];
958 function clearResult()
960 result = [];
963 function handler(aEvent)
965 result.push(aEvent);
968 textarea.addEventListener("compositionupdate", handler, true);
969 textarea.addEventListener("compositionend", handler, true);
970 textarea.addEventListener("beforeinput", handler, true);
971 textarea.addEventListener("input", handler, true);
972 textarea.addEventListener("text", handler, true);
974 // compositioncommit with different composing string.
975 textarea.value = "";
976 synthesizeCompositionChange(
977 { "composition":
978 { "string": "\u3042",
979 "clauses":
981 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
984 "caret": { "start": 1, "length": 0 },
985 "key": { key: "a", type: "keydown" },
987 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
989 clearResult();
990 synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
992 is(result.length, 5,
993 "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #1");
994 is(result[0].type, "compositionupdate",
995 "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
996 is(result[1].type, "text",
997 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
998 is(result[2].type, "beforeinput",
999 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
1000 checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
1001 "runCompositionCommitTest: after dispatching compositioncommit #1");
1002 is(result[3].type, "compositionend",
1003 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
1004 is(result[4].type, "input",
1005 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
1006 checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
1007 "runCompositionCommitTest: after dispatching compositioncommit #1");
1008 is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
1010 // compositioncommit with different committed string when there is already committed string
1011 textarea.value = "";
1012 synthesizeCompositionChange(
1013 { "composition":
1014 { "string": "\u3042",
1015 "clauses":
1017 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1020 "caret": { "start": 1, "length": 0 },
1021 "key": { key: "a" },
1023 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
1024 synthesizeCompositionChange(
1025 { "composition":
1026 { "string": "\u3042",
1027 "clauses":
1029 { "length": 0, "attr": 0 }
1032 "caret": { "start": 1, "length": 0 },
1033 "key": { key: "KEY_Enter", type: "keydown" },
1035 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
1037 clearResult();
1038 synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
1040 is(result.length, 5,
1041 "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #2");
1042 is(result[0].type, "compositionupdate",
1043 "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
1044 is(result[1].type, "text",
1045 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
1046 is(result[2].type, "beforeinput",
1047 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #2");
1048 checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
1049 "runCompositionCommitTest: after dispatching compositioncommit #2");
1050 is(result[3].type, "compositionend",
1051 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
1052 is(result[4].type, "input",
1053 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
1054 checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
1055 "runCompositionCommitTest: after dispatching compositioncommit #2");
1056 is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
1058 // compositioncommit with empty composition string.
1059 textarea.value = "";
1060 synthesizeCompositionChange(
1061 { "composition":
1062 { "string": "\u3042",
1063 "clauses":
1065 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1068 "caret": { "start": 1, "length": 0 },
1069 "key": { key: "a" },
1071 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
1072 synthesizeCompositionChange(
1073 { "composition":
1074 { "string": "",
1075 "clauses":
1077 { "length": 0, "attr": 0 }
1080 "caret": { "start": 0, "length": 0 },
1081 "key": { key: "KEY_Enter", type: "keydown" },
1083 is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
1085 clearResult();
1086 synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
1088 is(result.length, 5,
1089 "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #3");
1090 is(result[0].type, "compositionupdate",
1091 "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
1092 is(result[1].type, "text",
1093 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
1094 is(result[2].type, "beforeinput",
1095 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #3");
1096 checkInputEvent(result[2], true, "insertCompositionText", "\u3043", [],
1097 "runCompositionCommitTest: after dispatching compositioncommit #3");
1098 is(result[3].type, "compositionend",
1099 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
1100 is(result[4].type, "input",
1101 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
1102 checkInputEvent(result[4], false, "insertCompositionText", "\u3043", [],
1103 "runCompositionCommitTest: after dispatching compositioncommit #3");
1104 is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
1106 // inserting empty string with simple composition.
1107 textarea.value = "abc";
1108 textarea.setSelectionRange(3, 3);
1109 synthesizeComposition({ type: "compositionstart" });
1111 clearResult();
1112 synthesizeComposition({ type: "compositioncommit", data: "" });
1114 is(result.length, 4,
1115 "runCompositionCommitTest: 4 events should be fired when inserting empty string with composition");
1116 is(result[0].type, "text",
1117 "runCompositionCommitTest: text should be fired when inserting empty string with composition");
1118 is(result[1].type, "beforeinput",
1119 "runCompositionCommitTest: beforeinput should be fired when inserting empty string with composition");
1120 checkInputEvent(result[1], true, "insertCompositionText", "", [],
1121 "runCompositionCommitTest: when inserting empty string with composition");
1122 is(result[2].type, "compositionend",
1123 "runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
1124 is(result[3].type, "input",
1125 "runCompositionCommitTest: input should be fired when inserting empty string with composition");
1126 checkInputEvent(result[3], false, "insertCompositionText", "", [],
1127 "runCompositionCommitTest: when inserting empty string with composition");
1128 is(textarea.value, "abc",
1129 "runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");
1131 // replacing selection with empty string with simple composition.
1132 textarea.value = "abc";
1133 textarea.setSelectionRange(0, 3);
1134 synthesizeComposition({ type: "compositionstart" });
1136 clearResult();
1137 synthesizeComposition({ type: "compositioncommit", data: "" });
1139 is(result.length, 4,
1140 "runCompositionCommitTest: 4 events should be fired when replacing with empty string with composition");
1141 is(result[0].type, "text",
1142 "runCompositionCommitTest: text should be fired when replacing with empty string with composition");
1143 is(result[1].type, "beforeinput",
1144 "runCompositionCommitTest: beforeinput should be fired when replacing with empty string with composition");
1145 checkInputEvent(result[1], true, "insertCompositionText", "", [],
1146 "runCompositionCommitTest: when replacing with empty string with composition");
1147 is(result[2].type, "compositionend",
1148 "runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
1149 is(result[3].type, "input",
1150 "runCompositionCommitTest: input should be fired when replacing with empty string with composition");
1151 checkInputEvent(result[3], false, "insertCompositionText", "", [],
1152 "runCompositionCommitTest: when replacing with empty string with composition");
1153 is(textarea.value, "",
1154 "runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");
1156 // replacing selection with same string with simple composition.
1157 textarea.value = "abc";
1158 textarea.setSelectionRange(0, 3);
1159 synthesizeComposition({ type: "compositionstart" });
1161 clearResult();
1162 synthesizeComposition({ type: "compositioncommit", data: "abc" });
1164 is(result.length, 5,
1165 "runCompositionCommitTest: 5 events should be fired when replacing selection with same string with composition");
1166 is(result[0].type, "compositionupdate",
1167 "runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
1168 is(result[1].type, "text",
1169 "runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
1170 is(result[2].type, "beforeinput",
1171 "runCompositionCommitTest: beforeinput should be fired when replacing selection with same string with composition");
1172 checkInputEvent(result[2], true, "insertCompositionText", "abc", [],
1173 "runCompositionCommitTest: when replacing selection with same string with composition");
1174 is(result[3].type, "compositionend",
1175 "runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
1176 is(result[4].type, "input",
1177 "runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
1178 checkInputEvent(result[4], false, "insertCompositionText", "abc", [],
1179 "runCompositionCommitTest: when replacing selection with same string with composition");
1180 is(textarea.value, "abc",
1181 "runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");
1183 // compositioncommit with non-empty composition string.
1184 textarea.value = "";
1185 synthesizeCompositionChange(
1186 { "composition":
1187 { "string": "\u3042",
1188 "clauses":
1190 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1193 "caret": { "start": 1, "length": 0 },
1194 "key": { key: "a" },
1196 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
1198 clearResult();
1199 synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
1201 is(result.length, 5,
1202 "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #4");
1203 is(result[0].type, "compositionupdate",
1204 "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
1205 is(result[1].type, "text",
1206 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
1207 is(result[2].type, "beforeinput",
1208 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #4");
1209 checkInputEvent(result[2], true, "insertCompositionText", "", [],
1210 "runCompositionCommitTest: after dispatching compositioncommit #4");
1211 is(result[3].type, "compositionend",
1212 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
1213 is(result[4].type, "input",
1214 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
1215 checkInputEvent(result[4], false, "insertCompositionText", "", [],
1216 "runCompositionCommitTest: after dispatching compositioncommit #4");
1217 is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
1219 // compositioncommit immediately without compositionstart
1220 textarea.value = "";
1222 clearResult();
1223 synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
1225 is(result.length, 5,
1226 "runCompositionCommitTest: 5 events should be fired after dispatching compositioncommit #5");
1227 is(result[0].type, "compositionupdate",
1228 "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
1229 is(result[1].type, "text",
1230 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
1231 is(result[2].type, "beforeinput",
1232 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #5");
1233 checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
1234 "runCompositionCommitTest: after dispatching compositioncommit #5");
1235 is(result[3].type, "compositionend",
1236 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
1237 is(result[4].type, "input",
1238 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
1239 checkInputEvent(result[4], false, "insertCompositionText", "\u3042", [],
1240 "runCompositionCommitTest: after dispatching compositioncommit #5");
1241 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
1243 // compositioncommit with same composition string.
1244 textarea.value = "";
1245 synthesizeCompositionChange(
1246 { "composition":
1247 { "string": "\u3042",
1248 "clauses":
1250 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1253 "caret": { "start": 1, "length": 0 },
1254 "key": { key: "a" },
1256 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
1258 clearResult();
1259 synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
1261 is(result.length, 4,
1262 "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #6");
1263 is(result[0].type, "text",
1264 "runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
1265 is(result[1].type, "beforeinput",
1266 "runCompositionCommitTest: beforeinput should be fired after dispatching compositioncommit #6");
1267 checkInputEvent(result[1], true, "insertCompositionText", "\u3042", [],
1268 "runCompositionCommitTest: after dispatching compositioncommit #6");
1269 is(result[2].type, "compositionend",
1270 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
1271 is(result[3].type, "input",
1272 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
1273 checkInputEvent(result[3], false, "insertCompositionText", "\u3042", [],
1274 "runCompositionCommitTest: after dispatching compositioncommit #6");
1275 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
1277 // compositioncommit with same composition string when there is committed string
1278 textarea.value = "";
1279 synthesizeCompositionChange(
1280 { "composition":
1281 { "string": "\u3042",
1282 "clauses":
1284 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1287 "caret": { "start": 1, "length": 0 },
1288 "key": { key: "a" },
1290 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
1292 synthesizeCompositionChange(
1293 { "composition":
1294 { "string": "\u3042",
1295 "clauses":
1297 { "length": 0, "attr": 0 }
1300 "caret": { "start": 1, "length": 0 },
1301 "key": { key: "KEY_Enter", type: "keydown" },
1303 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
1305 clearResult();
1306 synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
1308 is(result.length, 2,
1309 "runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
1310 // XXX Do we need a "beforeinput" event here? Not sure.
1311 is(result[0].type, "compositionend",
1312 "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
1313 is(result[1].type, "input",
1314 "runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
1315 checkInputEvent(result[1], false, "insertCompositionText", "\u3042", [],
1316 "runCompositionCommitTest: after dispatching compositioncommit #7");
1317 is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
1319 textarea.removeEventListener("compositionupdate", handler, true);
1320 textarea.removeEventListener("compositionend", handler, true);
1321 textarea.removeEventListener("beforeinput", handler, true);
1322 textarea.removeEventListener("input", handler, true);
1323 textarea.removeEventListener("text", handler, true);
1326 // eslint-disable-next-line complexity
1327 async function runCompositionTest()
1329 textarea.value = "";
1330 textarea.focus();
1331 let caretRects = [];
1333 let caretRect = synthesizeQueryCaretRect(0);
1334 if (!checkQueryContentResult(caretRect,
1335 "runCompositionTest: synthesizeQueryCaretRect #0")) {
1336 return;
1338 caretRects[0] = caretRect;
1340 // input first character
1341 synthesizeCompositionChange(
1342 { "composition":
1343 { "string": "\u3089",
1344 "clauses":
1346 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1349 "caret": { "start": 1, "length": 0 },
1350 "key": { key: "o" },
1353 if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
1354 !checkSelection(1, "", "runCompositionTest", "#1-1")) {
1355 return;
1358 caretRect = synthesizeQueryCaretRect(1);
1359 if (!checkQueryContentResult(caretRect,
1360 "runCompositionTest: synthesizeQueryCaretRect #1-1")) {
1361 return;
1363 caretRects[1] = caretRect;
1365 // input second character
1366 synthesizeCompositionChange(
1367 { "composition":
1368 { "string": "\u3089\u30FC",
1369 "clauses":
1371 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1374 "caret": { "start": 2, "length": 0 },
1375 "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
1378 if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
1379 !checkSelection(2, "", "runCompositionTest", "#1-2")) {
1380 return;
1383 caretRect = synthesizeQueryCaretRect(2);
1384 if (!checkQueryContentResult(caretRect,
1385 "runCompositionTest: synthesizeQueryCaretRect #1-2")) {
1386 return;
1388 caretRects[2] = caretRect;
1390 isnot(caretRects[2].left, caretRects[1].left,
1391 "runCompositionTest: caret isn't moved (#1-2)");
1392 is(caretRects[2].top, caretRects[1].top,
1393 "runCompositionTest: caret is moved to another line (#1-2)");
1394 is(caretRects[2].width, caretRects[1].width,
1395 "runCompositionTest: caret width is wrong (#1-2)");
1396 is(caretRects[2].height, caretRects[1].height,
1397 "runCompositionTest: caret width is wrong (#1-2)");
1399 // input third character
1400 synthesizeCompositionChange(
1401 { "composition":
1402 { "string": "\u3089\u30FC\u3081",
1403 "clauses":
1405 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1408 "caret": { "start": 3, "length": 0 },
1409 "key": { key: "/" },
1412 if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
1413 !checkSelection(3, "", "runCompositionTest", "#1-3")) {
1414 return;
1417 caretRect = synthesizeQueryCaretRect(3);
1418 if (!checkQueryContentResult(caretRect,
1419 "runCompositionTest: synthesizeQueryCaretRect #1-3")) {
1420 return;
1422 caretRects[3] = caretRect;
1424 isnot(caretRects[3].left, caretRects[2].left,
1425 "runCompositionTest: caret isn't moved (#1-3)");
1426 is(caretRects[3].top, caretRects[2].top,
1427 "runCompositionTest: caret is moved to another line (#1-3)");
1428 is(caretRects[3].width, caretRects[2].width,
1429 "runCompositionTest: caret width is wrong (#1-3)");
1430 is(caretRects[3].height, caretRects[2].height,
1431 "runCompositionTest: caret height is wrong (#1-3)");
1433 // moves the caret left
1434 synthesizeCompositionChange(
1435 { "composition":
1436 { "string": "\u3089\u30FC\u3081",
1437 "clauses":
1439 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1442 "caret": { "start": 2, "length": 0 },
1443 "key": { key: "KEY_ArrowLeft" },
1446 if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
1447 !checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
1448 return;
1452 caretRect = synthesizeQueryCaretRect(2);
1453 if (!checkQueryContentResult(caretRect,
1454 "runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
1455 return;
1458 is(caretRect.left, caretRects[2].left,
1459 "runCompositionTest: caret rects are different (#1-3-1, left)");
1460 is(caretRect.top, caretRects[2].top,
1461 "runCompositionTest: caret rects are different (#1-3-1, top)");
1462 // by bug 335359, the caret width depends on the right side's character.
1463 is(caretRect.width, caretRects[2].width + Math.round(window.devicePixelRatio),
1464 "runCompositionTest: caret rects are different (#1-3-1, width)");
1465 is(caretRect.height, caretRects[2].height,
1466 "runCompositionTest: caret rects are different (#1-3-1, height)");
1468 // moves the caret left
1469 synthesizeCompositionChange(
1470 { "composition":
1471 { "string": "\u3089\u30FC\u3081",
1472 "clauses":
1474 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1477 "caret": { "start": 1, "length": 0 },
1478 "key": { key: "KEY_ArrowLeft" },
1481 if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
1482 !checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
1483 return;
1487 caretRect = synthesizeQueryCaretRect(1);
1488 if (!checkQueryContentResult(caretRect,
1489 "runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
1490 return;
1493 is(caretRect.left, caretRects[1].left,
1494 "runCompositionTest: caret rects are different (#1-3-2, left)");
1495 is(caretRect.top, caretRects[1].top,
1496 "runCompositionTest: caret rects are different (#1-3-2, top)");
1497 // by bug 335359, the caret width depends on the right side's character.
1498 is(caretRect.width, caretRects[1].width + Math.round(window.devicePixelRatio),
1499 "runCompositionTest: caret rects are different (#1-3-2, width)");
1500 is(caretRect.height, caretRects[1].height,
1501 "runCompositionTest: caret rects are different (#1-3-2, height)");
1503 synthesizeCompositionChange(
1504 { "composition":
1505 { "string": "\u3089\u30FC\u3081\u3093",
1506 "clauses":
1508 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1511 "caret": { "start": 4, "length": 0 },
1512 "key": { key: "y" },
1515 if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
1516 !checkSelection(4, "", "runCompositionTest", "#1-4")) {
1517 return;
1521 // backspace
1522 synthesizeCompositionChange(
1523 { "composition":
1524 { "string": "\u3089\u30FC\u3081",
1525 "clauses":
1527 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1530 "caret": { "start": 3, "length": 0 },
1531 "key": { key: "KEY_Backspace" },
1534 if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
1535 !checkSelection(3, "", "runCompositionTest", "#1-5")) {
1536 return;
1539 // re-input
1540 synthesizeCompositionChange(
1541 { "composition":
1542 { "string": "\u3089\u30FC\u3081\u3093",
1543 "clauses":
1545 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1548 "caret": { "start": 4, "length": 0 },
1549 "key": { key: "y" },
1552 if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
1553 !checkSelection(4, "", "runCompositionTest", "#1-6")) {
1554 return;
1557 synthesizeCompositionChange(
1558 { "composition":
1559 { "string": "\u3089\u30FC\u3081\u3093\u3055",
1560 "clauses":
1562 { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1565 "caret": { "start": 5, "length": 0 },
1566 "key": { key: "x" },
1569 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
1570 !checkSelection(5, "", "runCompositionTest", "#1-7")) {
1571 return;
1574 synthesizeCompositionChange(
1575 { "composition":
1576 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
1577 "clauses":
1579 { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1582 "caret": { "start": 6, "length": 0 },
1583 "key": { key: "e" },
1586 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
1587 !checkSelection(6, "", "runCompositionTest", "#1-8")) {
1588 return;
1591 synthesizeCompositionChange(
1592 { "composition":
1593 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
1594 "clauses":
1596 { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1599 "caret": { "start": 7, "length": 0 },
1600 "key": { key: "b" },
1603 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
1604 !checkSelection(7, "", "runCompositionTest", "#1-8")) {
1605 return;
1608 synthesizeCompositionChange(
1609 { "composition":
1610 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
1611 "clauses":
1613 { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1616 "caret": { "start": 8, "length": 0 },
1617 "key": { key: "4" },
1620 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
1621 "runCompositionTest", "#1-9") ||
1622 !checkSelection(8, "", "runCompositionTest", "#1-9")) {
1623 return;
1626 // convert
1627 synthesizeCompositionChange(
1628 { "composition":
1629 { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
1630 "clauses":
1632 { "length": 4,
1633 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
1634 { "length": 2,
1635 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
1638 "caret": { "start": 4, "length": 0 },
1639 "key": { key: " " },
1642 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
1643 "runCompositionTest", "#1-10") ||
1644 !checkSelection(4, "", "runCompositionTest", "#1-10")) {
1645 return;
1648 // change the selected clause
1649 synthesizeCompositionChange(
1650 { "composition":
1651 { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
1652 "clauses":
1654 { "length": 4,
1655 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
1656 { "length": 2,
1657 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
1660 "caret": { "start": 6, "length": 0 },
1661 "key": { key: "KEY_ArrowLeft", shiftKey: true },
1664 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
1665 "runCompositionTest", "#1-11") ||
1666 !checkSelection(6, "", "runCompositionTest", "#1-11")) {
1667 return;
1670 // reset clauses
1671 synthesizeCompositionChange(
1672 { "composition":
1673 { "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
1674 "clauses":
1676 { "length": 5,
1677 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
1678 { "length": 3,
1679 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
1682 "caret": { "start": 5, "length": 0 },
1683 "key": { key: "KEY_ArrowRight" },
1686 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
1687 "runCompositionTest", "#1-12") ||
1688 !checkSelection(5, "", "runCompositionTest", "#1-12")) {
1689 return;
1693 let textRect1 = synthesizeQueryTextRect(0, 1);
1694 let textRect2 = synthesizeQueryTextRect(1, 1);
1695 if (!checkQueryContentResult(textRect1,
1696 "runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
1697 !checkQueryContentResult(textRect2,
1698 "runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
1699 return;
1702 // commit the composition string
1703 synthesizeComposition({ type: "compositioncommitasis" });
1705 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
1706 "runCompositionTest", "#1-13") ||
1707 !checkSelection(8, "", "runCompositionTest", "#1-13")) {
1708 return;
1711 let textRect3 = synthesizeQueryTextRect(0, 1);
1712 let textRect4 = synthesizeQueryTextRect(1, 1);
1714 if (!checkQueryContentResult(textRect3,
1715 "runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
1716 !checkQueryContentResult(textRect4,
1717 "runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
1718 return;
1721 checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
1722 checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");
1724 // restart composition and input characters
1725 synthesizeCompositionChange(
1726 { "composition":
1727 { "string": "\u3057",
1728 "clauses":
1730 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1733 "caret": { "start": 1, "length": 0 },
1734 "key": { key: "d" },
1737 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
1738 "runCompositionTest", "#2-1") ||
1739 !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
1740 return;
1743 let textRect3QueriedWithRelativeOffset = synthesizeQueryTextRect(-8, 1, true);
1744 let textRect4QueriedWithRelativeOffset = synthesizeQueryTextRect(-8 + 1, 1, true);
1745 checkRect(textRect3QueriedWithRelativeOffset, textRect3, "runCompositionTest: textRect #2-1-2");
1746 checkRect(textRect4QueriedWithRelativeOffset, textRect4, "runCompositionTest: textRect #2-1-3");
1748 synthesizeCompositionChange(
1749 { "composition":
1750 { "string": "\u3058",
1751 "clauses":
1753 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1756 "caret": { "start": 1, "length": 0 },
1757 "key": { key: "r" },
1760 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
1761 "runCompositionTest", "#2-2") ||
1762 !checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
1763 return;
1766 synthesizeCompositionChange(
1767 { "composition":
1768 { "string": "\u3058\u3087",
1769 "clauses":
1771 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1774 "caret": { "start": 2, "length": 0 },
1775 "key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
1778 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
1779 "runCompositionTest", "#2-3") ||
1780 !checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
1781 return;
1784 synthesizeCompositionChange(
1785 { "composition":
1786 { "string": "\u3058\u3087\u3046",
1787 "clauses":
1789 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1792 "caret": { "start": 3, "length": 0 },
1793 "key": { key: "4" },
1796 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
1797 "runCompositionTest", "#2-4") ||
1798 !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
1799 return;
1802 // commit the composition string
1803 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
1805 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
1806 "runCompositionTest", "#2-4") ||
1807 !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
1808 return;
1811 // set selection
1812 const selectionSetTest = await synthesizeSelectionSet(4, 7, false);
1813 ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");
1815 if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
1816 return;
1819 // start composition with selection
1820 synthesizeCompositionChange(
1821 { "composition":
1822 { "string": "\u304A",
1823 "clauses":
1825 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1828 "caret": { "start": 1, "length": 0 },
1829 "key": { key: "6" },
1832 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
1833 "runCompositionTest", "#3-2") ||
1834 !checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
1835 return;
1838 // remove the composition string
1839 synthesizeCompositionChange(
1840 { "composition":
1841 { "string": "",
1842 "clauses":
1844 { "length": 0, "attr": 0 }
1847 "caret": { "start": 0, "length": 0 },
1848 "key": { key: "KEY_Backspace" },
1851 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1852 "runCompositionTest", "#3-3") ||
1853 !checkSelection(4, "", "runCompositionTest", "#3-3")) {
1854 return;
1857 // re-input the composition string
1858 synthesizeCompositionChange(
1859 { "composition":
1860 { "string": "\u3046",
1861 "clauses":
1863 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
1866 "caret": { "start": 1, "length": 0 },
1867 "key": { key: "4" },
1870 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
1871 "runCompositionTest", "#3-4") ||
1872 !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
1873 return;
1876 // cancel the composition
1877 synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
1879 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1880 "runCompositionTest", "#3-5") ||
1881 !checkSelection(4, "", "runCompositionTest", "#3-5")) {
1882 return;
1885 // bug 271815, some Chinese IMEs for Linux make empty composition string
1886 // and compty clause information when it lists up Chinese characters on
1887 // its candidate window.
1888 synthesizeCompositionChange(
1889 { "composition":
1890 { "string": "",
1891 "clauses":
1893 { "length": 0, "attr": 0 }
1896 "caret": { "start": 0, "length": 0 },
1897 "key": { key: "a" },
1900 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1901 "runCompositionTest", "#4-1") ||
1902 !checkSelection(4, "", "runCompositionTest", "#4-1")) {
1903 return;
1906 synthesizeCompositionChange(
1907 { "composition":
1908 { "string": "",
1909 "clauses":
1911 { "length": 0, "attr": 0 }
1914 "caret": { "start": 0, "length": 0 },
1915 "key": { key: "b" },
1918 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1919 "runCompositionTest", "#4-2") ||
1920 !checkSelection(4, "", "runCompositionTest", "#4-2")) {
1921 return;
1924 synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
1925 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
1926 "runCompositionTest", "#4-3") ||
1927 !checkSelection(5, "", "runCompositionTest", "#4-3")) {
1928 return;
1931 // testing the canceling case
1932 synthesizeCompositionChange(
1933 { "composition":
1934 { "string": "",
1935 "clauses":
1937 { "length": 0, "attr": 0 }
1940 "caret": { "start": 0, "length": 0 },
1941 "key": { key: "a" },
1944 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
1945 "runCompositionTest", "#4-5") ||
1946 !checkSelection(5, "", "runCompositionTest", "#4-5")) {
1947 return;
1950 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });
1952 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
1953 "runCompositionTest", "#4-6") ||
1954 !checkSelection(5, "", "runCompositionTest", "#4-6")) {
1955 return;
1958 // testing whether the empty composition string deletes selected string.
1959 synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
1961 synthesizeCompositionChange(
1962 { "composition":
1963 { "string": "",
1964 "clauses":
1966 { "length": 0, "attr": 0 }
1969 "caret": { "start": 0, "length": 0 },
1970 "key": { key: "a" },
1973 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1974 "runCompositionTest", "#4-8") ||
1975 !checkSelection(4, "", "runCompositionTest", "#4-8")) {
1976 return;
1979 synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
1980 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
1981 "runCompositionTest", "#4-9") ||
1982 !checkSelection(5, "", "runCompositionTest", "#4-9")) {
1983 return;
1986 synthesizeKey("KEY_Backspace");
1987 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
1988 "runCompositionTest", "#4-11") ||
1989 !checkSelection(4, "", "runCompositionTest", "#4-11")) {
1990 return;
1993 // bug 23558, ancient Japanese IMEs on Window may send empty text event
1994 // twice at canceling composition.
1995 synthesizeCompositionChange(
1996 { "composition":
1997 { "string": "\u6700",
1998 "clauses":
2000 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2003 "caret": { "start": 1, "length": 0 },
2004 "key": { key: "a" },
2007 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
2008 "runCompositionTest", "#5-1") ||
2009 !checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
2010 return;
2013 synthesizeCompositionChange(
2014 { "composition":
2015 { "string": "",
2016 "clauses":
2018 { "length": 0, "attr": 0 }
2021 "caret": { "start": 0, "length": 0 },
2022 "key": { key: "KEY_Backspace", type: "keydown" },
2025 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
2026 "runCompositionTest", "#5-2") ||
2027 !checkSelection(4, "", "runCompositionTest", "#5-2")) {
2028 return;
2031 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
2032 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
2033 "runCompositionTest", "#5-3") ||
2034 !checkSelection(4, "", "runCompositionTest", "#5-3")) {
2035 return;
2038 // Undo tests for the testcases for bug 23558 and bug 271815
2039 synthesizeKey("z", { accelKey: true });
2041 // XXX this is unexpected behavior, see bug 258291
2042 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
2043 "runCompositionTest", "#6-1") ||
2044 !checkSelection(4, "", "runCompositionTest", "#6-1")) {
2045 return;
2048 synthesizeKey("z", { accelKey: true });
2050 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
2051 "runCompositionTest", "#6-2") ||
2052 !checkSelection(5, "", "runCompositionTest", "#6-2")) {
2053 return;
2056 synthesizeKey("z", { accelKey: true });
2058 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
2059 "runCompositionTest", "#6-3") ||
2060 !checkSelection(4, "", "runCompositionTest", "#6-3")) {
2061 return;
2064 synthesizeKey("z", { accelKey: true });
2066 // XXX this is unexpected behavior, see bug 258291
2067 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
2068 "runCompositionTest", "#6-4") ||
2069 !checkSelection(5, "", "runCompositionTest", "#6-4")) {
2070 return;
2073 synthesizeKey("z", { accelKey: true });
2075 if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
2076 "runCompositionTest", "#6-5") ||
2077 !checkSelection(4, "", "runCompositionTest", "#6-5")) {
2078 // eslint-disable-next-line no-useless-return
2079 return;
2083 function runCompositionEventTest()
2085 const kDescription = "runCompositionEventTest: ";
2086 const kEvents = ["compositionstart", "compositionupdate", "compositionend",
2087 "input"];
2089 input.value = "";
2090 input.focus();
2092 let windowEventCounts = [], windowEventData = [], windowEventLocale = [];
2093 let inputEventCounts = [], inputEventData = [], inputEventLocale = [];
2094 let preventDefault = false;
2095 let stopPropagation = false;
2097 function initResults()
2099 for (let i = 0; i < kEvents.length; i++) {
2100 windowEventCounts[kEvents[i]] = 0;
2101 windowEventData[kEvents[i]] = "";
2102 windowEventLocale[kEvents[i]] = "";
2103 inputEventCounts[kEvents[i]] = 0;
2104 inputEventData[kEvents[i]] = "";
2105 inputEventLocale[kEvents[i]] = "";
2109 function compositionEventHandlerForWindow(aEvent)
2111 windowEventCounts[aEvent.type]++;
2112 windowEventData[aEvent.type] = aEvent.data;
2113 windowEventLocale[aEvent.type] = aEvent.locale;
2114 if (preventDefault) {
2115 aEvent.preventDefault();
2117 if (stopPropagation) {
2118 aEvent.stopPropagation();
2122 function formEventHandlerForWindow(aEvent)
2124 ok(aEvent.isTrusted, "input events must be trusted events");
2125 windowEventCounts[aEvent.type]++;
2126 windowEventData[aEvent.type] = input.value;
2129 function compositionEventHandlerForInput(aEvent)
2131 inputEventCounts[aEvent.type]++;
2132 inputEventData[aEvent.type] = aEvent.data;
2133 inputEventLocale[aEvent.type] = aEvent.locale;
2134 if (preventDefault) {
2135 aEvent.preventDefault();
2137 if (stopPropagation) {
2138 aEvent.stopPropagation();
2142 function formEventHandlerForInput(aEvent)
2144 inputEventCounts[aEvent.type]++;
2145 inputEventData[aEvent.type] = input.value;
2148 window.addEventListener("compositionstart", compositionEventHandlerForWindow,
2149 true, true);
2150 window.addEventListener("compositionend", compositionEventHandlerForWindow,
2151 true, true);
2152 window.addEventListener("compositionupdate", compositionEventHandlerForWindow,
2153 true, true);
2154 window.addEventListener("input", formEventHandlerForWindow,
2155 true, true);
2157 input.addEventListener("compositionstart", compositionEventHandlerForInput,
2158 true, true);
2159 input.addEventListener("compositionend", compositionEventHandlerForInput,
2160 true, true);
2161 input.addEventListener("compositionupdate", compositionEventHandlerForInput,
2162 true, true);
2163 input.addEventListener("input", formEventHandlerForInput,
2164 true, true);
2166 // test for normal case
2167 initResults();
2169 synthesizeCompositionChange(
2170 { "composition":
2171 { "string": "\u3089",
2172 "clauses":
2174 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2177 "caret": { "start": 1, "length": 0 },
2178 "key": { key: "o" },
2181 is(windowEventCounts.compositionstart, 1,
2182 kDescription + "compositionstart hasn't been handled by window #1");
2183 is(windowEventData.compositionstart, "",
2184 kDescription + "data of compositionstart isn't empty (window) #1");
2185 is(windowEventLocale.compositionstart, "",
2186 kDescription + "locale of compositionstart isn't empty (window) #1");
2187 is(inputEventCounts.compositionstart, 1,
2188 kDescription + "compositionstart hasn't been handled by input #1");
2189 is(inputEventData.compositionstart, "",
2190 kDescription + "data of compositionstart isn't empty (input) #1");
2191 is(inputEventLocale.compositionstart, "",
2192 kDescription + "locale of compositionstart isn't empty (input) #1");
2194 is(windowEventCounts.compositionupdate, 1,
2195 kDescription + "compositionupdate hasn't been handled by window #1");
2196 is(windowEventData.compositionupdate, "\u3089",
2197 kDescription + "data of compositionupdate doesn't match (window) #1");
2198 is(windowEventLocale.compositionupdate, "",
2199 kDescription + "locale of compositionupdate isn't empty (window) #1");
2200 is(inputEventCounts.compositionupdate, 1,
2201 kDescription + "compositionupdate hasn't been handled by input #1");
2202 is(inputEventData.compositionupdate, "\u3089",
2203 kDescription + "data of compositionupdate doesn't match (input) #1");
2204 is(inputEventLocale.compositionupdate, "",
2205 kDescription + "locale of compositionupdate isn't empty (input) #1");
2207 is(windowEventCounts.compositionend, 0,
2208 kDescription + "compositionend has been handled by window #1");
2209 is(inputEventCounts.compositionend, 0,
2210 kDescription + "compositionend has been handled by input #1");
2212 is(windowEventCounts.input, 1,
2213 kDescription + "input hasn't been handled by window #1");
2214 is(windowEventData.input, "\u3089",
2215 kDescription + "value of input element wasn't modified (window) #1");
2216 is(inputEventCounts.input, 1,
2217 kDescription + "input hasn't been handled by input #1");
2218 is(inputEventData.input, "\u3089",
2219 kDescription + "value of input element wasn't modified (input) #1");
2221 synthesizeCompositionChange(
2222 { "composition":
2223 { "string": "\u3089\u30FC",
2224 "clauses":
2226 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2229 "caret": { "start": 2, "length": 0 },
2230 "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
2233 is(windowEventCounts.compositionstart, 1,
2234 kDescription + "compositionstart has been handled more than once by window #2");
2235 is(inputEventCounts.compositionstart, 1,
2236 kDescription + "compositionstart has been handled more than once by input #2");
2238 is(windowEventCounts.compositionupdate, 2,
2239 kDescription + "compositionupdate hasn't been handled by window #2");
2240 is(windowEventData.compositionupdate, "\u3089\u30FC",
2241 kDescription + "data of compositionupdate doesn't match (window) #2");
2242 is(windowEventLocale.compositionupdate, "",
2243 kDescription + "locale of compositionupdate isn't empty (window) #2");
2244 is(inputEventCounts.compositionupdate, 2,
2245 kDescription + "compositionupdate hasn't been handled by input #2");
2246 is(inputEventData.compositionupdate, "\u3089\u30FC",
2247 kDescription + "data of compositionupdate doesn't match (input) #2");
2248 is(inputEventLocale.compositionupdate, "",
2249 kDescription + "locale of compositionupdate isn't empty (input) #2");
2251 is(windowEventCounts.compositionend, 0,
2252 kDescription + "compositionend has been handled during composition by window #2");
2253 is(inputEventCounts.compositionend, 0,
2254 kDescription + "compositionend has been handled during composition by input #2");
2256 is(windowEventCounts.input, 2,
2257 kDescription + "input hasn't been handled by window #2");
2258 is(windowEventData.input, "\u3089\u30FC",
2259 kDescription + "value of input element wasn't modified (window) #2");
2260 is(inputEventCounts.input, 2,
2261 kDescription + "input hasn't been handled by input #2");
2262 is(inputEventData.input, "\u3089\u30FC",
2263 kDescription + "value of input element wasn't modified (input) #2");
2265 // text event shouldn't cause composition update, e.g., at committing.
2266 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
2268 is(windowEventCounts.compositionstart, 1,
2269 kDescription + "compositionstart has been handled more than once by window #3");
2270 is(inputEventCounts.compositionstart, 1,
2271 kDescription + "compositionstart has been handled more than once by input #3");
2273 is(windowEventCounts.compositionupdate, 2,
2274 kDescription + "compositionupdate has been fired unexpectedly on window #3");
2275 is(inputEventCounts.compositionupdate, 2,
2276 kDescription + "compositionupdate has been fired unexpectedly on input #3");
2278 is(windowEventCounts.compositionend, 1,
2279 kDescription + "compositionend hasn't been handled by window #3");
2280 is(windowEventData.compositionend, "\u3089\u30FC",
2281 kDescription + "data of compositionend doesn't match (window) #3");
2282 is(windowEventLocale.compositionend, "",
2283 kDescription + "locale of compositionend isn't empty (window) #3");
2284 is(inputEventCounts.compositionend, 1,
2285 kDescription + "compositionend hasn't been handled by input #3");
2286 is(inputEventData.compositionend, "\u3089\u30FC",
2287 kDescription + "data of compositionend doesn't match (input) #3");
2288 is(inputEventLocale.compositionend, "",
2289 kDescription + "locale of compositionend isn't empty (input) #3");
2291 is(windowEventCounts.input, 3,
2292 kDescription + "input hasn't been handled by window #3");
2293 is(windowEventData.input, "\u3089\u30FC",
2294 kDescription + "value of input element wasn't modified (window) #3");
2295 is(inputEventCounts.input, 3,
2296 kDescription + "input hasn't been handled by input #3");
2297 is(inputEventData.input, "\u3089\u30FC",
2298 kDescription + "value of input element wasn't modified (input) #3");
2300 // select the second character, then, data of composition start should be
2301 // the selected character.
2302 initResults();
2303 synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
2305 synthesizeCompositionChange(
2306 { "composition":
2307 { "string": "\u3089",
2308 "clauses":
2310 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2313 "caret": { "start": 1, "length": 0 },
2314 "key": { key: "o" },
2317 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
2319 is(windowEventCounts.compositionstart, 1,
2320 kDescription + "compositionstart hasn't been handled by window #4");
2321 is(windowEventData.compositionstart, "\u30FC",
2322 kDescription + "data of compositionstart is empty (window) #4");
2323 is(windowEventLocale.compositionstart, "",
2324 kDescription + "locale of compositionstart isn't empty (window) #4");
2325 is(inputEventCounts.compositionstart, 1,
2326 kDescription + "compositionstart hasn't been handled by input #4");
2327 is(inputEventData.compositionstart, "\u30FC",
2328 kDescription + "data of compositionstart is empty (input) #4");
2329 is(inputEventLocale.compositionstart, "",
2330 kDescription + "locale of compositionstart isn't empty (input) #4");
2332 is(windowEventCounts.compositionupdate, 1,
2333 kDescription + "compositionupdate hasn't been handled by window #4");
2334 is(windowEventData.compositionupdate, "\u3089",
2335 kDescription + "data of compositionupdate doesn't match (window) #4");
2336 is(windowEventLocale.compositionupdate, "",
2337 kDescription + "locale of compositionupdate isn't empty (window) #4");
2338 is(inputEventCounts.compositionupdate, 1,
2339 kDescription + "compositionupdate hasn't been handled by input #4");
2340 is(inputEventData.compositionupdate, "\u3089",
2341 kDescription + "data of compositionupdate doesn't match (input) #4");
2342 is(inputEventLocale.compositionupdate, "",
2343 kDescription + "locale of compositionupdate isn't empty (input) #4");
2345 is(windowEventCounts.compositionend, 1,
2346 kDescription + "compositionend hasn't been handled by window #4");
2347 is(windowEventData.compositionend, "\u3089",
2348 kDescription + "data of compositionend doesn't match (window) #4");
2349 is(windowEventLocale.compositionend, "",
2350 kDescription + "locale of compositionend isn't empty (window) #4");
2351 is(inputEventCounts.compositionend, 1,
2352 kDescription + "compositionend hasn't been handled by input #4");
2353 is(inputEventData.compositionend, "\u3089",
2354 kDescription + "data of compositionend doesn't match (input) #4");
2355 is(inputEventLocale.compositionend, "",
2356 kDescription + "locale of compositionend isn't empty (input) #4");
2358 is(windowEventCounts.input, 2,
2359 kDescription + "input hasn't been handled by window #4");
2360 is(windowEventData.input, "\u3089\u3089",
2361 kDescription + "value of input element wasn't modified (window) #4");
2362 is(inputEventCounts.input, 2,
2363 kDescription + "input hasn't been handled by input #4");
2364 is(inputEventData.input, "\u3089\u3089",
2365 kDescription + "value of input element wasn't modified (input) #4");
2367 // preventDefault() should effect nothing.
2368 preventDefault = true;
2370 initResults();
2371 synthesizeKey("a", { accelKey: true }); // Select All
2373 synthesizeCompositionChange(
2374 { "composition":
2375 { "string": "\u306D",
2376 "clauses":
2378 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2381 "caret": { "start": 1, "length": 0 },
2382 "key": { key: "," },
2385 synthesizeComposition({ type: "compositioncommitasis" });
2387 is(windowEventCounts.compositionstart, 1,
2388 kDescription + "compositionstart hasn't been handled by window #5");
2389 is(windowEventData.compositionstart, "\u3089\u3089",
2390 kDescription + "data of compositionstart is empty (window) #5");
2391 is(windowEventLocale.compositionstart, "",
2392 kDescription + "locale of compositionstart isn't empty (window) #5");
2393 is(inputEventCounts.compositionstart, 1,
2394 kDescription + "compositionstart hasn't been handled by input #5");
2395 is(inputEventData.compositionstart, "\u3089\u3089",
2396 kDescription + "data of compositionstart is empty (input) #5");
2397 is(inputEventLocale.compositionstart, "",
2398 kDescription + "locale of compositionstart isn't empty (input) #5");
2400 is(windowEventCounts.compositionupdate, 1,
2401 kDescription + "compositionupdate hasn't been handled by window #5");
2402 is(windowEventData.compositionupdate, "\u306D",
2403 kDescription + "data of compositionupdate doesn't match (window) #5");
2404 is(windowEventLocale.compositionupdate, "",
2405 kDescription + "locale of compositionupdate isn't empty (window) #5");
2406 is(inputEventCounts.compositionupdate, 1,
2407 kDescription + "compositionupdate hasn't been handled by input #5");
2408 is(inputEventData.compositionupdate, "\u306D",
2409 kDescription + "data of compositionupdate doesn't match (input) #5");
2410 is(inputEventLocale.compositionupdate, "",
2411 kDescription + "locale of compositionupdate isn't empty (input) #5");
2413 is(windowEventCounts.compositionend, 1,
2414 kDescription + "compositionend hasn't been handled by window #5");
2415 is(windowEventData.compositionend, "\u306D",
2416 kDescription + "data of compositionend doesn't match (window) #5");
2417 is(windowEventLocale.compositionend, "",
2418 kDescription + "locale of compositionend isn't empty (window) #5");
2419 is(inputEventCounts.compositionend, 1,
2420 kDescription + "compositionend hasn't been handled by input #5");
2421 is(inputEventData.compositionend, "\u306D",
2422 kDescription + "data of compositionend doesn't match (input) #5");
2423 is(inputEventLocale.compositionend, "",
2424 kDescription + "locale of compositionend isn't empty (input) #5");
2426 is(windowEventCounts.input, 2,
2427 kDescription + "input hasn't been handled by window #5");
2428 is(windowEventData.input, "\u306D",
2429 kDescription + "value of input element wasn't modified (window) #5");
2430 is(inputEventCounts.input, 2,
2431 kDescription + "input hasn't been handled by input #5");
2432 is(inputEventData.input, "\u306D",
2433 kDescription + "value of input element wasn't modified (input) #5");
2435 preventDefault = false;
2437 // stopPropagation() should effect nothing (except event count)
2438 stopPropagation = true;
2440 initResults();
2441 synthesizeKey("a", { accelKey: true }); // Select All
2443 synthesizeCompositionChange(
2444 { "composition":
2445 { "string": "\u306E",
2446 "clauses":
2448 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
2451 "caret": { "start": 1, "length": 0 },
2452 "key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
2455 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
2457 is(windowEventCounts.compositionstart, 1,
2458 kDescription + "compositionstart hasn't been handled by window #6");
2459 is(windowEventData.compositionstart, "\u306D",
2460 kDescription + "data of compositionstart is empty #6");
2461 is(windowEventLocale.compositionstart, "",
2462 kDescription + "locale of compositionstart isn't empty #6");
2463 is(inputEventCounts.compositionstart, 0,
2464 kDescription + "compositionstart has been handled by input #6");
2466 is(windowEventCounts.compositionupdate, 1,
2467 kDescription + "compositionupdate hasn't been handled by window #6");
2468 is(windowEventData.compositionupdate, "\u306E",
2469 kDescription + "data of compositionupdate doesn't match #6");
2470 is(windowEventLocale.compositionupdate, "",
2471 kDescription + "locale of compositionupdate isn't empty #6");
2472 is(inputEventCounts.compositionupdate, 0,
2473 kDescription + "compositionupdate has been handled by input #6");
2475 is(windowEventCounts.compositionend, 1,
2476 kDescription + "compositionend hasn't been handled by window #6");
2477 is(windowEventData.compositionend, "\u306E",
2478 kDescription + "data of compositionend doesn't match #6");
2479 is(windowEventLocale.compositionend, "",
2480 kDescription + "locale of compositionend isn't empty #6");
2481 is(inputEventCounts.compositionend, 0,
2482 kDescription + "compositionend has been handled by input #6");
2484 is(windowEventCounts.input, 2,
2485 kDescription + "input hasn't been handled by window #6");
2486 is(windowEventData.input, "\u306E",
2487 kDescription + "value of input element wasn't modified (window) #6");
2488 is(inputEventCounts.input, 2,
2489 kDescription + "input hasn't been handled by input #6");
2490 is(inputEventData.input, "\u306E",
2491 kDescription + "value of input element wasn't modified (input) #6");
2493 stopPropagation = false;
2495 // create event and dispatch it.
2496 initResults();
2498 input.value = "value of input";
2499 synthesizeKey("a", { accelKey: true }); // Select All
2501 let compositionstart = document.createEvent("CompositionEvent");
2502 compositionstart.initCompositionEvent("compositionstart",
2503 true, true, document.defaultView,
2504 "start data", "start locale");
2505 is(compositionstart.type, "compositionstart",
2506 kDescription + "type doesn't match #7");
2507 is(compositionstart.data, "start data",
2508 kDescription + "data doesn't match #7");
2509 is(compositionstart.locale, "start locale",
2510 kDescription + "locale doesn't match #7");
2511 is(compositionstart.detail, 0,
2512 kDescription + "detail isn't 0 #7");
2514 input.dispatchEvent(compositionstart);
2516 is(windowEventCounts.compositionstart, 1,
2517 kDescription + "compositionstart hasn't been handled by window #7");
2518 is(windowEventData.compositionstart, "start data",
2519 kDescription + "data of compositionstart was changed (window) #7");
2520 is(windowEventLocale.compositionstart, "start locale",
2521 kDescription + "locale of compositionstart was changed (window) #7");
2522 is(inputEventCounts.compositionstart, 1,
2523 kDescription + "compositionstart hasn't been handled by input #7");
2524 is(inputEventData.compositionstart, "start data",
2525 kDescription + "data of compositionstart was changed (input) #7");
2526 is(inputEventLocale.compositionstart, "start locale",
2527 kDescription + "locale of compositionstart was changed (input) #7");
2529 is(input.value, "value of input",
2530 kDescription + "input value was changed #7");
2532 let compositionupdate1 = document.createEvent("compositionevent");
2533 compositionupdate1.initCompositionEvent("compositionupdate",
2534 true, false, document.defaultView,
2535 "composing string", "composing locale");
2536 is(compositionupdate1.type, "compositionupdate",
2537 kDescription + "type doesn't match #8");
2538 is(compositionupdate1.data, "composing string",
2539 kDescription + "data doesn't match #8");
2540 is(compositionupdate1.locale, "composing locale",
2541 kDescription + "locale doesn't match #8");
2542 is(compositionupdate1.detail, 0,
2543 kDescription + "detail isn't 0 #8");
2545 input.dispatchEvent(compositionupdate1);
2547 is(windowEventCounts.compositionupdate, 1,
2548 kDescription + "compositionupdate hasn't been handled by window #8");
2549 is(windowEventData.compositionupdate, "composing string",
2550 kDescription + "data of compositionupdate was changed (window) #8");
2551 is(windowEventLocale.compositionupdate, "composing locale",
2552 kDescription + "locale of compositionupdate was changed (window) #8");
2553 is(inputEventCounts.compositionupdate, 1,
2554 kDescription + "compositionupdate hasn't been handled by input #8");
2555 is(inputEventData.compositionupdate, "composing string",
2556 kDescription + "data of compositionupdate was changed (input) #8");
2557 is(inputEventLocale.compositionupdate, "composing locale",
2558 kDescription + "locale of compositionupdate was changed (input) #8");
2560 is(input.value, "value of input",
2561 kDescription + "input value was changed #8");
2563 let compositionupdate2 = document.createEvent("compositionEvent");
2564 compositionupdate2.initCompositionEvent("compositionupdate",
2565 true, false, document.defaultView,
2566 "commit string", "commit locale");
2567 is(compositionupdate2.type, "compositionupdate",
2568 kDescription + "type doesn't match #9");
2569 is(compositionupdate2.data, "commit string",
2570 kDescription + "data doesn't match #9");
2571 is(compositionupdate2.locale, "commit locale",
2572 kDescription + "locale doesn't match #9");
2573 is(compositionupdate2.detail, 0,
2574 kDescription + "detail isn't 0 #9");
2576 input.dispatchEvent(compositionupdate2);
2578 is(windowEventCounts.compositionupdate, 2,
2579 kDescription + "compositionupdate hasn't been handled by window #9");
2580 is(windowEventData.compositionupdate, "commit string",
2581 kDescription + "data of compositionupdate was changed (window) #9");
2582 is(windowEventLocale.compositionupdate, "commit locale",
2583 kDescription + "locale of compositionupdate was changed (window) #9");
2584 is(inputEventCounts.compositionupdate, 2,
2585 kDescription + "compositionupdate hasn't been handled by input #9");
2586 is(inputEventData.compositionupdate, "commit string",
2587 kDescription + "data of compositionupdate was changed (input) #9");
2588 is(inputEventLocale.compositionupdate, "commit locale",
2589 kDescription + "locale of compositionupdate was changed (input) #9");
2591 is(input.value, "value of input",
2592 kDescription + "input value was changed #9");
2594 let compositionend = document.createEvent("Compositionevent");
2595 compositionend.initCompositionEvent("compositionend",
2596 true, false, document.defaultView,
2597 "end data", "end locale");
2598 is(compositionend.type, "compositionend",
2599 kDescription + "type doesn't match #10");
2600 is(compositionend.data, "end data",
2601 kDescription + "data doesn't match #10");
2602 is(compositionend.locale, "end locale",
2603 kDescription + "locale doesn't match #10");
2604 is(compositionend.detail, 0,
2605 kDescription + "detail isn't 0 #10");
2607 input.dispatchEvent(compositionend);
2609 is(windowEventCounts.compositionend, 1,
2610 kDescription + "compositionend hasn't been handled by window #10");
2611 is(windowEventData.compositionend, "end data",
2612 kDescription + "data of compositionend was changed (window) #10");
2613 is(windowEventLocale.compositionend, "end locale",
2614 kDescription + "locale of compositionend was changed (window) #10");
2615 is(inputEventCounts.compositionend, 1,
2616 kDescription + "compositionend hasn't been handled by input #10");
2617 is(inputEventData.compositionend, "end data",
2618 kDescription + "data of compositionend was changed (input) #10");
2619 is(inputEventLocale.compositionend, "end locale",
2620 kDescription + "locale of compositionend was changed (input) #10");
2622 is(input.value, "value of input",
2623 kDescription + "input value was changed #10");
2625 window.removeEventListener("compositionstart",
2626 compositionEventHandlerForWindow, true);
2627 window.removeEventListener("compositionend",
2628 compositionEventHandlerForWindow, true);
2629 window.removeEventListener("compositionupdate",
2630 compositionEventHandlerForWindow, true);
2631 window.removeEventListener("input",
2632 formEventHandlerForWindow, true);
2634 input.removeEventListener("compositionstart",
2635 compositionEventHandlerForInput, true);
2636 input.removeEventListener("compositionend",
2637 compositionEventHandlerForInput, true);
2638 input.removeEventListener("compositionupdate",
2639 compositionEventHandlerForInput, true);
2640 input.removeEventListener("input",
2641 formEventHandlerForInput, true);
2644 function runCompositionTestWhoseTextNodeModified() {
2645 const selection = windowOfContenteditable.getSelection();
2647 (function testInsertTextBeforeComposition() {
2648 const description =
2649 "runCompositionTestWhoseTextNodeModified: testInsertTextBeforeComposition:";
2650 contenteditable.focus();
2651 contenteditable.innerHTML = "<p>def</p>";
2652 const textNode = contenteditable.firstChild.firstChild;
2653 selection.collapse(textNode, "def".length);
2654 // Insert composition to the end of a text node
2655 synthesizeSimpleCompositionChange("g");
2657 textNode.data,
2658 "defg",
2659 `${description} Composition should be inserted to end of the text node`
2662 // Insert a character before the composition string
2663 textNode.insertData(0, "c");
2665 textNode.data,
2666 "cdefg",
2668 description
2669 } Composition should be shifted when a character is inserted before it`
2671 checkIMESelection(
2672 "RawClause",
2673 true,
2674 `${kLF}cdef`.length,
2675 "g",
2677 description
2678 } IME selection should be shifted when a character is inserted before it`
2681 // Update composition string (appending a character)
2682 synthesizeSimpleCompositionChange("gh");
2684 textNode.data,
2685 "cdefgh",
2687 description
2688 } Composition should be updated correctly after inserted a character before it`
2690 checkIMESelection(
2691 "RawClause",
2692 true,
2693 `${kLF}cdef`.length,
2694 "gh",
2696 description
2697 } IME selection should be extended correctly at updating composition after inserted a character before it`
2700 // Insert another character before the composition
2701 textNode.insertData(0, "b");
2703 textNode.data,
2704 "bcdefgh",
2706 description
2707 } Composition should be shifted when a character is inserted again before it`
2709 checkIMESelection(
2710 "RawClause",
2711 true,
2712 `${kLF}bcdef`.length,
2713 "gh",
2715 description
2716 } IME selection should be shifted when a character is inserted again before it`
2719 // Update the composition string again (appending another character)
2720 synthesizeSimpleCompositionChange("ghi");
2722 textNode.data,
2723 "bcdefghi",
2725 description
2726 } Composition should be updated correctly after inserted 2 characters before it`
2728 checkIMESelection(
2729 "RawClause",
2730 true,
2731 `${kLF}bcdef`.length,
2732 "ghi",
2734 description
2735 } IME selection should be extended correctly at updating composition after inserted 2 characters before it`
2738 // Insert a new character before the composition string
2739 textNode.insertData(0, "a");
2741 textNode.data,
2742 "abcdefghi",
2744 description
2745 } Composition should be shifted when a character is inserted again and again before it`
2747 checkIMESelection(
2748 "RawClause",
2749 true,
2750 `${kLF}abcdef`.length,
2751 "ghi",
2753 description
2754 } IME selection should be shifted when a character is inserted again and again before it`
2757 // Commit the composition string
2758 synthesizeComposition({ type: "compositioncommitasis" });
2760 textNode.data,
2761 "abcdefghi",
2763 description
2764 } Composition should be committed as is`
2767 selection.focusOffset,
2768 "abcdefghi".length,
2769 `${description} Selection should be collapsed at end of the commit string`
2772 // Undo the commit
2773 synthesizeKey("z", { accelKey: true });
2775 textNode.data,
2776 "abcdef",
2778 description
2779 } Composition should be undone correctly`
2782 selection.focusOffset,
2783 "abcdef".length,
2785 description
2786 } Selection should be collapsed at where the composition was after undoing`
2789 // Redo the commit
2790 synthesizeKey("z", { accelKey: true, shiftKey: true });
2792 textNode.data,
2793 "abcdefghi",
2795 description
2796 } Composition should be redone correctly`
2799 selection.focusOffset,
2800 "abcdefghi".length,
2802 description
2803 } focus offset of Selection should be at end of the commit string after redoing`
2805 })();
2807 (function testInsertTextImmediatelyBeforeComposition() {
2808 const description =
2809 "runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyBeforeComposition:";
2810 contenteditable.focus();
2811 contenteditable.innerHTML = "<p>d</p>";
2812 const textNode = contenteditable.firstChild.firstChild;
2813 selection.collapse(textNode, 0);
2814 // Insert composition at start of the text node
2815 synthesizeSimpleCompositionChange("b");
2817 textNode.data,
2818 "bd",
2819 `${description} Composition should be inserted to start of the text node`
2822 // Insert a character before the composition string
2823 textNode.insertData(0, "a");
2825 textNode.data,
2826 "abd",
2828 description
2829 } Composition should be shifted when a character is inserted immediately before it`
2831 checkIMESelection(
2832 "RawClause",
2833 true,
2834 `${kLF}a`.length,
2835 "b",
2837 description
2838 } IME selection should be shifted when a character is inserted immediately before it`,
2840 { offset: todo_is, text: todo_is }
2843 // Update the composition string after inserting character immediately before it
2844 synthesizeSimpleCompositionChange("bc");
2846 textNode.data,
2847 "abcd",
2848 `${description} Composition should be updated after the inserted character`
2850 checkIMESelection(
2851 "RawClause",
2852 true,
2853 `${kLF}a`.length,
2854 "bc",
2856 description
2857 } IME selection should be set at the composition string after the inserted character`
2860 // Commit it
2861 synthesizeComposition({ type: "compositioncommitasis" });
2863 textNode.data,
2864 "abcd",
2866 description
2867 } Composition should be committed after the inserted character`
2870 selection.focusOffset,
2871 "abc".length,
2872 `${description} Selection should be collapsed at end of the commit string`
2874 })();
2876 (function testInsertTextImmediatelyAfterComposition() {
2877 const description =
2878 "runCompositionTestWhoseTextNodeModified: testInsertTextImmediatelyAfterComposition:";
2879 contenteditable.focus();
2880 contenteditable.innerHTML = "<p>a</p>";
2881 const textNode = contenteditable.firstChild.firstChild;
2882 selection.collapse(textNode, "a".length);
2883 // Insert composition at end of the text node
2884 synthesizeSimpleCompositionChange("b");
2886 textNode.data,
2887 "ab",
2888 `${description} Composition should be inserted to start of the text node`
2891 // Insert a character after the composition string
2892 textNode.insertData("ab".length, "d");
2894 textNode.data,
2895 "abd",
2897 description
2898 } Composition should stay when a character is inserted immediately after it`
2900 checkIMESelection(
2901 "RawClause",
2902 true,
2903 `${kLF}a`.length,
2904 "b",
2906 description
2907 } IME selection should stay when a character is inserted immediately after it`
2910 // Update the composition string after inserting character immediately after it
2911 synthesizeSimpleCompositionChange("bc");
2913 textNode.data,
2914 "abcd",
2915 `${description} Composition should be updated before the inserted character`
2917 checkIMESelection(
2918 "RawClause",
2919 true,
2920 `${kLF}a`.length,
2921 "bc",
2923 description
2924 } IME selection should be set at the composition string before the inserted character`
2927 // Commit it
2928 synthesizeComposition({ type: "compositioncommitasis" });
2930 textNode.data,
2931 "abcd",
2933 description
2934 } Composition should be committed before the inserted character`
2937 selection.focusOffset,
2938 "abc".length,
2939 `${description} Selection should be collapsed at end of the commit string`
2941 })();
2943 // Inserting/replacing text before the last character of composition string
2944 // should be contained by the composition, i.e., updated by next composition
2945 // update. This is Chrome compatible.
2946 (function testInsertTextMiddleOfComposition() {
2947 const description =
2948 "runCompositionTestWhoseTextNodeModified: testInsertTextMiddleOfComposition:";
2949 contenteditable.focus();
2950 contenteditable.innerHTML = "<p>a</p>";
2951 const textNode = contenteditable.firstChild.firstChild;
2952 selection.collapse(textNode, "a".length);
2953 // Insert composition at middle of the text node
2954 synthesizeSimpleCompositionChange("bd");
2956 textNode.data,
2957 "abd",
2958 `${description} Composition should be inserted to end of the text node`
2961 // Insert a character before the composition string
2962 textNode.insertData("ab".length, "c");
2964 textNode.data,
2965 "abcd",
2967 description
2968 } Inserted string should inserted into the middle of composition string`
2970 checkIMESelection(
2971 "RawClause",
2972 true,
2973 `${kLF}a`.length,
2974 "bcd",
2976 description
2977 } IME selection should be extended when a character is inserted into middle of it`
2980 // Update the composition string after inserting character into it
2981 synthesizeSimpleCompositionChange("BD");
2983 textNode.data,
2984 "aBD",
2986 description
2987 } Composition should be replace the range containing the inserted character`
2989 checkIMESelection(
2990 "RawClause",
2991 true,
2992 `${kLF}a`.length,
2993 "BD",
2995 description
2996 } IME selection should be set at the updated composition string`
2999 // Commit it
3000 synthesizeComposition({ type: "compositioncommitasis" });
3002 textNode.data,
3003 "aBD",
3005 description
3006 } Composition should be committed without the inserted character`
3009 selection.focusOffset,
3010 "aBD".length,
3011 `${description} Selection should be collapsed at end of the commit string`
3013 })();
3015 (function testReplaceFirstCharOfCompositionString() {
3016 const description =
3017 "runCompositionTestWhoseTextNodeModified: testReplaceFirstCharOfCompositionString:";
3018 contenteditable.focus();
3019 contenteditable.innerHTML = "<p>abfg</p>";
3020 const textNode = contenteditable.firstChild.firstChild;
3021 selection.collapse(textNode, "ab".length);
3022 // Insert composition at middle of the text node
3023 synthesizeSimpleCompositionChange("cde");
3025 textNode.data,
3026 "abcdefg",
3027 `${description} Composition should be inserted`
3030 // Replace the composition string
3031 textNode.replaceData("ab".length, "c".length, "XYZ");
3033 textNode.data,
3034 "abXYZdefg",
3035 `${description} First character of the composition should be replaced`
3037 checkIMESelection(
3038 "RawClause",
3039 true,
3040 `${kLF}ab`.length,
3041 "XYZde",
3042 `${description} IME selection should contain the replace string`
3045 // Update the composition string after replaced
3046 synthesizeSimpleCompositionChange("CDE");
3048 textNode.data,
3049 "abCDEfg",
3050 `${description} Composition should update the replace string too`
3052 checkIMESelection(
3053 "RawClause",
3054 true,
3055 `${kLF}ab`.length,
3056 "CDE",
3057 `${description} IME selection should update the replace string too`
3060 // Commit it
3061 synthesizeComposition({ type: "compositioncommitasis" });
3063 textNode.data,
3064 "abCDEfg",
3065 `${description} Composition should be committed`
3068 selection.focusOffset,
3069 "abCDE".length,
3070 `${description} Selection should be collapsed at end of the commit string`
3072 })();
3074 // Although Chrome commits composition if all composition string is removed,
3075 // let's keep composition for making TSF stable...
3076 (function testReplaceAllCompositionString() {
3077 const description =
3078 "runCompositionTestWhoseTextNodeModified: testReplaceAllCompositionString:";
3079 contenteditable.focus();
3080 contenteditable.innerHTML = "<p>abfg</p>";
3081 const textNode = contenteditable.firstChild.firstChild;
3082 selection.collapse(textNode, "ab".length);
3083 // Insert composition at middle of the text node
3084 synthesizeSimpleCompositionChange("cde");
3086 textNode.data,
3087 "abcdefg",
3088 `${description} Composition should be inserted to the text node`
3091 // Replace the composition string
3092 textNode.replaceData("ab".length, "cde".length, "XYZ");
3094 textNode.data,
3095 "abXYZfg",
3096 `${description} Composition should be replaced`
3098 checkIMESelection(
3099 "RawClause",
3100 true,
3101 `${kLF}ab`.length,
3104 description
3105 } IME selection should be collapsed before the replace string`
3108 // Update the composition string after replaced
3109 synthesizeSimpleCompositionChange("CDE");
3111 textNode.data,
3112 "abCDEXYZfg",
3113 `${description} Composition should be inserted again`
3115 checkIMESelection(
3116 "RawClause",
3117 true,
3118 `${kLF}ab`.length,
3119 "CDE",
3120 `${description} IME selection should not contain the replace string`
3123 // Commit it
3124 synthesizeComposition({ type: "compositioncommitasis" });
3126 textNode.data,
3127 "abCDEXYZfg",
3128 `${description} Composition should be committed`
3131 selection.focusOffset,
3132 "abCDE".length,
3133 `${description} Selection should be collapsed at end of the commit string`
3135 })();
3137 (function testReplaceCompositionStringAndSurroundedCharacters() {
3138 const description =
3139 "runCompositionTestWhoseTextNodeModified: testReplaceCompositionStringAndSurroundedCharacters:";
3140 contenteditable.focus();
3141 contenteditable.innerHTML = "<p>abfg</p>";
3142 const textNode = contenteditable.firstChild.firstChild;
3143 selection.collapse(textNode, "ab".length);
3144 // Insert composition at middle of the text node
3145 synthesizeSimpleCompositionChange("cde");
3147 textNode.data,
3148 "abcdefg",
3149 `${description} Composition should be inserted to the text node`
3152 // Replace the composition string
3153 textNode.replaceData("a".length, "bcdef".length, "XYZ");
3155 textNode.data,
3156 "aXYZg",
3157 `${description} Composition should be replaced`
3159 checkIMESelection(
3160 "RawClause",
3161 true,
3162 `${kLF}a`.length,
3165 description
3166 } IME selection should be collapsed before the replace string`
3169 // Update the composition string after replaced
3170 synthesizeSimpleCompositionChange("CDE");
3172 textNode.data,
3173 "aCDEXYZg",
3174 `${description} Composition should be inserted again`
3176 checkIMESelection(
3177 "RawClause",
3178 true,
3179 `${kLF}a`.length,
3180 "CDE",
3181 `${description} IME selection should not contain the replace string`
3184 // Commit it
3185 synthesizeComposition({ type: "compositioncommitasis" });
3187 textNode.data,
3188 "aCDEXYZg",
3189 `${description} Composition should be committed`
3192 selection.focusOffset,
3193 "aCDE".length,
3194 `${description} Selection should be collapsed at end of the commit string`
3196 })();
3198 // If start boundary characters are replaced, the replace string should be
3199 // contained into the composition range. This is Chrome compatible.
3200 (function testReplaceStartBoundaryOfCompositionString() {
3201 const description =
3202 "runCompositionTestWhoseTextNodeModified: testReplaceStartBoundaryOfCompositionString:";
3203 contenteditable.focus();
3204 contenteditable.innerHTML = "<p>abfg</p>";
3205 const textNode = contenteditable.firstChild.firstChild;
3206 selection.collapse(textNode, "ab".length);
3207 // Insert composition at middle of the text node
3208 synthesizeSimpleCompositionChange("cde");
3210 textNode.data,
3211 "abcdefg",
3212 `${description} Composition should be inserted to the text node`
3215 // Replace some text
3216 textNode.replaceData("a".length, "bc".length, "XYZ");
3218 textNode.data,
3219 "aXYZdefg",
3221 description
3222 } Start of the composition should be replaced`
3224 checkIMESelection(
3225 "RawClause",
3226 true,
3227 `${kLF}a`.length,
3228 "XYZde",
3229 `${description} IME selection should contain the replace string`
3232 // Update the replace string and remaining composition.
3233 synthesizeSimpleCompositionChange("CDE");
3235 textNode.data,
3236 "aCDEfg",
3237 `${description} Composition should update the replace string too`
3239 checkIMESelection(
3240 "RawClause",
3241 true,
3242 `${kLF}a`.length,
3243 "CDE",
3244 `${description} IME selection should contain the replace string`
3247 // Commit it
3248 synthesizeComposition({ type: "compositioncommitasis" });
3250 textNode.data,
3251 "aCDEfg",
3253 description
3254 } Composition should be committed`
3257 selection.focusOffset,
3258 "aCDE".length,
3259 `${description} Selection should be collapsed at end of the commit string`
3261 })();
3263 // If start boundary characters are replaced, the replace string should NOT
3264 // be contained in the composition range. This is Chrome compatible.
3265 (function testReplaceEndBoundaryOfCompositionString() {
3266 const description =
3267 "runCompositionTestWhoseTextNodeModified: testReplaceEndBoundaryOfCompositionString:";
3268 contenteditable.focus();
3269 contenteditable.innerHTML = "<p>abfg</p>";
3270 const textNode = contenteditable.firstChild.firstChild;
3271 selection.collapse(textNode, "ab".length);
3272 // Insert composition at middle of the text node
3273 synthesizeSimpleCompositionChange("cde");
3275 textNode.data,
3276 "abcdefg",
3277 `${description} Composition should be inserted to the text node`
3280 // Replace the composition string
3281 textNode.replaceData("abcd".length, "ef".length, "XYZ");
3283 textNode.data,
3284 "abcdXYZg",
3286 description
3287 } End half of the composition should be replaced`
3289 checkIMESelection(
3290 "RawClause",
3291 true,
3292 `${kLF}ab`.length,
3293 "cd",
3295 description
3296 } IME selection should be shrunken to the non-replaced part`
3299 // Update the composition string after replaced
3300 synthesizeSimpleCompositionChange("CDE");
3302 textNode.data,
3303 "abCDEXYZg",
3304 `${description} Only the remaining composition string should be updated`
3306 checkIMESelection(
3307 "RawClause",
3308 true,
3309 `${kLF}ab`.length,
3310 "CDE",
3311 `${description} IME selection should NOT include the replace string`
3314 // Commit it
3315 synthesizeComposition({ type: "compositioncommitasis" });
3317 textNode.data,
3318 "abCDEXYZg",
3319 `${description} Composition should be committed`
3322 selection.focusOffset,
3323 "abCDE".length,
3324 `${description} Selection should be collapsed at end of the commit string`
3326 })();
3328 // If the last character of composition is replaced, i.e., it should NOT be
3329 // treated as a part of composition string. This is Chrome compatible.
3330 (function testReplaceLastCharOfCompositionString() {
3331 const description =
3332 "runCompositionTestWhoseTextNodeModified: testReplaceLastCharOfCompositionString:";
3333 contenteditable.focus();
3334 contenteditable.innerHTML = "<p>abfg</p>";
3335 const textNode = contenteditable.firstChild.firstChild;
3336 selection.collapse(textNode, "ab".length);
3337 // Insert composition at middle of the text node
3338 synthesizeSimpleCompositionChange("cde");
3340 textNode.data,
3341 "abcdefg",
3342 `${description} Composition should be inserted`
3345 // Replace the composition string
3346 textNode.replaceData("abcd".length, "e".length, "XYZ");
3348 textNode.data,
3349 "abcdXYZfg",
3350 `${description} Last character of the composition should be replaced`
3352 checkIMESelection(
3353 "RawClause",
3354 true,
3355 `${kLF}ab`.length,
3356 "cd",
3357 `${description} IME selection should be shrunken`
3360 // Update the composition string after replaced
3361 synthesizeSimpleCompositionChange("CDE");
3363 textNode.data,
3364 "abCDEXYZfg",
3365 `${description} Composition should NOT update the replace string`
3367 checkIMESelection(
3368 "RawClause",
3369 true,
3370 `${kLF}ab`.length,
3371 "CDE",
3372 `${description} IME selection should not contain the replace string`
3375 // Commit it
3376 synthesizeComposition({ type: "compositioncommitasis" });
3378 textNode.data,
3379 "abCDEXYZfg",
3380 `${description} Composition should be committed`
3383 selection.focusOffset,
3384 "abCDE".length,
3385 `${description} Selection should be collapsed at end of the commit string`
3387 })();
3389 (function testReplaceMiddleCharOfCompositionString() {
3390 const description =
3391 "runCompositionTestWhoseTextNodeModified: testReplaceMiddleCharOfCompositionString:";
3392 contenteditable.focus();
3393 contenteditable.innerHTML = "<p>abfg</p>";
3394 const textNode = contenteditable.firstChild.firstChild;
3395 selection.collapse(textNode, "ab".length);
3396 // Insert composition at middle of the text node
3397 synthesizeSimpleCompositionChange("cde");
3399 textNode.data,
3400 "abcdefg",
3401 `${description} Composition should be inserted`
3404 // Replace the composition string
3405 textNode.replaceData("abc".length, "d".length, "XYZ");
3407 textNode.data,
3408 "abcXYZefg",
3410 description
3411 } Middle character of the composition should be replaced`
3413 checkIMESelection(
3414 "RawClause",
3415 true,
3416 `${kLF}ab`.length,
3417 "cXYZe",
3418 `${description} IME selection should be extended by the replace string`
3421 // Update the composition string after replaced
3422 synthesizeSimpleCompositionChange("CDE");
3424 textNode.data,
3425 "abCDEfg",
3426 `${description} Composition should update the replace string`
3428 checkIMESelection(
3429 "RawClause",
3430 true,
3431 `${kLF}ab`.length,
3432 "CDE",
3433 `${description} IME selection should be shrunken after update`
3436 // Commit it
3437 synthesizeComposition({ type: "compositioncommitasis" });
3439 textNode.data,
3440 "abCDEfg",
3441 `${description} Composition should be committed`
3444 selection.focusOffset,
3445 "abCDE".length,
3446 `${description} Selection should be collapsed at end of the commit string`
3448 })();
3451 // eslint-disable-next-line complexity
3452 function runQueryTextRectInContentEditableTest()
3454 contenteditable.focus();
3456 contenteditable.innerHTML = "<p>abc</p><p>def</p>";
3457 // \n 0 123 4 567
3458 // \r\n 01 234 56 789
3460 let description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
3462 // "a"
3463 let a = synthesizeQueryTextRect(kLFLen, 1);
3464 if (!checkQueryContentResult(a, description + "rect for 'a'")) {
3465 return;
3468 // "b"
3469 let b = synthesizeQueryTextRect(kLFLen + 1, 1);
3470 if (!checkQueryContentResult(b, description + "rect for 'b'")) {
3471 return;
3474 is(b.top, a.top, description + "'a' and 'b' should be at same top");
3475 isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
3476 is(b.height, a.height, description + "'a' and 'b' should be same height");
3478 // "c"
3479 let c = synthesizeQueryTextRect(kLFLen + 2, 1);
3480 if (!checkQueryContentResult(c, description + "rect for 'c'")) {
3481 return;
3484 is(c.top, b.top, description + "'b' and 'c' should be at same top");
3485 isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
3486 is(c.height, b.height, description + "'b' and 'c' should be same height");
3488 // "abc" as array
3489 let abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
3490 if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
3491 !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
3492 return;
3495 // 2nd <p> (can be computed with the rect of 'c')
3496 let p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
3497 if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
3498 return;
3501 is(p2.top, c.top, description + "'c' and a line breaker caused by 2nd <p> should be at same top");
3502 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'");
3503 is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");
3505 // 2nd <p> as array
3506 let p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
3507 if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
3508 !checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
3509 return;
3512 if (kLFLen > 1) {
3513 // \n of \r\n
3514 let p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
3515 if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
3516 return;
3519 is(p2_2.top, p2.top, description + "'\\r' and '\\n' should be at same top");
3520 is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
3521 is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
3522 is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");
3524 // \n of \r\n as array
3525 let p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
3526 if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
3527 !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")) {
3528 return;
3532 // "d"
3533 let d = synthesizeQueryTextRect(kLFLen * 2 + 3, 1);
3534 if (!checkQueryContentResult(d, description + "rect for 'd'")) {
3535 return;
3538 isGreaterThan(d.top, a.top + a.height, description + "top of 'd' should be greater than bottom of 'a'");
3539 is(d.left, a.left, description + "'a' and 'd' should be same at same left");
3540 is(d.height, a.height, description + "'a' and 'd' should be same height");
3542 // "e"
3543 let e = synthesizeQueryTextRect(kLFLen * 2 + 4, 1);
3544 if (!checkQueryContentResult(e, description + "rect for 'e'")) {
3545 return;
3548 is(e.top, d.top, description + "'d' and 'd' should be at same top");
3549 isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
3550 is(e.height, d.height, description + "'d' and 'e' should be same height");
3552 // "f"
3553 let f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
3554 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
3555 return;
3558 is(f.top, e.top, description + "'e' and 'f' should be at same top");
3559 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
3560 is(f.height, e.height, description + "'e' and 'f' should be same height");
3562 // "def" as array
3563 let defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
3564 if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
3565 !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
3566 return;
3569 // next of "f" (can be computed with rect of 'f')
3570 let next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
3571 if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
3572 return;
3575 is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
3576 isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
3577 is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
3579 // next of "f" as array
3580 let next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
3581 if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
3582 !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
3583 return;
3586 // too big offset for the editor
3587 let tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
3588 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
3589 return;
3592 is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
3593 is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
3594 is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
3595 is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
3597 // too big offset for the editors as array
3598 let tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
3599 if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
3600 !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
3601 return;
3604 contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
3605 // \n 0 123 4 567 8 9
3606 // \r\n 01 234 56 789 01 23
3608 description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
3610 // "f"
3611 f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
3612 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
3613 return;
3616 is(f.top, e.top, description + "'e' and 'f' should be at same top");
3617 is(f.height, e.height, description + "'e' and 'f' should be same height");
3618 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
3620 // 3rd <p> (can be computed with rect of 'f')
3621 let p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
3622 if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
3623 return;
3626 is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
3627 is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
3628 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'");
3630 // 3rd <p> as array
3631 let p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
3632 if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
3633 !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
3634 return;
3637 if (kLFLen > 1) {
3638 // \n of \r\n
3639 let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
3640 if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
3641 return;
3644 is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
3645 is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
3646 is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
3647 is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
3649 // \n of \r\n as array
3650 let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
3651 if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
3652 !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")) {
3653 return;
3657 // <br> in 3rd <p>
3658 let br = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
3659 if (!checkQueryContentResult(br, description + "rect for <br> in 3rd <p>")) {
3660 return;
3663 isGreaterThan(br.top, d.top + d.height, description + "a line breaker caused by <br> in 3rd <p> should be greater than bottom of 'd'");
3664 isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
3665 is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");
3667 // <br> in 3rd <p> as array
3668 let brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
3669 if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
3670 !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
3671 return;
3674 if (kLFLen > 1) {
3675 // \n of \r\n
3676 let br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
3677 if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
3678 return;
3681 is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
3682 is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
3683 is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
3684 is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
3686 // \n of \r\n as array
3687 let br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
3688 if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
3689 !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")) {
3690 return;
3694 // next of <br> in 3rd <p>
3695 let next_br = synthesizeQueryTextRect(kLFLen * 4 + 6, 1);
3696 if (!checkQueryContentResult(next_br, description + "rect for next of <br> in 3rd <p>")) {
3697 return;
3700 is(next_br.top, br.top, description + "next of <br> and <br> should be at same top");
3701 is(next_br.left, br.left, description + "next of <br> and <br> should be at same left");
3702 is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
3703 is(next_br.width, br.width, description + "next of <br> and <br> should be same width");
3705 // next of <br> in 3rd <p> as array
3706 let next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
3707 if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
3708 !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")) {
3709 return;
3712 // too big offset for the editor
3713 tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
3714 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
3715 return;
3718 is(tooBigOffset.top, next_br.top, description + "too big offset and next of 3rd <p> should be at same top");
3719 is(tooBigOffset.left, next_br.left, description + "too big offset and next of 3rd <p> should be at same left");
3720 is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
3721 is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");
3723 // too big offset for the editors as array
3724 tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
3725 if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
3726 !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
3727 return;
3730 contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
3731 // \n 0 123 4 567 8
3732 // \r\n 01 234 56 789 0
3734 description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
3736 // "f"
3737 f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
3738 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
3739 return;
3742 is(f.top, e.top, description + "'e' and 'f' should be at same top");
3743 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
3744 is(f.height, e.height, description + "'e' and 'f' should be same height");
3746 // 3rd <p> (can be computed with rect of 'f')
3747 p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
3748 if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
3749 return;
3752 is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
3753 is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
3754 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'");
3756 // 3rd <p> as array
3757 p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
3758 if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
3759 !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
3760 return;
3763 if (kLFLen > 1) {
3764 // \n of \r\n
3765 let p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
3766 if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
3767 return;
3770 is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
3771 is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
3772 is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
3773 is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");
3775 // \n of \r\n as array
3776 let p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
3777 if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
3778 !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")) {
3779 return;
3783 // next of 3rd <p>
3784 let next_p3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
3785 if (!checkQueryContentResult(next_p3, description + "rect for next of 3rd <p>")) {
3786 return;
3789 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'");
3790 isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
3791 isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");
3793 // next of 3rd <p> as array
3794 let next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
3795 if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
3796 !checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
3797 return;
3800 // too big offset for the editor
3801 tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
3802 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
3803 return;
3806 is(tooBigOffset.top, next_p3.top, description + "too big offset and next of 3rd <p> should be at same top");
3807 is(tooBigOffset.left, next_p3.left, description + "too big offset and next of 3rd <p> should be at same left");
3808 is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
3809 is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");
3811 // too big offset for the editors as array
3812 tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
3813 if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
3814 !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
3815 return;
3818 contenteditable.innerHTML = "abc<br>def";
3819 // \n 0123 456
3820 // \r\n 01234 567
3822 description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
3824 // "a"
3825 a = synthesizeQueryTextRect(0, 1);
3826 if (!checkQueryContentResult(a, description + "rect for 'a'")) {
3827 return;
3830 // "b"
3831 b = synthesizeQueryTextRect(1, 1);
3832 if (!checkQueryContentResult(b, description + "rect for 'b'")) {
3833 return;
3836 is(b.top, a.top, description + "'a' and 'b' should be at same top");
3837 isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
3838 is(b.height, a.height, description + "'a' and 'b' should be same height");
3840 // "c"
3841 c = synthesizeQueryTextRect(2, 1);
3842 if (!checkQueryContentResult(c, description + "rect for 'c'")) {
3843 return;
3846 is(c.top, b.top, description + "'b' and 'c' should be at same top");
3847 isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
3848 is(c.height, b.height, description + "'b' and 'c' should be same height");
3850 // "abc" as array
3851 abcAsArray = synthesizeQueryTextRectArray(0, 3);
3852 if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
3853 !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
3854 return;
3857 // <br> (can be computed with the rect of 'c')
3858 br = synthesizeQueryTextRect(3, 1);
3859 if (!checkQueryContentResult(br, description + "rect for <br>")) {
3860 return;
3863 is(br.top, c.top, description + "'c' and a line breaker caused by <br> should be at same top");
3864 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'");
3865 is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");
3867 // <br> as array
3868 brAsArray = synthesizeQueryTextRectArray(3, 1);
3869 if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
3870 !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) {
3871 return;
3874 if (kLFLen > 1) {
3875 // \n of \r\n
3876 let br_2 = synthesizeQueryTextRect(4, 1);
3877 if (!checkQueryContentResult(br_2, description + "rect for \n of \r\n caused by <br>")) {
3878 return;
3881 is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
3882 is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
3883 is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
3884 is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");
3886 // \n of \r\n as array
3887 let br_2AsArray = synthesizeQueryTextRectArray(4, 1);
3888 if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br>") ||
3889 !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> should match with each query text rect result")) {
3890 return;
3894 // "d"
3895 d = synthesizeQueryTextRect(kLFLen + 3, 1);
3896 if (!checkQueryContentResult(d, description + "rect for 'd'")) {
3897 return;
3900 isSimilarTo(d.top, a.top + a.height, 2, description + "top of 'd' should be at similar to bottom of 'a'");
3901 is(d.left, a.left, description + "'a' and 'd' should be same at same left");
3902 is(d.height, a.height, description + "'a' and 'd' should be same height");
3904 // "e"
3905 e = synthesizeQueryTextRect(kLFLen + 4, 1);
3906 if (!checkQueryContentResult(e, description + "rect for 'e'")) {
3907 return;
3910 is(e.top, d.top, description + "'d' and 'd' should be at same top");
3911 isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
3912 is(e.height, d.height, description + "'d' and 'e' should be same height");
3914 // "f"
3915 f = synthesizeQueryTextRect(kLFLen + 5, 1);
3916 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
3917 return;
3920 is(f.top, e.top, description + "'e' and 'f' should be at same top");
3921 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
3922 is(f.height, e.height, description + "'e' and 'f' should be same height");
3924 // "def" as array
3925 defAsArray = synthesizeQueryTextRectArray(kLFLen + 3, 3);
3926 if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
3927 !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
3928 return;
3931 // next of "f" (can be computed with rect of 'f')
3932 next_f = synthesizeQueryTextRect(kLFLen + 6, 1);
3933 if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
3934 return;
3937 is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
3938 isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
3939 is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");
3941 // next of "f" as array
3942 next_fAsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
3943 if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
3944 !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
3945 return;
3948 // too big offset for the editor
3949 tooBigOffset = synthesizeQueryTextRect(kLFLen + 7, 1);
3950 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
3951 return;
3954 is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
3955 is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
3956 is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
3957 is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");
3959 // too big offset for the editors as array
3960 tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
3961 if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
3962 !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
3963 return;
3966 // Note that this case does not have an empty line at the end.
3967 contenteditable.innerHTML = "abc<br>def<br>";
3968 // \n 0123 4567
3969 // \r\n 01234 56789
3971 description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
3973 // "f"
3974 f = synthesizeQueryTextRect(kLFLen + 5, 1);
3975 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
3976 return;
3979 is(f.top, e.top, description + "'e' and 'f' should be at same top");
3980 is(f.height, e.height, description + "'e' and 'f' should be same height");
3981 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
3983 // 2nd <br> (can be computed with rect of 'f')
3984 let br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
3985 if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
3986 return;
3989 is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
3990 is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
3991 isSimilarTo(br2.left, f.left + f.width, 2, description + "left of a line breaker caused by 2nd <br> should be similar to right of 'f'");
3993 // 2nd <br> as array
3994 let br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
3995 if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
3996 !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
3997 return;
4000 if (kLFLen > 1) {
4001 // \n of \r\n
4002 let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
4003 if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
4004 return;
4007 is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
4008 is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
4009 is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
4010 is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
4012 // \n of \r\n as array
4013 let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
4014 if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
4015 !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
4016 return;
4020 // next of 2nd <br>
4021 let next_br2 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
4022 if (!checkQueryContentResult(next_br2, description + "rect for next of 2nd <br>")) {
4023 return;
4026 is(next_br2.top, br2.top, description + "2nd <br> and next of 2nd <br> should be at same top");
4027 is(next_br2.left, br2.left, description + "2nd <br> and next of 2nd <br> should be at same top");
4028 is(next_br2.height, br2.height, description + "2nd <br> and next of 2nd <br> should be same height");
4029 is(next_br2.width, br2.width, description + "2nd <br> and next of 2nd <br> should be same width");
4031 // next of 2nd <br> as array
4032 let next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
4033 if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd <br>") ||
4034 !checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd <br> should match with each query text rect result")) {
4035 return;
4038 // too big offset for the editor
4039 tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
4040 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
4041 return;
4044 is(tooBigOffset.top, next_br2.top, description + "too big offset and next of 2nd <br> should be at same top");
4045 is(tooBigOffset.left, next_br2.left, description + "too big offset and next of 2nd <br> should be at same left");
4046 is(tooBigOffset.height, next_br2.height, description + "too big offset and next of 2nd <br> should be same height");
4047 is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd <br> should be same width");
4049 // too big offset for the editors as array
4050 tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
4051 if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
4052 !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
4053 return;
4056 contenteditable.innerHTML = "abc<br>def<br><br>";
4057 // \n 0123 4567 8
4058 // \r\n 01234 56789 01
4060 description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";
4062 // "f"
4063 f = synthesizeQueryTextRect(kLFLen + 5, 1);
4064 if (!checkQueryContentResult(f, description + "rect for 'f'")) {
4065 return;
4068 is(f.top, e.top, description + "'e' and 'f' should be at same top");
4069 isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
4070 is(f.height, e.height, description + "'e' and 'f' should be same height");
4072 // 2nd <br>
4073 br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
4074 if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
4075 return;
4078 is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
4079 is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
4080 ok(f.left < br2.left, description + "left of a line breaker caused by 2nd <br> should be bigger than left of 'f', f.left=" + f.left + ", br2.left=" + br2.left);
4082 // 2nd <br> as array
4083 br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
4084 if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
4085 !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
4086 return;
4089 if (kLFLen > 1) {
4090 // \n of \r\n
4091 let br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
4092 if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
4093 return;
4096 is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
4097 is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
4098 is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
4099 is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");
4101 // \n of \r\n as array
4102 let br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
4103 if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
4104 !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <br> should match with each query text rect result")) {
4105 return;
4109 // 3rd <br>
4110 let br3 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
4111 if (!checkQueryContentResult(br3, description + "rect for next of 3rd <br>")) {
4112 return;
4115 isSimilarTo(br3.top, d.top + d.height, 3, description + "top of next of 3rd <br> should at similar to bottom of 'd'");
4116 isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd <br> should be at similar to left of 'd'");
4117 isSimilarTo(br3.height, d.height, 2, description + "next of 3rd <br> and 'd' should be similar height");
4119 // 3rd <br> as array
4120 let br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
4121 if (!checkQueryContentResult(br3AsArray, description + "3rd <br>'s line breaker as array") ||
4122 !checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd <br> should match with each query text rect result")) {
4123 return;
4126 if (kLFLen > 1) {
4127 // \n of \r\n
4128 let br3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
4129 if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd <br>")) {
4130 return;
4133 is(br3_2.top, br3.top, description + "'\\r' and '\\n' should be at same top");
4134 is(br3_2.left, br3.left, description + "'\\r' and '\\n' should be at same left");
4135 is(br3_2.height, br3.height, description + "'\\r' and '\\n' should be same height");
4136 is(br3_2.width, br3.width, description + "'\\r' and '\\n' should be same width");
4138 // \n of \r\n as array
4139 let br3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
4140 if (!checkQueryContentResult(br3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
4141 !checkRectArray(br3_2AsArray, [br3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <br> should match with each query text rect result")) {
4142 return;
4146 // next of 3rd <br>
4147 let next_br3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
4148 if (!checkQueryContentResult(next_br3, description + "rect for next of 3rd <br>")) {
4149 return;
4152 is(next_br3.top, br3.top, description + "3rd <br> and next of 3rd <br> should be at same top");
4153 is(next_br3.left, br3.left, description + "3rd <br> and next of 3rd <br> should be at same left");
4154 is(next_br3.height, br3.height, description + "3rd <br> and next of 3rd <br> should be same height");
4155 is(next_br3.width, br3.width, description + "3rd <br> and next of 3rd <br> should be same width");
4157 // next of 3rd <br> as array
4158 let next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
4159 if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd <br>") ||
4160 !checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd <br> should match with each query text rect result")) {
4161 return;
4164 // too big offset for the editor
4165 tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
4166 if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
4167 return;
4170 is(tooBigOffset.top, next_br3.top, description + "too big offset and next of 3rd <br> should be at same top");
4171 is(tooBigOffset.left, next_br3.left, description + "too big offset and next of 3rd <br> should be at same left");
4172 is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd <br> should be same height");
4173 is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd <br> should be same width");
4175 // too big offset for the editors as array
4176 tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
4177 if (checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset")) {
4178 checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result");
4181 if (!(function test_query_text_rects_across_invisible_text() {
4182 contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
4183 // \n 0 1 2 345
4184 // \r\n 01 2345 678
4185 description = `runQueryTextRectInContentEditableTest: test_query_text_rects_across_invisible_text: "${
4186 contenteditable.innerHTML.replace(/\n/g, "\\n")
4187 }",`;
4188 // rect of "a"
4189 const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
4190 if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
4191 return false;
4193 const rectArrayFromStartToA = synthesizeQueryTextRectArray(0, kLFLen * 3 + 1);
4194 if (!checkQueryContentResult(rectArrayFromStartToA, `${description} rect array from invisible text to "a"`)) {
4195 return false;
4197 const fromStartToARects = getRectArray(rectArrayFromStartToA);
4198 if (!checkRect(
4199 fromStartToARects[kLFLen * 3],
4200 rectA,
4201 `${description} rect for "a" in array should be same as the result of query only it`
4202 )) {
4203 return false;
4205 return checkRect(
4206 fromStartToARects[kLFLen * 2],
4207 fromStartToARects[0],
4208 `${description} rect for the linebreak in invisible text node should be same as first linebreak`
4210 })()) {
4211 return;
4214 function test_query_text_rects_starting_from_invisible_text() {
4215 contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
4216 // \n 0 1 2 345
4217 // \r\n 01 2345 678
4218 description = `runQueryTextRectInContentEditableTest: test_query_text_rects_starting_from_invisible_text: "${
4219 contenteditable.innerHTML.replace(/\n/g, "\\n")
4220 }",`;
4221 // rect of "a"
4222 const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
4223 if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
4224 return false;
4226 const rectArrayFromInvisibleToA = synthesizeQueryTextRectArray(kLFLen, kLFLen * 2 + 1);
4227 if (!checkQueryContentResult(rectArrayFromInvisibleToA, `${description} rect array from invisible text to "a"`)) {
4228 return false;
4230 const fromInvisibleToARects = getRectArray(rectArrayFromInvisibleToA);
4231 if (!checkRect(
4232 fromInvisibleToARects[kLFLen * 2],
4233 rectA,
4234 `${description} rect for "a" in array should be same as the result of query only it`
4235 )) {
4236 return false;
4238 // For now the rect of characters in invisible text node should be caret rect
4239 // before the following line break. This is inconsistent from the result of
4240 // the query text rect array event starting from the previous visible line
4241 // break or character, but users anyway cannot insert text into the invisible
4242 // text node only with user's operation. Therefore, this won't be problem
4243 // in usual web apps.
4244 const caretRectBeforeLineBreakBeforeA = fromInvisibleToARects[kLFLen];
4245 return checkRect(
4246 fromInvisibleToARects[0],
4247 caretRectBeforeLineBreakBeforeA,
4248 `${description} rect for the linebreak in invisible text node should be same as caret rect before the following visible linebreak before "a"`
4251 if (!test_query_text_rects_starting_from_invisible_text()) {
4252 return;
4255 if (kLFLen > 1) {
4256 function test_query_text_rects_starting_from_middle_of_invisible_linebreak() {
4257 contenteditable.innerHTML = "<div>\n<div>abc</div></div>";
4258 // \n 0 1 2 345
4259 // \r\n 01 2345 678
4260 description = `runQueryTextRectInContentEditableTest: test_query_text_rects_starting_from_middle_of_invisible_linebreak: "${
4261 contenteditable.innerHTML.replace(/\n/g, "\\n")
4262 }",`;
4263 // rect of "a"
4264 const rectA = synthesizeQueryTextRect(kLFLen * 3, 1);
4265 if (!checkQueryContentResult(rectA, `${description} rect of "a"`)) {
4266 return false;
4268 const rectArrayFromInvisibleToA = synthesizeQueryTextRectArray(kLFLen + 1, 1 + kLFLen + 1);
4269 if (!checkQueryContentResult(rectArrayFromInvisibleToA, `${description} rect array from invisible text to "a"`)) {
4270 return false;
4272 const fromInvisibleToARects = getRectArray(rectArrayFromInvisibleToA);
4273 if (!checkRect(
4274 fromInvisibleToARects[1 + kLFLen],
4275 rectA,
4276 `${description} rect for "a" in array should be same as the result of query only it`
4277 )) {
4278 return false;
4280 // For now the rect of characters in invisible text node should be caret rect
4281 // before the following line break. This is inconsistent from the result of
4282 // the query text rect array event starting from the previous visible line
4283 // break or character, but users anyway cannot insert text into the invisible
4284 // text node only with user's operation. Therefore, this won't be problem
4285 // in usual web apps.
4286 const caretRectBeforeLineBreakBeforeA = fromInvisibleToARects[1];
4287 return checkRect(
4288 fromInvisibleToARects[0],
4289 caretRectBeforeLineBreakBeforeA,
4290 `${description} rect for the linebreak in invisible text node should be same as caret rect before the following visible linebreak before "a"`
4293 if (!test_query_text_rects_starting_from_middle_of_invisible_linebreak()) {
4294 return;
4298 function test_query_text_rects_ending_with_invisible_text() {
4299 contenteditable.innerHTML = "<div><div>abc</div>\n</div>";
4300 // \n 0 1 234 5
4301 // \r\n 01 23 456 78
4302 description = `runQueryTextRectInContentEditableTest: test_query_text_rects_ending_with_invisible_text: "${
4303 contenteditable.innerHTML.replace(/\n/g, "\\n")
4304 }",`;
4305 // rect of "c"
4306 const rectC = synthesizeQueryTextRect(kLFLen * 2 + 2, 1);
4307 if (!checkQueryContentResult(rectC, `${description} rect of "c"`)) {
4308 return false;
4310 const rectArrayFromCToInvisible = synthesizeQueryTextRectArray(kLFLen * 2 + 2, 1 + kLFLen);
4311 if (!checkQueryContentResult(rectArrayFromCToInvisible, `${description} rect array from "c" to invisible linebreak`)) {
4312 return false;
4314 const fromCToInvisibleRects = getRectArray(rectArrayFromCToInvisible);
4315 if (!checkRect(
4316 fromCToInvisibleRects[0],
4317 rectC,
4318 `${description} rect for "c" in array should be same as the result of query only it`
4319 )) {
4320 return false;
4322 const caretRectAfterC = {
4323 left: rectC.left + rectC.width,
4324 top: rectC.top,
4325 width: 1,
4326 height: rectC.height,
4328 return checkRectFuzzy(
4329 fromCToInvisibleRects[1],
4330 caretRectAfterC,
4332 left: 1,
4333 top: 0,
4334 width: 0,
4335 height: 0,
4337 `${description} rect for the linebreak in invisible text node should be same as caret rect after "c"`
4340 if (!test_query_text_rects_ending_with_invisible_text()) {
4341 // eslint-disable-next-line no-useless-return
4342 return;
4346 function runCharAtPointTest(aFocusedEditor, aTargetName)
4348 aFocusedEditor.value = "This is a test of the\nContent Events";
4349 // 012345678901234567890 12345678901234
4350 // 0 1 2 3
4352 aFocusedEditor.focus();
4354 const kNone = -1;
4355 const kTestingOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
4356 const kLeftSideOffset = [ kNone, 9, 19, kNone, 33 + kLFLen];
4357 const kRightSideOffset = [ 1, 11, kNone, 22 + kLFLen, kNone];
4358 const kLeftTentativeCaretOffset = [ 0, 10, 20, 21 + kLFLen, 34 + kLFLen];
4359 const kRightTentativeCaretOffset = [ 1, 11, 21, 22 + kLFLen, 35 + kLFLen];
4361 let editorRect = synthesizeQueryEditorRect();
4362 if (!checkQueryContentResult(editorRect,
4363 "runCharAtPointTest (" + aTargetName + "): editorRect")) {
4364 return;
4367 for (let i = 0; i < kTestingOffset.length; i++) {
4368 let textRect = synthesizeQueryTextRect(kTestingOffset[i], 1);
4369 if (!checkQueryContentResult(textRect,
4370 "runCharAtPointTest (" + aTargetName + "): textRect: i=" + i)) {
4371 continue;
4374 checkRectContainsRect(textRect, editorRect,
4375 "runCharAtPointTest (" + aTargetName +
4376 "): the text rect isn't in the editor");
4378 // Test #1, getting same character rect by the point near the top-left.
4379 let charAtPt1 = synthesizeCharAtPoint(textRect.left + 1,
4380 textRect.top + 1);
4381 if (checkQueryContentResult(charAtPt1,
4382 "runCharAtPointTest (" + aTargetName + "): charAtPt1: i=" + i)) {
4383 ok(!charAtPt1.notFound,
4384 "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
4385 if (!charAtPt1.notFound) {
4386 is(charAtPt1.offset, kTestingOffset[i],
4387 "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
4388 checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
4389 "): charAtPt1 left is wrong: i=" + i);
4391 ok(!charAtPt1.tentativeCaretOffsetNotFound,
4392 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
4393 if (!charAtPt1.tentativeCaretOffsetNotFound) {
4394 is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
4395 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
4399 // Test #2, getting same character rect by the point near the bottom-right.
4400 let charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
4401 textRect.top + textRect.height - 2);
4402 if (checkQueryContentResult(charAtPt2,
4403 "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
4404 ok(!charAtPt2.notFound,
4405 "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
4406 if (!charAtPt2.notFound) {
4407 is(charAtPt2.offset, kTestingOffset[i],
4408 "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
4409 checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
4410 "): charAtPt1 left is wrong: i=" + i);
4412 ok(!charAtPt2.tentativeCaretOffsetNotFound,
4413 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
4414 if (!charAtPt2.tentativeCaretOffsetNotFound) {
4415 is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
4416 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
4420 // Test #3, getting left character offset.
4421 let charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
4422 textRect.top + 1);
4423 if (checkQueryContentResult(charAtPt3,
4424 "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
4425 is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
4426 kLeftSideOffset[i] == kNone ?
4427 "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
4428 "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
4429 if (!charAtPt3.notFound) {
4430 is(charAtPt3.offset, kLeftSideOffset[i],
4431 "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
4433 if (kLeftSideOffset[i] == kNone) {
4434 // There may be no enough padding-left (depends on platform)
4435 todo(false,
4436 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
4437 } else {
4438 ok(!charAtPt3.tentativeCaretOffsetNotFound,
4439 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
4440 if (!charAtPt3.tentativeCaretOffsetNotFound) {
4441 is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
4442 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
4447 // Test #4, getting right character offset.
4448 let charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
4449 textRect.top + textRect.height - 2);
4450 if (checkQueryContentResult(charAtPt4,
4451 "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
4452 is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
4453 kRightSideOffset[i] == kNone ?
4454 "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
4455 "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
4456 if (!charAtPt4.notFound) {
4457 is(charAtPt4.offset, kRightSideOffset[i],
4458 "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
4460 ok(!charAtPt4.tentativeCaretOffsetNotFound,
4461 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
4462 if (!charAtPt4.tentativeCaretOffsetNotFound) {
4463 is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
4464 "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
4470 function runCharAtPointAtOutsideTest()
4472 textarea.focus();
4473 textarea.value = "some text";
4474 let editorRect = synthesizeQueryEditorRect();
4475 if (!checkQueryContentResult(editorRect,
4476 "runCharAtPointAtOutsideTest: editorRect")) {
4477 return;
4479 // Check on a text node which is at the outside of editor.
4480 let charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
4481 editorRect.top - 10);
4482 if (checkQueryContentResult(charAtPt,
4483 "runCharAtPointAtOutsideTest: charAtPt")) {
4484 ok(charAtPt.notFound,
4485 "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
4486 ok(charAtPt.tentativeCaretOffsetNotFound,
4487 "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
4491 async function runSetSelectionEventTest()
4493 contenteditable.focus();
4495 const selection = windowOfContenteditable.getSelection();
4497 // #1
4498 contenteditable.innerHTML = "abc<br>def";
4500 await synthesizeSelectionSet(0, 100);
4501 is(selection.anchorNode, contenteditable.firstChild,
4502 "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4503 is(selection.anchorOffset, 0,
4504 "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4505 is(selection.focusNode, contenteditable,
4506 "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
4507 is(selection.focusOffset, contenteditable.childNodes.length,
4508 "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
4509 checkSelection(0, "abc" + kLF + "def", "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
4511 await synthesizeSelectionSet(2, 2 + kLFLen);
4512 is(selection.anchorNode, contenteditable.firstChild,
4513 "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4514 is(selection.anchorOffset, 2,
4515 "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
4516 is(selection.focusNode, contenteditable.lastChild,
4517 "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4518 is(selection.focusOffset, 1,
4519 "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4520 checkSelection(2, "c" + kLF + "d", "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
4522 await synthesizeSelectionSet(1, 2);
4523 is(selection.anchorNode, contenteditable.firstChild,
4524 "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4525 is(selection.anchorOffset, 1,
4526 "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4527 is(selection.focusNode, contenteditable.firstChild,
4528 "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
4529 is(selection.focusOffset, contenteditable.firstChild.wholeText.length,
4530 "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node");
4531 checkSelection(1, "bc", "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
4533 await synthesizeSelectionSet(3, kLFLen);
4534 is(selection.anchorNode, contenteditable.firstChild,
4535 "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4536 is(selection.anchorOffset, contenteditable.firstChild.wholeText.length,
4537 "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
4538 is(selection.focusNode, contenteditable,
4539 "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4540 is(selection.focusOffset, 2,
4541 "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the last text node");
4542 checkSelection(3, kLF, "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
4544 await synthesizeSelectionSet(6+kLFLen, 0);
4545 is(selection.anchorNode, contenteditable.lastChild,
4546 "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4547 is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
4548 "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
4549 is(selection.focusNode, contenteditable.lastChild,
4550 "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4551 is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
4552 "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4553 checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
4555 await synthesizeSelectionSet(100, 0);
4556 is(selection.anchorNode, contenteditable,
4557 "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node of the editor");
4558 is(selection.anchorOffset, contenteditable.childNodes.length,
4559 "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of children");
4560 is(selection.focusNode, contenteditable,
4561 "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
4562 is(selection.focusOffset, contenteditable.childNodes.length,
4563 "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
4564 checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\"");
4566 // #2
4567 contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
4569 await synthesizeSelectionSet(kLFLen, 4+kLFLen);
4570 is(selection.anchorNode, contenteditable.firstChild,
4571 "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
4572 is(selection.anchorOffset, 0,
4573 "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
4574 is(selection.focusNode, contenteditable.lastChild.firstChild,
4575 "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
4576 is(selection.focusOffset, 1,
4577 "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4578 checkSelection(kLFLen, "abc" + kLF + "d", "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
4580 await synthesizeSelectionSet(kLFLen, 2);
4581 is(selection.anchorNode, contenteditable.firstChild,
4582 "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
4583 is(selection.anchorOffset, 0,
4584 "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
4585 is(selection.focusNode, contenteditable.firstChild.childNodes.item(1).firstChild,
4586 "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <b> node");
4587 is(selection.focusOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
4588 "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <b> node");
4589 checkSelection(kLFLen, "ab", "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
4591 await synthesizeSelectionSet(1+kLFLen, 2);
4592 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4593 "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4594 is(selection.anchorOffset, 1,
4595 "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4596 is(selection.focusNode, contenteditable.firstChild.lastChild,
4597 "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node in the first <p> node");
4598 is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
4599 "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node in the first <p> node");
4600 checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
4602 await synthesizeSelectionSet(2+kLFLen, 2+kLFLen);
4603 is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
4604 "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <b> node");
4605 is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
4606 "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <b> node");
4607 is(selection.focusNode, contenteditable.lastChild.firstChild,
4608 "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the last <p> node");
4609 is(selection.focusOffset, 1,
4610 "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4611 checkSelection(2+kLFLen, "c" + kLF + "d", "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
4613 await synthesizeSelectionSet(3+kLFLen*2, 1);
4614 is(selection.anchorNode, contenteditable.lastChild,
4615 "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <p> node");
4616 is(selection.anchorOffset, 0,
4617 "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the second <p> node");
4618 is(selection.focusNode, contenteditable.lastChild.firstChild,
4619 "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
4620 is(selection.focusOffset, 1,
4621 "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4622 checkSelection(3+kLFLen*2, "d", "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
4624 await synthesizeSelectionSet(0, 0);
4625 is(selection.anchorNode, contenteditable,
4626 "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4627 is(selection.anchorOffset, 0,
4628 "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4629 is(selection.focusNode, contenteditable,
4630 "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4631 is(selection.focusOffset, 0,
4632 "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4633 checkSelection(0, "", "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\"");
4635 await synthesizeSelectionSet(0, kLFLen);
4636 is(selection.anchorNode, contenteditable,
4637 "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4638 is(selection.anchorOffset, 0,
4639 "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
4640 is(selection.focusNode, contenteditable.firstChild,
4641 "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first <p> node");
4642 is(selection.focusOffset, 0,
4643 "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4644 checkSelection(0, kLF, "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
4646 await synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
4647 is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
4648 "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the <b> node");
4649 is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
4650 "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <b> node");
4651 is(selection.focusNode, contenteditable.lastChild,
4652 "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
4653 is(selection.focusOffset, 0,
4654 "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4655 checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
4657 await synthesizeSelectionSet(3+kLFLen, kLFLen);
4658 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4659 "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
4660 is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
4661 "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
4662 is(selection.focusNode, contenteditable.lastChild,
4663 "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
4664 is(selection.focusOffset, 0,
4665 "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4666 checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
4668 await synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
4669 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4670 "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
4671 is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
4672 "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
4673 is(selection.focusNode, contenteditable.lastChild.firstChild,
4674 "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the second <p> node");
4675 is(selection.focusOffset, 1,
4676 "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4677 checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
4679 // #3
4680 contenteditable.innerHTML = "<div>abc<p>def</p></div>";
4682 await synthesizeSelectionSet(1+kLFLen, 2);
4683 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4684 "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4685 is(selection.anchorOffset, 1,
4686 "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4687 is(selection.focusNode, contenteditable.firstChild.firstChild,
4688 "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
4689 is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
4690 "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
4691 checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
4693 await synthesizeSelectionSet(1+kLFLen, 3+kLFLen);
4694 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4695 "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4696 is(selection.anchorOffset, 1,
4697 "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4698 is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
4699 "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
4700 is(selection.focusOffset, 1,
4701 "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4702 checkSelection(1+kLFLen, "bc" + kLF + "d", "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
4704 await synthesizeSelectionSet(3+kLFLen, 0);
4705 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4706 "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
4707 is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
4708 "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
4709 is(selection.focusNode, contenteditable.firstChild.firstChild,
4710 "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
4711 is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
4712 "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
4713 checkSelection(3+kLFLen, "", "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
4715 await synthesizeSelectionSet(0, 6+kLFLen*2);
4716 is(selection.anchorNode, contenteditable,
4717 "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4718 is(selection.anchorOffset, 0,
4719 "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4720 is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
4721 "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4722 is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
4723 "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4724 checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
4726 await synthesizeSelectionSet(0, 100);
4727 is(selection.anchorNode, contenteditable,
4728 "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4729 is(selection.anchorOffset, 0,
4730 "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4731 is(selection.focusNode, contenteditable,
4732 "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4733 is(selection.focusOffset, contenteditable.childNodes.length,
4734 "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4735 checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
4737 await synthesizeSelectionSet(4+kLFLen*2, 2);
4738 is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
4739 "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4740 is(selection.anchorOffset, 1,
4741 "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4742 is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
4743 "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4744 is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
4745 "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4746 checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
4748 await synthesizeSelectionSet(4+kLFLen*2, 100);
4749 is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
4750 "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4751 is(selection.anchorOffset, 1,
4752 "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4753 is(selection.focusNode, contenteditable,
4754 "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4755 is(selection.focusOffset, contenteditable.childNodes.length,
4756 "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4757 checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
4759 await synthesizeSelectionSet(6+kLFLen*2, 0);
4760 is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
4761 "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4762 is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
4763 "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
4764 is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
4765 "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4766 is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
4767 "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4768 checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
4770 await synthesizeSelectionSet(6+kLFLen*2, 1);
4771 is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
4772 "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4773 is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
4774 "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
4775 is(selection.focusNode, contenteditable,
4776 "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4777 is(selection.focusOffset, contenteditable.childNodes.length,
4778 "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4779 checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
4781 await synthesizeSelectionSet(0, kLFLen);
4782 is(selection.anchorNode, contenteditable,
4783 "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4784 is(selection.anchorOffset, 0,
4785 "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first text node");
4786 is(selection.focusNode, contenteditable.firstChild,
4787 "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
4788 is(selection.focusOffset, 0,
4789 "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4790 checkSelection(0, kLF, "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
4792 await synthesizeSelectionSet(0, 1+kLFLen);
4793 is(selection.anchorNode, contenteditable,
4794 "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4795 is(selection.anchorOffset, 0,
4796 "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
4797 is(selection.focusNode, contenteditable.firstChild.firstChild,
4798 "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node of the <div> node");
4799 is(selection.focusOffset, 1,
4800 "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4801 checkSelection(0, kLF + "a", "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
4803 await synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
4804 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4805 "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
4806 is(selection.anchorOffset, 2,
4807 "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
4808 is(selection.focusNode, contenteditable.firstChild.lastChild,
4809 "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
4810 is(selection.focusOffset, 0,
4811 "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4812 checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
4814 await synthesizeSelectionSet(3+kLFLen, kLFLen);
4815 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4816 "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
4817 is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
4818 "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
4819 is(selection.focusNode, contenteditable.firstChild.lastChild,
4820 "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
4821 is(selection.focusOffset, 0,
4822 "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4823 checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
4825 await synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
4826 is(selection.anchorNode, contenteditable.firstChild.firstChild,
4827 "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
4828 is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
4829 "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
4830 is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
4831 "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the <p> node");
4832 is(selection.focusOffset, 1,
4833 "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4834 checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
4836 // #4
4837 contenteditable.innerHTML = "<div><p>abc</p>def</div>";
4839 await synthesizeSelectionSet(1+kLFLen*2, 2);
4840 is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
4841 "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
4842 is(selection.anchorOffset, 1,
4843 "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4844 is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
4845 "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
4846 is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
4847 "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
4848 checkSelection(1+kLFLen*2, "bc", "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
4850 await synthesizeSelectionSet(1+kLFLen*2, 3);
4851 is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
4852 "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
4853 is(selection.anchorOffset, 1,
4854 "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4855 is(selection.focusNode, contenteditable.firstChild.lastChild,
4856 "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4857 is(selection.focusOffset, 1,
4858 "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4859 checkSelection(1+kLFLen*2, "bcd", "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
4861 await synthesizeSelectionSet(3+kLFLen*2, 0);
4862 is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
4863 "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
4864 is(selection.anchorOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
4865 "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <p> node");
4866 is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
4867 "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
4868 is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
4869 "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
4870 checkSelection(3+kLFLen*2, "", "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
4872 await synthesizeSelectionSet(0, 6+kLFLen*2);
4873 is(selection.anchorNode, contenteditable,
4874 "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4875 is(selection.anchorOffset, 0,
4876 "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4877 is(selection.focusNode, contenteditable.firstChild.lastChild,
4878 "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4879 is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
4880 "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4881 checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
4883 await synthesizeSelectionSet(0, 100);
4884 is(selection.anchorNode, contenteditable,
4885 "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4886 is(selection.anchorOffset, 0,
4887 "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4888 is(selection.focusNode, contenteditable,
4889 "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4890 is(selection.focusOffset, contenteditable.childNodes.length,
4891 "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4892 checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
4894 await synthesizeSelectionSet(4+kLFLen*2, 2);
4895 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4896 "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4897 is(selection.anchorOffset, 1,
4898 "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4899 is(selection.focusNode, contenteditable.firstChild.lastChild,
4900 "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4901 is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
4902 "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4903 checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
4905 await synthesizeSelectionSet(4+kLFLen*2, 100);
4906 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4907 "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4908 is(selection.anchorOffset, 1,
4909 "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
4910 is(selection.focusNode, contenteditable,
4911 "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4912 is(selection.focusOffset, contenteditable.childNodes.length,
4913 "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4914 checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
4916 await synthesizeSelectionSet(6+kLFLen*2, 0);
4917 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4918 "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4919 is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
4920 "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
4921 is(selection.focusNode, contenteditable.firstChild.lastChild,
4922 "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
4923 is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
4924 "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
4925 checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
4927 await synthesizeSelectionSet(6+kLFLen*2, 1);
4928 is(selection.anchorNode, contenteditable.firstChild.lastChild,
4929 "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
4930 is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
4931 "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
4932 is(selection.focusNode, contenteditable,
4933 "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
4934 is(selection.focusOffset, contenteditable.childNodes.length,
4935 "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
4936 checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
4938 await synthesizeSelectionSet(0, kLFLen);
4939 is(selection.anchorNode, contenteditable,
4940 "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4941 is(selection.anchorOffset, 0,
4942 "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
4943 is(selection.focusNode, contenteditable.firstChild,
4944 "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
4945 is(selection.focusOffset, 0,
4946 "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4947 checkSelection(0, kLF, "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
4949 await synthesizeSelectionSet(0, kLFLen*2);
4950 is(selection.anchorNode, contenteditable,
4951 "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4952 is(selection.anchorOffset, 0,
4953 "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
4954 is(selection.focusNode, contenteditable.firstChild.firstChild,
4955 "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
4956 is(selection.focusOffset, 0,
4957 "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4958 checkSelection(0, kLF + kLF, "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
4960 await synthesizeSelectionSet(0, 1+kLFLen*2);
4961 is(selection.anchorNode, contenteditable,
4962 "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
4963 is(selection.anchorOffset, 0,
4964 "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
4965 is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
4966 "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
4967 is(selection.focusOffset, 1,
4968 "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
4969 checkSelection(0, kLF + kLF + "a", "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
4971 await synthesizeSelectionSet(kLFLen, 0);
4972 is(selection.anchorNode, contenteditable.firstChild,
4973 "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
4974 is(selection.anchorOffset, 0,
4975 "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
4976 is(selection.focusNode, contenteditable.firstChild,
4977 "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
4978 is(selection.focusOffset, 0,
4979 "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4980 checkSelection(kLFLen, "", "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
4982 await synthesizeSelectionSet(kLFLen, kLFLen);
4983 is(selection.anchorNode, contenteditable.firstChild,
4984 "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
4985 is(selection.anchorOffset, 0,
4986 "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
4987 is(selection.focusNode, contenteditable.firstChild.firstChild,
4988 "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
4989 is(selection.focusOffset, 0,
4990 "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
4991 checkSelection(kLFLen, kLF, "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
4993 await synthesizeSelectionSet(kLFLen, 1+kLFLen);
4994 is(selection.anchorNode, contenteditable.firstChild,
4995 "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
4996 is(selection.anchorOffset, 0,
4997 "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
4998 is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
4999 "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
5000 is(selection.focusOffset, 1,
5001 "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
5002 checkSelection(kLFLen, kLF +"a", "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5004 // #5
5005 contenteditable.innerHTML = "<br>";
5007 await synthesizeSelectionSet(0, 0);
5008 is(selection.anchorNode, contenteditable,
5009 "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5010 is(selection.anchorOffset, 0,
5011 "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5012 is(selection.focusNode, contenteditable,
5013 "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5014 is(selection.focusOffset, 0,
5015 "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5016 checkSelection(0, "", "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\"");
5018 await synthesizeSelectionSet(0, kLFLen);
5019 is(selection.anchorNode, contenteditable,
5020 "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5021 is(selection.anchorOffset, 0,
5022 "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5023 is(selection.focusNode, contenteditable,
5024 "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5025 is(selection.focusOffset, contenteditable.childNodes.length,
5026 "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5027 checkSelection(0, kLF, "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5029 await synthesizeSelectionSet(kLFLen, 0);
5030 is(selection.anchorNode, contenteditable,
5031 "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5032 is(selection.anchorOffset, contenteditable.childNodes.length,
5033 "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
5034 is(selection.focusNode, contenteditable,
5035 "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5036 is(selection.focusOffset, contenteditable.childNodes.length,
5037 "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5038 checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5040 await synthesizeSelectionSet(kLFLen, 1);
5041 is(selection.anchorNode, contenteditable,
5042 "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5043 is(selection.anchorOffset, contenteditable.childNodes.length,
5044 "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
5045 is(selection.focusNode, contenteditable,
5046 "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5047 is(selection.focusOffset, contenteditable.childNodes.length,
5048 "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5049 checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
5051 // #6
5052 contenteditable.innerHTML = "<p><br></p>";
5054 await synthesizeSelectionSet(kLFLen, kLFLen);
5055 is(selection.anchorNode, contenteditable.firstChild,
5056 "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5057 is(selection.anchorOffset, 0,
5058 "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5059 is(selection.focusNode, contenteditable.firstChild,
5060 "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5061 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5062 "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5063 checkSelection(kLFLen, kLF, "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5065 await synthesizeSelectionSet(kLFLen*2, 0);
5066 is(selection.anchorNode, contenteditable.firstChild,
5067 "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5068 is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
5069 "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
5070 is(selection.focusNode, contenteditable.firstChild,
5071 "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5072 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5073 "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5074 checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
5076 await synthesizeSelectionSet(kLFLen*2, 1);
5077 is(selection.anchorNode, contenteditable.firstChild,
5078 "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5079 is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
5080 "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
5081 is(selection.focusNode, contenteditable,
5082 "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5083 is(selection.focusOffset, contenteditable.childNodes.length,
5084 "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5085 checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5087 await synthesizeSelectionSet(0, kLFLen);
5088 is(selection.anchorNode, contenteditable,
5089 "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5090 is(selection.anchorOffset, 0,
5091 "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5092 is(selection.focusNode, contenteditable.firstChild,
5093 "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5094 is(selection.focusOffset, 0,
5095 "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5096 checkSelection(0, kLF, "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5098 await synthesizeSelectionSet(0, kLFLen*2);
5099 is(selection.anchorNode, contenteditable,
5100 "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5101 is(selection.anchorOffset, 0,
5102 "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5103 is(selection.focusNode, contenteditable.firstChild,
5104 "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5105 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5106 "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5107 checkSelection(0, kLF + kLF, "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5109 await synthesizeSelectionSet(kLFLen, 0);
5110 is(selection.anchorNode, contenteditable.firstChild,
5111 "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5112 is(selection.anchorOffset, 0,
5113 "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5114 is(selection.focusNode, contenteditable.firstChild,
5115 "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5116 is(selection.focusOffset, 0,
5117 "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5118 checkSelection(kLFLen, "", "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5120 // #7
5121 contenteditable.innerHTML = "<br><br>";
5123 await synthesizeSelectionSet(0, kLFLen);
5124 is(selection.anchorNode, contenteditable,
5125 "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5126 is(selection.anchorOffset, 0,
5127 "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5128 is(selection.focusNode, contenteditable,
5129 "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5130 is(selection.focusOffset, 1,
5131 "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
5132 checkSelection(0, kLF, "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5134 await synthesizeSelectionSet(0, kLFLen * 2);
5135 is(selection.anchorNode, contenteditable,
5136 "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5137 is(selection.anchorOffset, 0,
5138 "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5139 is(selection.focusNode, contenteditable,
5140 "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5141 is(selection.focusOffset, contenteditable.childNodes.length,
5142 "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5143 checkSelection(0, kLF + kLF, "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5145 await synthesizeSelectionSet(kLFLen, 0);
5146 is(selection.anchorNode, contenteditable,
5147 "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5148 is(selection.anchorOffset, 1,
5149 "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5150 is(selection.focusNode, contenteditable,
5151 "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5152 is(selection.focusOffset, 1,
5153 "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
5154 checkSelection(kLFLen, "", "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5156 await synthesizeSelectionSet(kLFLen, kLFLen);
5157 is(selection.anchorNode, contenteditable,
5158 "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5159 is(selection.anchorOffset, 1,
5160 "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5161 is(selection.focusNode, contenteditable,
5162 "runSetSelectionEventTest #7 (kLFLen, kLFLen) selection focus node should be the root node");
5163 is(selection.focusOffset, contenteditable.childNodes.length,
5164 "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5165 checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5167 await synthesizeSelectionSet(kLFLen * 2, 0);
5168 is(selection.anchorNode, contenteditable,
5169 "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5170 is(selection.anchorOffset, contenteditable.childNodes.length,
5171 "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
5172 is(selection.focusNode, contenteditable,
5173 "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5174 is(selection.focusOffset, contenteditable.childNodes.length,
5175 "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5176 checkSelection(kLFLen * 2, "", "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
5178 // #8
5179 contenteditable.innerHTML = "<p><br><br></p>";
5181 await synthesizeSelectionSet(kLFLen, kLFLen);
5182 is(selection.anchorNode, contenteditable.firstChild,
5183 "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5184 is(selection.anchorOffset, 0,
5185 "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5186 is(selection.focusNode, contenteditable.firstChild,
5187 "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5188 is(selection.focusOffset, 1,
5189 "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
5190 checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5192 await synthesizeSelectionSet(kLFLen, kLFLen * 2);
5193 is(selection.anchorNode, contenteditable.firstChild,
5194 "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5195 is(selection.anchorOffset, 0,
5196 "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5197 is(selection.focusNode, contenteditable.firstChild,
5198 "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5199 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5200 "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5201 checkSelection(kLFLen, kLF + kLF, "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5203 await synthesizeSelectionSet(kLFLen*2, 0);
5204 is(selection.anchorNode, contenteditable.firstChild,
5205 "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5206 is(selection.anchorOffset, 1,
5207 "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5208 is(selection.focusNode, contenteditable.firstChild,
5209 "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5210 is(selection.focusOffset, 1,
5211 "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
5212 checkSelection(kLFLen*2, "", "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");
5214 await synthesizeSelectionSet(kLFLen*2, kLFLen);
5215 is(selection.anchorNode, contenteditable.firstChild,
5216 "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5217 is(selection.anchorOffset, 1,
5218 "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5219 is(selection.focusNode, contenteditable.firstChild,
5220 "runSetSelectionEventTest #8 (kLFLen*2, kLFLen) selection focus node should be the <p> node");
5221 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5222 "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5223 checkSelection(kLFLen*2, kLF, "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
5225 await synthesizeSelectionSet(kLFLen*3, 0);
5226 is(selection.anchorNode, contenteditable.firstChild,
5227 "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
5228 is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
5229 "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
5230 is(selection.focusNode, contenteditable.firstChild,
5231 "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5232 is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
5233 "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
5234 checkSelection(kLFLen*3, "", "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\"");
5236 // #9 (ContentEventHandler cannot distinguish if <p> can have children, so, the result is same as case #5, "<br>")
5237 contenteditable.innerHTML = "<p></p>";
5239 await synthesizeSelectionSet(kLFLen, 0);
5240 is(selection.anchorNode, contenteditable,
5241 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5242 is(selection.anchorOffset, 1,
5243 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
5244 is(selection.focusNode, contenteditable,
5245 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
5246 is(selection.focusOffset, 1,
5247 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the <p> node + 1");
5248 checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5250 await synthesizeSelectionSet(kLFLen, 1);
5251 is(selection.anchorNode, contenteditable,
5252 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5253 is(selection.anchorOffset, 1,
5254 "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
5255 is(selection.focusNode, contenteditable,
5256 "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5257 is(selection.focusOffset, contenteditable.childNodes.length,
5258 "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5259 checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5261 // #10
5262 contenteditable.innerHTML = "";
5264 await synthesizeSelectionSet(0, 0);
5265 is(selection.anchorNode, contenteditable,
5266 "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5267 is(selection.anchorOffset, 0,
5268 "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5269 is(selection.focusNode, contenteditable,
5270 "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5271 is(selection.focusOffset, 0,
5272 "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5273 checkSelection(0, "", "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\"");
5275 await synthesizeSelectionSet(0, 1);
5276 is(selection.anchorNode, contenteditable,
5277 "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5278 is(selection.anchorOffset, 0,
5279 "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5280 is(selection.focusNode, contenteditable,
5281 "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5282 is(selection.focusOffset, 0,
5283 "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5284 checkSelection(0, "", "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\"");
5286 // #11
5287 contenteditable.innerHTML = "<span></span><i><u></u></i>";
5289 await synthesizeSelectionSet(0, 0);
5290 is(selection.anchorNode, contenteditable,
5291 "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5292 is(selection.anchorOffset, 0,
5293 "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5294 is(selection.focusNode, contenteditable,
5295 "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5296 is(selection.focusOffset, 0,
5297 "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5298 checkSelection(0, "", "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\"");
5300 await synthesizeSelectionSet(0, 1);
5301 is(selection.anchorNode, contenteditable,
5302 "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
5303 is(selection.anchorOffset, 0,
5304 "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5305 is(selection.focusNode, contenteditable,
5306 "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
5307 is(selection.focusOffset, contenteditable.childNodes.length,
5308 "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
5309 checkSelection(0, "", "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\"");
5311 // #12
5312 contenteditable.innerHTML = "<span>abc</span><i><u></u></i>";
5313 selection.selectAllChildren(contenteditable);
5315 await synthesizeSelectionSet(0, 0);
5316 is(selection.anchorNode, contenteditable.firstChild.firstChild,
5317 "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
5318 is(selection.anchorOffset, 0,
5319 "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5320 is(selection.focusNode, contenteditable.firstChild.firstChild,
5321 "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
5322 is(selection.focusOffset, 0,
5323 "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5324 checkSelection(0, "", "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\"");
5326 // #13
5327 contenteditable.innerHTML = "<span></span><i>abc<u></u></i>";
5328 selection.selectAllChildren(contenteditable);
5330 await synthesizeSelectionSet(0, 0);
5331 is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild,
5332 "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
5333 is(selection.anchorOffset, 0,
5334 "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5335 is(selection.focusNode, contenteditable.childNodes.item(1).firstChild,
5336 "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
5337 is(selection.focusOffset, 0,
5338 "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5339 checkSelection(0, "", "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\"");
5341 // #14
5342 contenteditable.innerHTML = "<span></span><i><u>abc</u></i>";
5343 selection.selectAllChildren(contenteditable);
5345 await synthesizeSelectionSet(0, 0);
5346 is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild.firstChild,
5347 "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
5348 is(selection.anchorOffset, 0,
5349 "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5350 is(selection.focusNode, contenteditable.childNodes.item(1).firstChild.firstChild,
5351 "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
5352 is(selection.focusOffset, 0,
5353 "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5354 checkSelection(0, "", "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\"");
5356 // #15
5357 contenteditable.innerHTML = "<span></span><i><u></u>abc</i>";
5358 selection.selectAllChildren(contenteditable);
5360 await synthesizeSelectionSet(0, 0);
5361 is(selection.anchorNode, contenteditable.childNodes.item(1).lastChild,
5362 "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
5363 is(selection.anchorOffset, 0,
5364 "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5365 is(selection.focusNode, contenteditable.childNodes.item(1).lastChild,
5366 "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
5367 is(selection.focusOffset, 0,
5368 "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5369 checkSelection(0, "", "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\"");
5371 // #16
5372 contenteditable.innerHTML = "a<blink>b</blink>c";
5373 await synthesizeSelectionSet(0, 3);
5374 is(selection.anchorNode, contenteditable.firstChild,
5375 "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
5376 is(selection.anchorOffset, 0,
5377 "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5378 is(selection.focusNode, contenteditable.lastChild,
5379 "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
5380 is(selection.focusOffset, contenteditable.lastChild.wholeText.length,
5381 "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
5382 checkSelection(0, "abc", "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
5384 // #17 (bug 1319660 - incorrect adjustment of content iterator last node)
5385 contenteditable.innerHTML = "<div>a</div><div><br></div>";
5387 await synthesizeSelectionSet(kLFLen, 1+kLFLen);
5388 is(selection.anchorNode, contenteditable.firstChild,
5389 "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
5390 is(selection.anchorOffset, 0,
5391 "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5392 is(selection.focusNode, contenteditable.lastChild,
5393 "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
5394 is(selection.focusOffset, 0,
5395 "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5396 checkSelection(kLFLen, "a" + kLF, "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5398 await synthesizeSelectionSet(1+2*kLFLen, 0);
5399 is(selection.anchorNode, contenteditable.lastChild,
5400 "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <div> element");
5401 is(selection.anchorOffset, 0,
5402 "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
5403 is(selection.focusNode, contenteditable.lastChild,
5404 "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
5405 is(selection.focusOffset, 0,
5406 "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5407 checkSelection(1+2*kLFLen, "", "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5409 // #18 (bug 1319660 - content iterator start node regression)
5410 contenteditable.innerHTML = "<div><br></div><div><br></div>";
5412 await synthesizeSelectionSet(2*kLFLen, kLFLen);
5413 is(selection.anchorNode, contenteditable.firstChild,
5414 "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
5415 is(selection.anchorOffset, 1,
5416 "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
5417 is(selection.focusNode, contenteditable.lastChild,
5418 "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
5419 is(selection.focusOffset, 0,
5420 "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
5421 checkSelection(2*kLFLen, kLF, "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5424 function runQueryTextContentEventTest()
5426 contenteditable.focus();
5428 let result;
5430 // #1
5431 contenteditable.innerHTML = "abc<br>def";
5433 result = synthesizeQueryTextContent(0, 6 + kLFLen);
5434 is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 6+kLFLen), \"" + contenteditable.innerHTML + "\"");
5436 result = synthesizeQueryTextContent(0, 100);
5437 is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");
5439 result = synthesizeQueryTextContent(2, 2 + kLFLen);
5440 is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
5442 result = synthesizeQueryTextContent(1, 2);
5443 is(result.text, "bc", "runQueryTextContentEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");
5445 result = synthesizeQueryTextContent(3, kLFLen);
5446 is(result.text, kLF, "runQueryTextContentEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");
5448 result = synthesizeQueryTextContent(6 + kLFLen, 1);
5449 is(result.text, "", "runQueryTextContentEventTest #1 (6 + kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
5451 // #2
5452 contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";
5454 result = synthesizeQueryTextContent(kLFLen, 4+kLFLen);
5455 is(result.text, "abc" + kLF + "d", "runQueryTextContentEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");
5457 result = synthesizeQueryTextContent(kLFLen, 2);
5458 is(result.text, "ab", "runQueryTextContentEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
5460 result = synthesizeQueryTextContent(1+kLFLen, 2);
5461 is(result.text, "bc", "runQueryTextContentEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
5463 result = synthesizeQueryTextContent(2+kLFLen, 2+kLFLen);
5464 is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");
5466 result = synthesizeQueryTextContent(3+kLFLen*2, 1);
5467 is(result.text, "d", "runQueryTextContentEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5469 result = synthesizeQueryTextContent(0, kLFLen);
5470 is(result.text, kLF, "runQueryTextContentEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5472 result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
5473 is(result.text, "c" + kLF, "runQueryTextContentEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5475 result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
5476 is(result.text, kLF, "runQueryTextContentEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5478 result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
5479 is(result.text, kLF + "d", "runQueryTextContentEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5481 // #3
5482 contenteditable.innerHTML = "<div>abc<p>def</p></div>";
5484 result = synthesizeQueryTextContent(1+kLFLen, 2);
5485 is(result.text, "bc", "runQueryTextContentEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");
5487 result = synthesizeQueryTextContent(1+kLFLen, 3+kLFLen);
5488 is(result.text, "bc" + kLF + "d", "runQueryTextContentEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");
5490 result = synthesizeQueryTextContent(3+kLFLen*2, 1);
5491 is(result.text, "d", "runQueryTextContentEventTest #3 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5493 result = synthesizeQueryTextContent(0, 6+kLFLen*2);
5494 is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5496 result = synthesizeQueryTextContent(0, 100);
5497 is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");
5499 result = synthesizeQueryTextContent(4+kLFLen*2, 2);
5500 is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
5502 result = synthesizeQueryTextContent(4+kLFLen*2, 100);
5503 is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
5505 result = synthesizeQueryTextContent(6+kLFLen*2, 1);
5506 is(result.text, "", "runQueryTextContentEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5508 result = synthesizeQueryTextContent(0, kLFLen);
5509 is(result.text, kLF, "runQueryTextContentEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5511 result = synthesizeQueryTextContent(0, 1+kLFLen);
5512 is(result.text, kLF + "a", "runQueryTextContentEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5514 result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
5515 is(result.text, "c" + kLF, "runQueryTextContentEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5517 result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
5518 is(result.text, kLF, "runQueryTextContentEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5520 result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
5521 is(result.text, kLF + "d", "runQueryTextContentEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
5523 // #4
5524 contenteditable.innerHTML = "<div><p>abc</p>def</div>";
5526 result = synthesizeQueryTextContent(1+kLFLen*2, 2);
5527 is(result.text, "bc", "runQueryTextContentEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
5529 result = synthesizeQueryTextContent(1+kLFLen*2, 3);
5530 is(result.text, "bcd", "runQueryTextContentEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");
5532 result = synthesizeQueryTextContent(3+kLFLen*2, 1);
5533 is(result.text, "d", "runQueryTextContentEventTest #4 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5535 result = synthesizeQueryTextContent(0, 6+kLFLen*2);
5536 is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5538 result = synthesizeQueryTextContent(0, 100);
5539 is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");
5541 result = synthesizeQueryTextContent(4+kLFLen*2, 2);
5542 is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");
5544 result = synthesizeQueryTextContent(4+kLFLen*2, 100);
5545 is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");
5547 result = synthesizeQueryTextContent(6+kLFLen*2, 1);
5548 is(result.text, "", "runQueryTextContentEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5550 result = synthesizeQueryTextContent(0, kLFLen);
5551 is(result.text, kLF, "runQueryTextContentEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5553 result = synthesizeQueryTextContent(0, kLFLen*2);
5554 is(result.text, kLF + kLF, "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5556 result = synthesizeQueryTextContent(0, 1+kLFLen*2);
5557 is(result.text, kLF + kLF + "a", "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5559 result = synthesizeQueryTextContent(kLFLen, kLFLen);
5560 is(result.text, kLF, "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5562 result = synthesizeQueryTextContent(kLFLen, 1+kLFLen);
5563 is(result.text, kLF + "a", "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5565 // #5
5566 contenteditable.innerHTML = "<br>";
5568 result = synthesizeQueryTextContent(0, kLFLen);
5569 is(result.text, kLF, "runQueryTextContentEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5571 result = synthesizeQueryTextContent(kLFLen, 1);
5572 is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");
5574 // #6
5575 contenteditable.innerHTML = "<p><br></p>";
5577 result = synthesizeQueryTextContent(kLFLen, kLFLen);
5578 is(result.text, kLF, "runQueryTextContentEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5580 result = synthesizeQueryTextContent(kLFLen*2, 1);
5581 is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5583 result = synthesizeQueryTextContent(0, kLFLen);
5584 is(result.text, kLF, "runQueryTextContentEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5586 result = synthesizeQueryTextContent(0, kLFLen*2);
5587 is(result.text, kLF + kLF, "runQueryTextContentEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5589 // #7
5590 contenteditable.innerHTML = "<br><br>";
5592 result = synthesizeQueryTextContent(0, kLFLen);
5593 is(result.text, kLF, "runQueryTextContentEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");
5595 result = synthesizeQueryTextContent(0, kLFLen * 2);
5596 is(result.text, kLF + kLF, "runQueryTextContentEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5598 result = synthesizeQueryTextContent(kLFLen, kLFLen);
5599 is(result.text, kLF, "runQueryTextContentEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5601 result = synthesizeQueryTextContent(kLFLen * 2, 1);
5602 is(result.text, "", "runQueryTextContentEventTest #7 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");
5604 // #8
5605 contenteditable.innerHTML = "<p><br><br></p>";
5607 result = synthesizeQueryTextContent(kLFLen, kLFLen);
5608 is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
5610 result = synthesizeQueryTextContent(kLFLen, kLFLen * 2);
5611 is(result.text, kLF + kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");
5613 result = synthesizeQueryTextContent(kLFLen*2, kLFLen);
5614 is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");
5616 result = synthesizeQueryTextContent(kLFLen*3, 1);
5617 is(result.text, "", "runQueryTextContentEventTest #8 (kLFLen*3, 1), \"" + contenteditable.innerHTML + "\"");
5619 // #16
5620 contenteditable.innerHTML = "a<blink>b</blink>c";
5622 result = synthesizeQueryTextContent(0, 3);
5623 is(result.text, "abc", "runQueryTextContentEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
5626 function runQuerySelectionEventTest()
5628 contenteditable.focus();
5630 let selection = windowOfContenteditable.getSelection();
5632 // #1
5633 contenteditable.innerHTML = "<br/>a";
5634 selection.setBaseAndExtent(
5635 contenteditable.firstChild,
5637 contenteditable.lastChild,
5640 checkSelection(
5642 `${kLF}a`,
5643 `runQuerySelectionEventTest #1, "${contenteditable.innerHTML}"`
5646 // #2
5647 contenteditable.innerHTML = "<p></p><p>abc</p>";
5648 selection.setBaseAndExtent(
5649 contenteditable.firstChild,
5651 contenteditable.lastChild.firstChild,
5654 checkSelection(
5655 kLFLen,
5656 `${kLF}a`,
5657 `runQuerySelectionEventTest #2, "${contenteditable.innerHTML}"`
5660 // #3
5661 contenteditable.innerHTML = "<p>abc</p><p>def</p>";
5662 selection.setBaseAndExtent(
5663 contenteditable.firstChild,
5665 contenteditable.lastChild.firstChild,
5668 checkSelection(
5669 kLFLen,
5670 `abc${kLF}d`,
5671 `runQuerySelectionEventTest #3, "${contenteditable.innerHTML}"`
5674 // #4
5675 contenteditable.innerHTML = "<p>abc</p>";
5676 selection.removeAllRanges();
5677 checkSelection(
5678 null,
5679 null,
5680 `runQuerySelectionEventTest #4, "${contenteditable.innerHTML}"`
5684 function runQueryIMESelectionTest()
5686 textarea.focus();
5687 textarea.value = "before after";
5688 let startoffset = textarea.selectionStart = textarea.selectionEnd = "before ".length;
5690 if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
5691 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
5692 !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
5693 !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition")) {
5694 synthesizeComposition({ type: "compositioncommitasis" });
5695 return;
5698 synthesizeCompositionChange(
5699 { "composition":
5700 { "string": "a",
5701 "clauses":
5703 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
5706 "caret": { "start": 1, "length": 0 }
5709 if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: inputting raw text") ||
5710 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
5711 !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
5712 !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text")) {
5713 synthesizeComposition({ type: "compositioncommitasis" });
5714 return;
5717 synthesizeCompositionChange(
5718 { "composition":
5719 { "string": "abcdefgh",
5720 "clauses":
5722 { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
5725 "caret": { "start": 8, "length": 0 }
5728 if (!checkIMESelection("RawClause", true, startoffset, "abcdefgh", "runQueryIMESelectionTest: updating raw text") ||
5729 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
5730 !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
5731 !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text")) {
5732 synthesizeComposition({ type: "compositioncommitasis" });
5733 return;
5736 synthesizeCompositionChange(
5737 { "composition":
5738 { "string": "ABCDEFGH",
5739 "clauses":
5741 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
5742 { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5743 { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5746 "caret": { "start": 2, "length": 0 }
5749 if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
5750 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
5751 !checkIMESelection("ConvertedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: starting to convert") ||
5752 !checkIMESelection("SelectedClause", true, startoffset, "AB", "runQueryIMESelectionTest: starting to convert")) {
5753 synthesizeComposition({ type: "compositioncommitasis" });
5754 return;
5757 synthesizeCompositionChange(
5758 { "composition":
5759 { "string": "ABCDEFGH",
5760 "clauses":
5762 { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5763 { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
5764 { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5767 "caret": { "start": 5, "length": 0 }
5770 if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
5771 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
5772 !checkIMESelection("ConvertedClause", true, startoffset, "AB", "runQueryIMESelectionTest: changing selected clause") ||
5773 !checkIMESelection("SelectedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: changing selected clause")) {
5774 synthesizeComposition({ type: "compositioncommitasis" });
5775 return;
5778 synthesizeComposition({ type: "compositioncommitasis" });
5780 if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
5781 !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
5782 !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
5783 !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition")) {
5784 return;
5787 startoffset = textarea.selectionStart;
5789 synthesizeCompositionChange(
5790 { "composition":
5791 { "string": "abcdefgh",
5792 "clauses":
5794 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
5795 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
5796 { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5797 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
5798 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
5799 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
5800 { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
5801 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
5804 "caret": { "start": 8, "length": 0 }
5807 if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: unrealistic testcase") ||
5808 !checkIMESelection("SelectedRawClause", true, startoffset + 1, "b", "runQueryIMESelectionTest: unrealistic testcase") ||
5809 !checkIMESelection("ConvertedClause", true, startoffset + 2, "c", "runQueryIMESelectionTest: unrealistic testcase") ||
5810 !checkIMESelection("SelectedClause", true, startoffset + 3, "d", "runQueryIMESelectionTest: unrealistic testcase")) {
5811 synthesizeComposition({ type: "compositioncommitasis" });
5812 return;
5815 synthesizeComposition({ type: "compositioncommitasis" });
5818 function runQueryPasswordTest() {
5819 function checkRange(aOffset, aLength, aExpectedResult, aDescription) {
5820 password.focus();
5821 let result = synthesizeQueryTextContent(aOffset, aLength);
5822 is(result.text, aExpectedResult,
5823 `${aDescription}: synthesizeQueryTextContent(${aOffset}, ${aLength})`);
5824 password.setSelectionRange(aOffset, aOffset + aLength);
5825 result = synthesizeQuerySelectedText();
5826 is(result.text, aExpectedResult,
5827 `${aDescription}: synthesizeQuerySelectedText(${aOffset}, ${aLength})`);
5830 let editor = password.editor;
5831 const kMask = editor.passwordMask;
5832 password.value = "abcdef";
5834 editor.mask();
5835 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5836 "runQueryPasswordTest: unmasked range is not specified #1");
5837 checkRange(0, 3, `${kMask}${kMask}${kMask}`,
5838 "runQueryPasswordTest: unmasked range is not specified #2");
5839 checkRange(3, 3, `${kMask}${kMask}${kMask}`,
5840 "runQueryPasswordTest: unmasked range is not specified #3");
5841 checkRange(2, 2, `${kMask}${kMask}`,
5842 "runQueryPasswordTest: unmasked range is not specified #4");
5844 editor.unmask(0, 6);
5845 checkRange(0, 6, "abcdef",
5846 "runQueryPasswordTest: unmasked range 0-6 #1");
5847 checkRange(0, 3, "abc",
5848 "runQueryPasswordTest: unmasked range 0-6 #2");
5849 checkRange(3, 3, "def",
5850 "runQueryPasswordTest: unmasked range 0-6 #3");
5851 checkRange(2, 2, "cd",
5852 "runQueryPasswordTest: unmasked range 0-6 #4");
5854 editor.unmask(0, 3);
5855 checkRange(0, 6, `abc${kMask}${kMask}${kMask}`,
5856 "runQueryPasswordTest: unmasked range 0-3 #1");
5857 checkRange(0, 3, "abc",
5858 "runQueryPasswordTest: unmasked range 0-3 #2");
5859 checkRange(3, 3, `${kMask}${kMask}${kMask}`,
5860 "runQueryPasswordTest: unmasked range 0-3 #3");
5861 checkRange(2, 2, `c${kMask}`,
5862 "runQueryPasswordTest: unmasked range 0-3 #4");
5864 editor.unmask(3, 6);
5865 checkRange(0, 6, `${kMask}${kMask}${kMask}def`,
5866 "runQueryPasswordTest: unmasked range 3-6 #1");
5867 checkRange(0, 3, `${kMask}${kMask}${kMask}`,
5868 "runQueryPasswordTest: unmasked range 3-6 #2");
5869 checkRange(3, 3, `def`,
5870 "runQueryPasswordTest: unmasked range 3-6 #3");
5871 checkRange(2, 2, `${kMask}d`,
5872 "runQueryPasswordTest: unmasked range 3-6 #4");
5874 editor.unmask(2, 4);
5875 checkRange(0, 6, `${kMask}${kMask}cd${kMask}${kMask}`,
5876 "runQueryPasswordTest: unmasked range 3-4 #1");
5877 checkRange(1, 2, `${kMask}c`,
5878 "runQueryPasswordTest: unmasked range 3-4 #2");
5879 checkRange(1, 3, `${kMask}cd`,
5880 "runQueryPasswordTest: unmasked range 3-4 #3");
5881 checkRange(1, 4, `${kMask}cd${kMask}`,
5882 "runQueryPasswordTest: unmasked range 3-4 #4");
5883 checkRange(2, 2, "cd",
5884 "runQueryPasswordTest: unmasked range 3-4 #5");
5885 checkRange(2, 3, `cd${kMask}`,
5886 "runQueryPasswordTest: unmasked range 3-4 #6");
5889 const kEmoji = String.fromCodePoint(0x1f914);
5890 password.value = `${kEmoji}${kEmoji}${kEmoji}`
5892 editor.mask();
5893 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5894 "runQueryPasswordTest: Emojis in password, unmasked range is not specified");
5896 editor.unmask(0, 2);
5897 checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
5898 "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #1");
5899 checkRange(0, 2, `${kEmoji}`,
5900 "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #2");
5901 checkRange(2, 2, `${kMask}${kMask}`,
5902 "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #3");
5903 checkRange(4, 2, `${kMask}${kMask}`,
5904 "runQueryPasswordTest: Emojis in password, unmasked range 0-2 #4");
5906 editor.unmask(2, 4);
5907 checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
5908 "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #1");
5909 checkRange(0, 2, `${kMask}${kMask}`,
5910 "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #2");
5911 checkRange(2, 2, `${kEmoji}`,
5912 "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #3");
5913 checkRange(4, 2, `${kMask}${kMask}`,
5914 "runQueryPasswordTest: Emojis in password, unmasked range 2-4 #4");
5916 editor.unmask(4, 6);
5917 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
5918 "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #1");
5919 checkRange(0, 2, `${kMask}${kMask}`,
5920 "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #2");
5921 checkRange(2, 2, `${kMask}${kMask}`,
5922 "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #3");
5923 checkRange(4, 2, `${kEmoji}`,
5924 "runQueryPasswordTest: Emojis in password, unmasked range 4-6 #4");
5926 editor.unmask(0, 1);
5927 checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
5928 "runQueryPasswordTest: Emojis in password, unmasked range 0-1");
5930 editor.unmask(1, 2);
5931 checkRange(0, 6, `${kEmoji}${kMask}${kMask}${kMask}${kMask}`,
5932 "runQueryPasswordTest: Emojis in password, unmasked range 1-2");
5934 editor.unmask(2, 3);
5935 checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
5936 "runQueryPasswordTest: Emojis in password, unmasked range 2-3");
5938 editor.unmask(3, 4);
5939 checkRange(0, 6, `${kMask}${kMask}${kEmoji}${kMask}${kMask}`,
5940 "runQueryPasswordTest: Emojis in password, unmasked range 3-4");
5942 editor.unmask(4, 5);
5943 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
5944 "runQueryPasswordTest: Emojis in password, unmasked range 4-5");
5946 editor.unmask(5, 6);
5947 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kEmoji}`,
5948 "runQueryPasswordTest: Emojis in password, unmasked range 5-6");
5951 const kEmojiSuperhero = String.fromCodePoint(0x1f9b8);
5952 const kEmojiMediumSkinTone = String.fromCodePoint(0x1f3fd);
5953 const kZeroWidthJoiner = "\u200d";
5954 const kFemaleSign = "\u2640";
5955 const kVariationSelector16 = "\ufe0f";
5956 const kComplicatedEmoji = `${kEmojiSuperhero}${kEmojiMediumSkinTone}${kZeroWidthJoiner}${kFemaleSign}${kVariationSelector16}`;
5957 password.value = `${kComplicatedEmoji}${kComplicatedEmoji}${kComplicatedEmoji}`
5958 editor.mask();
5959 checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5960 "runQueryPasswordTest: Complicated emojis in password, unmasked range is not specified");
5962 editor.unmask(0, 7);
5963 checkRange(0, 21, `${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5964 "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #1");
5965 checkRange(0, 7, `${kComplicatedEmoji}`,
5966 "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #2");
5967 checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5968 "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #3");
5969 checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5970 "runQueryPasswordTest: Complicated emojis in password, unmasked range 0-7 #4");
5972 editor.unmask(7, 14);
5973 checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5974 "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #1");
5975 checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5976 "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #2");
5977 checkRange(7, 7, `${kComplicatedEmoji}`,
5978 "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #3");
5979 checkRange(14, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5980 "runQueryPasswordTest: Complicated emojis in password, unmasked range 7-14 #4");
5982 editor.unmask(14, 21);
5983 checkRange(0, 21, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kComplicatedEmoji}`,
5984 "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #1");
5985 checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5986 "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #2");
5987 checkRange(7, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5988 "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #3");
5989 checkRange(14, 7, `${kComplicatedEmoji}`,
5990 "runQueryPasswordTest: Complicated emojis in password, unmasked range 14-21 #4");
5992 password.value = `${kComplicatedEmoji}`
5993 editor.unmask(0, 1);
5994 checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5995 "runQueryPasswordTest: Complicated emoji in password, unmasked range 0-1");
5997 editor.unmask(1, 2);
5998 checkRange(0, 7, `${kEmojiSuperhero}${kMask}${kMask}${kMask}${kMask}${kMask}`,
5999 "runQueryPasswordTest: Complicated emoji in password, unmasked range 1-2");
6001 editor.unmask(2, 3);
6002 checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
6003 "runQueryPasswordTest: Complicated emoji in password, unmasked range 2-3");
6005 editor.unmask(3, 4);
6006 checkRange(0, 7, `${kMask}${kMask}${kEmojiMediumSkinTone}${kMask}${kMask}${kMask}`,
6007 "runQueryPasswordTest: Complicated emoji in password, unmasked range 3-4");
6009 editor.unmask(4, 5);
6010 checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kZeroWidthJoiner}${kMask}${kMask}`,
6011 "runQueryPasswordTest: Complicated emoji in password, unmasked range 4-5");
6013 editor.unmask(5, 6);
6014 checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kFemaleSign}${kMask}`,
6015 "runQueryPasswordTest: Complicated emoji in password, unmasked range 5-6");
6017 editor.unmask(6, 7);
6018 checkRange(0, 7, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kVariationSelector16}`,
6019 "runQueryPasswordTest: Complicated emoji in password, unmasked range 6-7");
6022 const kKanji = "\u8fba";
6023 const kIVS = String.fromCodePoint(0xe0101);
6024 const kKanjiWithIVS = `${kKanji}${kIVS}`;
6025 password.value = `${kKanjiWithIVS}${kKanjiWithIVS}${kKanjiWithIVS}`
6027 editor.mask();
6028 checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
6029 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range is not specified");
6031 editor.unmask(0, 3);
6032 checkRange(0, 9, `${kKanjiWithIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
6033 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
6034 checkRange(0, 3, `${kKanjiWithIVS}`,
6035 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
6036 checkRange(1, 3, `${kIVS}${kMask}`,
6037 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
6038 checkRange(0, 1, `${kKanji}`,
6039 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
6040 checkRange(1, 2, `${kIVS}`,
6041 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
6042 checkRange(3, 1, `${kMask}`,
6043 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
6044 checkRange(4, 2, `${kMask}${kMask}`,
6045 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
6046 checkRange(6, 1, `${kMask}`,
6047 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #8");
6048 checkRange(7, 2, `${kMask}${kMask}`,
6049 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #9");
6051 editor.unmask(0, 1);
6052 checkRange(0, 9, `${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
6053 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #1");
6054 checkRange(0, 1, `${kKanji}`,
6055 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #2");
6056 checkRange(1, 2, `${kMask}${kMask}`,
6057 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #3");
6058 checkRange(3, 1, `${kMask}`,
6059 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #4");
6060 checkRange(4, 2, `${kMask}${kMask}`,
6061 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #5");
6062 checkRange(6, 1, `${kMask}`,
6063 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #6");
6064 checkRange(7, 2, `${kMask}${kMask}`,
6065 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-1 #7");
6067 editor.unmask(1, 3);
6068 checkRange(0, 9, `${kMask}${kIVS}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}`,
6069 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #1");
6070 checkRange(0, 1, `${kMask}`,
6071 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #2");
6072 checkRange(1, 2, `${kIVS}`,
6073 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #3");
6074 checkRange(3, 1, `${kMask}`,
6075 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #4");
6076 checkRange(4, 2, `${kMask}${kMask}`,
6077 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #5");
6078 checkRange(6, 1, `${kMask}`,
6079 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #6");
6080 checkRange(7, 2, `${kMask}${kMask}`,
6081 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-3 #7");
6083 editor.unmask(3, 6);
6084 checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
6085 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #1");
6086 checkRange(3, 3, `${kKanjiWithIVS}`,
6087 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #2");
6088 checkRange(4, 3, `${kIVS}${kMask}`,
6089 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #3");
6090 checkRange(0, 1, `${kMask}`,
6091 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #4");
6092 checkRange(1, 2, `${kMask}${kMask}`,
6093 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #5");
6094 checkRange(3, 1, `${kKanji}`,
6095 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #6");
6096 checkRange(4, 2, `${kIVS}`,
6097 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #7");
6098 checkRange(6, 1, `${kMask}`,
6099 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #8");
6100 checkRange(7, 2, `${kMask}${kMask}`,
6101 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-6 #9");
6103 editor.unmask(3, 4);
6104 checkRange(0, 9, `${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}${kMask}${kMask}${kMask}`,
6105 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #1");
6106 checkRange(0, 1, `${kMask}`,
6107 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #2");
6108 checkRange(1, 2, `${kMask}${kMask}`,
6109 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #3");
6110 checkRange(3, 1, `${kKanji}`,
6111 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #4");
6112 checkRange(4, 2, `${kMask}${kMask}`,
6113 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #5");
6114 checkRange(6, 1, `${kMask}`,
6115 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #6");
6116 checkRange(7, 2, `${kMask}${kMask}`,
6117 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-4 #7");
6119 editor.unmask(4, 6);
6120 checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kIVS}${kMask}${kMask}${kMask}`,
6121 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #1");
6122 checkRange(0, 1, `${kMask}`,
6123 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #2");
6124 checkRange(1, 2, `${kMask}${kMask}`,
6125 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #3");
6126 checkRange(3, 1, `${kMask}`,
6127 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #4");
6128 checkRange(4, 2, `${kIVS}`,
6129 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #5");
6130 checkRange(6, 1, `${kMask}`,
6131 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #6");
6132 checkRange(7, 2, `${kMask}${kMask}`,
6133 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-6 #7");
6135 editor.unmask(6, 9);
6136 checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
6137 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #1");
6138 checkRange(6, 3, `${kKanjiWithIVS}`,
6139 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #2");
6140 checkRange(4, 3, `${kMask}${kMask}${kKanji}`,
6141 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #3");
6142 checkRange(0, 1, `${kMask}`,
6143 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #4");
6144 checkRange(1, 2, `${kMask}${kMask}`,
6145 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #5");
6146 checkRange(3, 1, `${kMask}`,
6147 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #6");
6148 checkRange(4, 2, `${kMask}${kMask}`,
6149 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #7");
6150 checkRange(6, 1, `${kKanji}`,
6151 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #8");
6152 checkRange(7, 2, `${kIVS}`,
6153 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-9 #9");
6155 editor.unmask(6, 7);
6156 checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kKanji}${kMask}${kMask}`,
6157 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #1");
6158 checkRange(0, 1, `${kMask}`,
6159 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #2");
6160 checkRange(1, 2, `${kMask}${kMask}`,
6161 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #3");
6162 checkRange(3, 1, `${kMask}`,
6163 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #4");
6164 checkRange(4, 2, `${kMask}${kMask}`,
6165 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #5");
6166 checkRange(6, 1, `${kKanji}`,
6167 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #6");
6168 checkRange(7, 2, `${kMask}${kMask}`,
6169 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 6-7 #7");
6171 editor.unmask(7, 9);
6172 checkRange(0, 9, `${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kMask}${kIVS}`,
6173 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #1");
6174 checkRange(0, 1, `${kMask}`,
6175 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #2");
6176 checkRange(1, 2, `${kMask}${kMask}`,
6177 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #3");
6178 checkRange(3, 1, `${kMask}`,
6179 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #4");
6180 checkRange(4, 2, `${kMask}${kMask}`,
6181 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #5");
6182 checkRange(6, 1, `${kMask}`,
6183 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #6");
6184 checkRange(7, 2, `${kIVS}`,
6185 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 7-9 #7");
6187 password.value = `${kKanjiWithIVS}${kKanjiWithIVS}`;
6188 editor.unmask(0, 2);
6189 checkRange(0, 6, `${kKanjiWithIVS}${kMask}${kMask}${kMask}`,
6190 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 0-2");
6192 editor.unmask(1, 2);
6193 checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
6194 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 1-2");
6196 editor.unmask(2, 3);
6197 checkRange(0, 6, `${kMask}${kIVS}${kMask}${kMask}${kMask}`,
6198 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 2-3");
6200 editor.unmask(3, 5);
6201 checkRange(0, 6, `${kMask}${kMask}${kMask}${kKanjiWithIVS}`,
6202 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 3-5");
6204 editor.unmask(4, 5);
6205 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
6206 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 4-5");
6208 editor.unmask(5, 6);
6209 checkRange(0, 6, `${kMask}${kMask}${kMask}${kMask}${kIVS}`,
6210 "runQueryPasswordTest: Pairs of Kanji and IVS in password, unmasked range 5-6");
6212 editor.mask();
6215 function runQueryContentEventRelativeToInsertionPoint()
6217 textarea.focus();
6218 textarea.value = "0123456789";
6220 // "[]0123456789"
6221 let startOffset = textarea.selectionStart = textarea.selectionEnd = 0;
6222 if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#1") ||
6223 !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#2") ||
6224 !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#3") ||
6225 !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#4") ||
6226 !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#5")) {
6227 return;
6230 // "[01234]56789"
6231 textarea.selectionEnd = 5;
6232 if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#1") ||
6233 !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#2") ||
6234 !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#3") ||
6235 !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#4") ||
6236 !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#5")) {
6237 return;
6240 // "0123[]456789"
6241 startOffset = textarea.selectionStart = textarea.selectionEnd = 4;
6242 if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "4", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#1") ||
6243 !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#2") ||
6244 !checkContentRelativeToSelection(1, 1, startOffset + 1, "5", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#3") ||
6245 !checkContentRelativeToSelection(5, 10, startOffset + 5, "9", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#4") ||
6246 !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#5")) {
6247 return;
6250 synthesizeCompositionChange(
6251 { "composition":
6252 { "string": "a",
6253 "clauses":
6255 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
6258 "caret": { "start": 1, "length": 0 }
6260 // "0123[a]456789"
6261 if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#1") ||
6262 !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#2") ||
6263 !checkContentRelativeToSelection(1, 1, startOffset + 1, "4", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#3") ||
6264 !checkContentRelativeToSelection(5, 10, startOffset + 5, "89", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#4") ||
6265 !checkContentRelativeToSelection(11, 1, 11, "", "runQueryContentEventRelativeToInsertionPoint[composition at 4]")) {
6266 synthesizeComposition({ type: "compositioncommitasis" });
6267 return;
6270 synthesizeComposition({ type: "compositioncommitasis" });
6272 // Move start of composition at first compositionupdate event.
6273 function onCompositionUpdate(aEvent)
6275 startOffset = textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart - 1;
6276 textarea.removeEventListener("compositionupdate", onCompositionUpdate);
6278 textarea.addEventListener("compositionupdate", onCompositionUpdate);
6280 synthesizeCompositionChange(
6281 { "composition":
6282 { "string": "b",
6283 "clauses":
6285 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
6288 "caret": { "start": 1, "length": 0 }
6290 // "0123[b]a456789"
6291 if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "b", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#1") ||
6292 !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#2") ||
6293 !checkContentRelativeToSelection(1, 1, startOffset + 1, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#3") ||
6294 !checkContentRelativeToSelection(5, 10, startOffset + 5, "789", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#4") ||
6295 !checkContentRelativeToSelection(12, 1, 12, "", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#5")) {
6296 synthesizeComposition({ type: "compositioncommitasis" });
6297 return;
6300 synthesizeComposition({ type: "compositioncommitasis" });
6303 function runBug1375825Test()
6305 contenteditable.focus();
6307 // #1
6308 contenteditable.innerHTML = "abc<span contenteditable=\"false\">defgh</span>";
6310 let ret = synthesizeQueryTextRect(2, 1);
6311 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
6312 return;
6314 is(ret.text, "c", "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
6316 ret = synthesizeQueryTextRect(3, 1);
6317 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
6318 return;
6320 is(ret.text, "d", "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
6322 ret = synthesizeQueryTextRect(4, 1);
6323 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
6324 return;
6326 is(ret.text, "e", "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
6328 ret = synthesizeQueryTextRect(5, 1);
6329 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
6330 return;
6332 is(ret.text, "f", "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
6334 ret = synthesizeQueryTextRect(6, 1);
6335 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
6336 return;
6338 is(ret.text, "g", "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
6340 ret = synthesizeQueryTextRect(7, 1);
6341 if (!checkQueryContentResult(ret, "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
6342 return;
6344 is(ret.text, "h", "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
6346 // #2
6347 contenteditable.innerHTML = "abc<span style=\"user-select: all;\">defgh</span>";
6349 ret = synthesizeQueryTextRect(2, 1);
6350 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
6351 return;
6353 is(ret.text, "c", "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");
6355 ret = synthesizeQueryTextRect(3, 1);
6356 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
6357 return;
6359 is(ret.text, "d", "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");
6361 ret = synthesizeQueryTextRect(4, 1);
6362 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
6363 return;
6365 is(ret.text, "e", "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");
6367 ret = synthesizeQueryTextRect(5, 1);
6368 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
6369 return;
6371 is(ret.text, "f", "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");
6373 ret = synthesizeQueryTextRect(6, 1);
6374 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
6375 return;
6377 is(ret.text, "g", "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");
6379 ret = synthesizeQueryTextRect(7, 1);
6380 if (!checkQueryContentResult(ret, "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
6381 return;
6383 is(ret.text, "h", "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
6386 function runBug1530649Test()
6388 // Vietnamese IME on macOS commits composition with typing space key.
6389 // Then, typing new word shouldn't trim the trailing whitespace.
6390 contenteditable.focus();
6391 contenteditable.innerHTML = "";
6392 synthesizeCompositionChange(
6393 {composition: {string: "abc", clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6394 caret: {start: 3, length: 0}});
6395 synthesizeComposition({type: "compositioncommit", data: "abc ", key: " "});
6397 is(contenteditable.innerHTML, "abc <br>",
6398 "runBug1530649Test: The trailing space shouldn't be removed");
6400 synthesizeCompositionChange(
6401 {composition: {string: "d", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6402 caret: {start: 1, length: 0}});
6404 is(contenteditable.innerHTML, "abc d<br>",
6405 "runBug1530649Test: The new composition string shouldn't remove the last space");
6407 synthesizeComposition({type: "compositioncommitasis", key: "KEY_Enter"});
6409 is(contenteditable.innerHTML, "abc d<br>",
6410 "runBug1530649Test: Committing the new composition string shouldn't remove the last space");
6413 function runBug1571375Test()
6415 let selection = windowOfContenteditableBySpan.getSelection();
6416 let doc = document.getElementById("iframe7").contentDocument;
6418 contenteditableBySpan.focus();
6420 contenteditableBySpan.innerHTML = "hello world";
6421 let range = doc.createRange();
6422 range.setStart(contenteditableBySpan.firstChild, 6);
6423 range.setEnd(contenteditableBySpan.firstChild, 11);
6424 selection.removeAllRanges();
6425 selection.addRange(range);
6427 synthesizeCompositionChange({
6428 composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6429 caret: { start: 5, length: 0 },
6431 synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
6432 is(contenteditableBySpan.innerHTML, "hello world",
6433 "runBug1571375Test: space must not be removed by commit");
6435 contenteditableBySpan.innerHTML = "hello world";
6436 range = doc.createRange();
6437 range.setStart(contenteditableBySpan.firstChild, 0);
6438 range.setEnd(contenteditableBySpan.firstChild, 5);
6439 selection.removeAllRanges();
6440 selection.addRange(range);
6442 synthesizeCompositionChange({
6443 composition: {string: "hello", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6444 caret: { start: 5, length: 0 },
6446 synthesizeComposition({type: "compositioncommit", data: "hello", key: " "});
6447 is(contenteditableBySpan.innerHTML, "hello world",
6448 "runBug1571375Test: space must not be removed by commit");
6450 contenteditableBySpan.innerHTML = "hello world<div>.</div>";
6451 range = doc.createRange();
6452 range.setStart(contenteditableBySpan.firstChild, 6);
6453 range.setEnd(contenteditableBySpan.firstChild, 11);
6454 selection.removeAllRanges();
6455 selection.addRange(range);
6457 synthesizeCompositionChange({
6458 composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6459 caret: {start: 0, length: 0}}
6461 synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
6462 is(contenteditableBySpan.innerHTML, "hello world<div>.</div>",
6463 "runBug1571375Test: space must not be removed by commit");
6466 async function runBug1584901Test()
6468 contenteditableBySpan.focus();
6469 contenteditableBySpan.innerHTML = "";
6471 // XXX synthesizeCompositionChange won't work without wait.
6472 await waitForTick();
6474 synthesizeCompositionChange({
6475 composition: {string: "a ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6477 synthesizeComposition({type: "compositioncommitasis", key: " "});
6479 is(contenteditableBySpan.innerHTML, "a&nbsp;",
6480 "runBug1584901Test: space must not be removed by composition change");
6482 synthesizeCompositionChange({
6483 composition: {string: "b ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6485 synthesizeComposition({type: "compositioncommitasis", key: " "});
6487 is(contenteditableBySpan.innerHTML, "a b&nbsp;",
6488 "runBug1584901Test: space must not be removed by composition change");
6491 function runBug1675313Test()
6493 input.value = "";
6494 input.focus();
6495 let count = 0;
6497 function handler() {
6498 input.focus();
6499 count++;
6502 input.addEventListener("keydown", handler);
6503 input.addEventListener("keyup", handler);
6505 synthesizeCompositionChange({
6506 composition: {
6507 string: "a",
6508 clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6509 key: { key: "a", type: "keyup" },
6512 synthesizeCompositionChange({
6513 composition: {
6514 string: "b",
6515 clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6516 key: { key: "b", type: "keyup" },
6519 synthesizeComposition({type: "compositioncommitasis"});
6521 is(count, 6, "runBug1675313Test: keydown event and keyup event are fired correctly");
6522 is(input.value, "b",
6523 "runBug1675313Test: re-focus element doesn't commit composition if re-focus isn't click by user");
6525 input.removeEventListener("keyup", handler);
6528 function runCommitCompositionWithSpaceKey()
6530 contenteditable.focus();
6531 contenteditable.innerHTML = "";
6533 // Last white space might be &nbsp; if last child is no <br>
6534 // Actually, our implementation will insert <br> element at last child, so
6535 // white space will be ASCII space.
6537 synthesizeCompositionChange({
6538 composition: {string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6540 synthesizeComposition({type: "compositioncommit", data: "a"});
6541 synthesizeKey(" ");
6543 is(contenteditable.innerHTML, "a <br>",
6544 "runCommitCompositionWithSpaceKey: last single space should be kept");
6546 synthesizeCompositionChange({
6547 composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6549 synthesizeComposition({type: "compositioncommit", data: "b"});
6550 synthesizeKey(" ");
6552 is(contenteditable.innerHTML, "a b <br>",
6553 "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
6555 synthesizeCompositionChange({
6556 composition: {string: "c", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6558 synthesizeComposition({type: "compositioncommit", data: "c"});
6559 synthesizeKey(" ");
6561 is(contenteditable.innerHTML, "a b c <br>",
6562 "runCommitCompositionWithSpaceKey: inserting composition shouldn't remove last single space.");
6564 contenteditable.innerHTML = "a";
6565 windowOfContenteditable.getSelection().collapse(contenteditable.firstChild, contenteditable.firstChild.length);
6566 is(contenteditable.innerHTML, "a",
6567 "runCommitCompositionWithSpaceKey: contenteditable should be initialized with text ending with a space and without following <br> element");
6569 synthesizeCompositionChange({
6570 composition: {string: "b", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
6572 synthesizeComposition({type: "compositioncommit", data: "b ", key: { key: " ", code: "Space" }});
6574 is(contenteditable.innerHTML, "ab <br>",
6575 "runCommitCompositionWithSpaceKey: contenteditable should end with a padding <br> element after inserting commit string ending with a space");
6578 function runCSSTransformTest()
6580 textarea.focus();
6581 textarea.value = "some text";
6582 textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
6583 let editorRect = synthesizeQueryEditorRect();
6584 if (!checkQueryContentResult(editorRect,
6585 "runCSSTransformTest: editorRect")) {
6586 return;
6588 let firstCharRect = synthesizeQueryTextRect(0, 1);
6589 if (!checkQueryContentResult(firstCharRect,
6590 "runCSSTransformTest: firstCharRect")) {
6591 return;
6593 let lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
6594 if (!checkQueryContentResult(lastCharRect,
6595 "runCSSTransformTest: lastCharRect")) {
6596 return;
6598 let caretRect = synthesizeQueryCaretRect(textarea.selectionStart);
6599 if (!checkQueryContentResult(caretRect,
6600 "runCSSTransformTest: caretRect")) {
6601 return;
6603 let caretRectBeforeFirstChar = synthesizeQueryCaretRect(0);
6604 if (!checkQueryContentResult(caretRectBeforeFirstChar,
6605 "runCSSTransformTest: caretRectBeforeFirstChar")) {
6606 return;
6609 try {
6610 textarea.style.transform = "translate(10px, 15px)";
6611 function movedRect(aRect, aCSS_CX, aCSS_CY)
6613 return {
6614 left: aRect.left + Math.round(aCSS_CX * window.devicePixelRatio),
6615 top: aRect.top + Math.round(aCSS_CY * window.devicePixelRatio),
6616 width: aRect.width,
6617 height: aRect.height
6621 let editorRectTranslated = synthesizeQueryEditorRect();
6622 if (!checkQueryContentResult(editorRectTranslated,
6623 "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
6624 !checkRectFuzzy(editorRectTranslated, movedRect(editorRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
6625 "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
6626 return;
6628 let firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
6629 if (!checkQueryContentResult(firstCharRectTranslated,
6630 "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
6631 !checkRectFuzzy(firstCharRectTranslated, movedRect(firstCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
6632 "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
6633 return;
6635 let lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
6636 if (!checkQueryContentResult(lastCharRectTranslated,
6637 "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
6638 !checkRectFuzzy(lastCharRectTranslated, movedRect(lastCharRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
6639 "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
6640 return;
6642 let caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
6643 if (!checkQueryContentResult(caretRectTranslated,
6644 "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
6645 !checkRectFuzzy(caretRectTranslated, movedRect(caretRect, 10, 15), {left: 1, top: 1, width: 1, height: 1},
6646 "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
6647 return;
6649 let caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
6650 if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
6651 "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
6652 !checkRectFuzzy(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15), {left: 1, top: 1, width: 1, height: 1},
6653 "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
6654 return;
6656 let firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
6657 if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
6658 !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
6659 return;
6661 let lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
6662 if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
6663 !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
6664 return;
6667 // XXX It's too difficult to check the result with scale and rotate...
6668 // For now, let's check if query text rect and query text rect array returns same rect.
6669 textarea.style.transform = "scale(1.5)";
6670 firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
6671 if (!checkQueryContentResult(firstCharRectTranslated,
6672 "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
6673 return;
6675 lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
6676 if (!checkQueryContentResult(lastCharRectTranslated,
6677 "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
6678 return;
6680 firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
6681 if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
6682 !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
6683 return;
6685 lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
6686 if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
6687 !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
6688 return;
6691 textarea.style.transform = "rotate(30deg)";
6692 firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
6693 if (!checkQueryContentResult(firstCharRectTranslated,
6694 "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
6695 return;
6697 lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
6698 if (!checkQueryContentResult(lastCharRectTranslated,
6699 "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
6700 return;
6702 firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
6703 if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
6704 !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
6705 return;
6707 lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
6708 if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
6709 !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
6710 return;
6712 } finally {
6713 textarea.style.transform = "";
6717 function runBug722639Test()
6719 textarea.focus();
6720 textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
6721 textarea.value += textarea.value;
6722 textarea.value += textarea.value; // 80 characters
6724 let firstLine = synthesizeQueryTextRect(0, 1);
6725 if (!checkQueryContentResult(firstLine,
6726 "runBug722639Test: firstLine")) {
6727 return;
6729 ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
6730 let firstLineAsArray = synthesizeQueryTextRectArray(0, 1);
6731 if (!checkQueryContentResult(firstLineAsArray, "runBug722639Test: 1st line as array") ||
6732 !checkRectArray(firstLineAsArray, [firstLine], "runBug722639Test: 1st line as array should match with text rect result")) {
6733 return;
6735 if (kLFLen > 1) {
6736 let firstLineLF = synthesizeQueryTextRect(1, 1);
6737 if (!checkQueryContentResult(firstLineLF,
6738 "runBug722639Test: firstLineLF")) {
6739 return;
6741 is(firstLineLF.top, firstLine.top, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
6742 is(firstLineLF.left, firstLine.left, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
6743 isfuzzy(firstLineLF.height, firstLine.height, 1,
6744 "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
6745 is(firstLineLF.width, firstLine.width, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
6746 let firstLineLFAsArray = synthesizeQueryTextRectArray(1, 1);
6747 if (!checkQueryContentResult(firstLineLFAsArray, "runBug722639Test: 1st line's \\n rect as array") ||
6748 !checkRectArray(firstLineLFAsArray, [firstLineLF], "runBug722639Test: 1st line's rect as array should match with text rect result")) {
6749 return;
6752 let secondLine = synthesizeQueryTextRect(kLFLen, 1);
6753 if (!checkQueryContentResult(secondLine,
6754 "runBug722639Test: secondLine")) {
6755 return;
6757 ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
6758 let secondLineAsArray = synthesizeQueryTextRectArray(kLFLen, 1);
6759 if (!checkQueryContentResult(secondLineAsArray, "runBug722639Test: 2nd line as array") ||
6760 !checkRectArray(secondLineAsArray, [secondLine], "runBug722639Test: 2nd line as array should match with text rect result")) {
6761 return;
6763 if (kLFLen > 1) {
6764 let secondLineLF = synthesizeQueryTextRect(kLFLen + 1, 1);
6765 if (!checkQueryContentResult(secondLineLF,
6766 "runBug722639Test: secondLineLF")) {
6767 return;
6769 is(secondLineLF.top, secondLine.top, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
6770 is(secondLineLF.left, secondLine.left, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
6771 isfuzzy(secondLineLF.height, secondLine.height, 1,
6772 "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
6773 is(secondLineLF.width, secondLine.width, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
6774 let secondLineLFAsArray = synthesizeQueryTextRectArray(kLFLen + 1, 1);
6775 if (!checkQueryContentResult(secondLineLFAsArray, "runBug722639Test: 2nd line's \\n rect as array") ||
6776 !checkRectArray(secondLineLFAsArray, [secondLineLF], "runBug722639Test: 2nd line's rect as array should match with text rect result")) {
6777 return;
6780 let lineHeight = secondLine.top - firstLine.top;
6781 ok(lineHeight > 0,
6782 "runBug722639Test: lineHeight must be positive");
6783 is(secondLine.left, firstLine.left,
6784 "runBug722639Test: the left value must be always same value");
6785 isfuzzy(secondLine.height, firstLine.height, 1,
6786 "runBug722639Test: the height must be always same value");
6787 let previousTop = secondLine.top;
6788 for (let i = 3; i <= textarea.value.length + 1; i++) {
6789 let currentLine = synthesizeQueryTextRect(kLFLen * (i - 1), 1);
6790 if (!checkQueryContentResult(currentLine,
6791 "runBug722639Test: " + i + "th currentLine")) {
6792 return;
6794 ok(true, "runBug722639Test: " + i + "th line, top=" + currentLine.top + ", left=" + currentLine.left);
6795 let currentLineAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1), 1);
6796 if (!checkQueryContentResult(currentLineAsArray, "runBug722639Test: " + i + "th line as array") ||
6797 !checkRectArray(currentLineAsArray, [currentLine], "runBug722639Test: " + i + "th line as array should match with text rect result")) {
6798 return;
6800 // NOTE: the top position may be 1px larger or smaller than other lines
6801 // due to sub pixel positioning.
6802 if (Math.abs(currentLine.top - (previousTop + lineHeight)) <= 1) {
6803 ok(true, "runBug722639Test: " + i + "th line's top is expected");
6804 } else {
6805 is(currentLine.top, previousTop + lineHeight,
6806 "runBug722639Test: " + i + "th line's top is unexpected");
6808 is(currentLine.left, firstLine.left,
6809 "runBug722639Test: " + i + "th line's left is unexpected");
6810 isfuzzy(currentLine.height, firstLine.height, 1,
6811 `runBug722639Test: ${i}th line's height is unexpected`);
6812 if (kLFLen > 1) {
6813 let currentLineLF = synthesizeQueryTextRect(kLFLen * (i - 1) + 1, 1);
6814 if (!checkQueryContentResult(currentLineLF,
6815 "runBug722639Test: " + i + "th currentLineLF")) {
6816 return;
6818 is(currentLineLF.top, currentLine.top, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
6819 is(currentLineLF.left, currentLine.left, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
6820 isfuzzy(currentLineLF.height, currentLine.height, 1,
6821 `runBug722639Test: ${i}th line's \\n rect should be same as same line's \\r rect`);
6822 is(currentLineLF.width, currentLine.width, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
6823 let currentLineLFAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1) + 1, 1);
6824 if (!checkQueryContentResult(currentLineLFAsArray, "runBug722639Test: " + i + "th line's \\n rect as array") ||
6825 !checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
6826 return;
6829 previousTop = currentLine.top;
6833 function runCompositionWithSelectionChange() {
6834 function doTest(aEditor, aDescription) {
6835 aEditor.focus();
6836 const isHTMLEditor =
6837 aEditor.nodeName.toLowerCase() != "input" && aEditor.nodeName.toLowerCase() != "textarea";
6838 const win = isHTMLEditor ? windowOfContenteditable : window;
6839 function getValue() {
6840 return isHTMLEditor ? aEditor.innerHTML : aEditor.value;
6842 function setSelection(aStart, aLength) {
6843 if (isHTMLEditor) {
6844 win.getSelection().setBaseAndExtent(aEditor.firstChild, aStart, aEditor.firstChild, aStart + aLength);
6845 } else {
6846 aEditor.setSelectionRange(aStart, aStart + aLength);
6850 if (isHTMLEditor) {
6851 aEditor.innerHTML = "abcxyz";
6852 } else {
6853 aEditor.value = "abcxyz";
6855 setSelection("abc".length, 0);
6857 synthesizeCompositionChange({
6858 composition: {
6859 string: "1",
6860 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6861 caret: { start: 1, length: 0 },
6865 is(getValue(), "abc1xyz",
6866 `${aDescription}: First composing character should be inserted middle of the text`);
6868 aEditor.addEventListener("compositionupdate", () => {
6869 setSelection("abc".length, "1".length);
6870 }, {once: true});
6872 synthesizeCompositionChange({
6873 composition: {
6874 string: "12",
6875 clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6876 caret: { start: 2, length: 0 },
6880 is(getValue(), "abc12xyz",
6881 `${aDescription}: Only composition string should be updated even if selection range is updated by "compositionupdate" event listener`);
6883 aEditor.addEventListener("compositionupdate", () => {
6884 setSelection("abc1".length, "2d".length);
6885 }, {once: true});
6887 synthesizeCompositionChange({
6888 composition: {
6889 string: "123",
6890 clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6891 caret: { start: 3, length: 0 },
6895 is(getValue(), "abc123xyz",
6896 `${aDescription}: Only composition string should be updated even if selection range wider than composition string is updated by "compositionupdate" event listener`);
6898 aEditor.addEventListener("compositionupdate", () => {
6899 setSelection("ab".length, "c123d".length);
6900 }, {once: true});
6902 synthesizeCompositionChange({
6903 composition: {
6904 string: "456",
6905 clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
6906 caret: { start: 3, length: 0 },
6910 is(getValue(), "abc456xyz",
6911 `${aDescription}: Only composition string should be updated even if selection range which covers all over the composition string is updated by "compositionupdate" event listener`);
6913 aEditor.addEventListener("beforeinput", () => {
6914 setSelection("abc456d".length, 0);
6915 }, {once: true});
6917 synthesizeComposition({ type: "compositioncommitasis" });
6919 is(getValue(), "abc456xyz",
6920 `${aDescription}: Only composition string should be updated when committing composition but selection is updated by "beforeinput" event listener`);
6921 if (isHTMLEditor) {
6922 is(win.getSelection().focusNode, aEditor.firstChild,
6923 `${aDescription}: The focus node after composition should be the text node`);
6924 is(win.getSelection().focusOffset, "abc456".length,
6925 `${aDescription}: The focus offset after composition should be end of the composition string`);
6926 is(win.getSelection().anchorNode, aEditor.firstChild,
6927 `${aDescription}: The anchor node after composition should be the text node`);
6928 is(win.getSelection().anchorOffset, "abc456".length,
6929 `${aDescription}: The anchor offset after composition should be end of the composition string`);
6930 } else {
6931 is(aEditor.selectionStart, "abc456".length,
6932 `${aDescription}: The selectionStart after composition should be end of the composition string`);
6933 is(aEditor.selectionEnd, "abc456".length,
6934 `${aDescription}: The selectionEnd after composition should be end of the composition string`);
6937 doTest(textarea, "runCompositionWithSelectionChange(textarea)");
6938 doTest(input, "runCompositionWithSelectionChange(input)");
6939 doTest(contenteditable, "runCompositionWithSelectionChange(contenteditable)");
6942 function runForceCommitTest()
6944 let events;
6945 function eventHandler(aEvent)
6947 events.push(aEvent);
6949 window.addEventListener("compositionstart", eventHandler, true);
6950 window.addEventListener("compositionupdate", eventHandler, true);
6951 window.addEventListener("compositionend", eventHandler, true);
6952 window.addEventListener("beforeinput", eventHandler, true);
6953 window.addEventListener("input", eventHandler, true);
6954 window.addEventListener("text", eventHandler, true);
6956 // Make the composition in textarea commit by click in the textarea
6957 textarea.focus();
6958 textarea.value = "";
6960 events = [];
6961 synthesizeCompositionChange(
6962 { "composition":
6963 { "string": "\u306E",
6964 "clauses":
6966 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
6969 "caret": { "start": 1, "length": 0 }
6972 is(events.length, 5,
6973 "runForceCommitTest: wrong event count #1");
6974 is(events[0].type, "compositionstart",
6975 "runForceCommitTest: the 1st event must be compositionstart #1");
6976 is(events[1].type, "compositionupdate",
6977 "runForceCommitTest: the 2nd event must be compositionupdate #1");
6978 is(events[2].type, "text",
6979 "runForceCommitTest: the 3rd event must be text #1");
6980 is(events[3].type, "beforeinput",
6981 "runForceCommitTest: the 4th event must be beforeinput #1");
6982 checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
6983 "runForceCommitTest #1");
6984 is(events[4].type, "input",
6985 "runForceCommitTest: the 5th event must be input #1");
6986 checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
6987 "runForceCommitTest #1");
6989 events = [];
6990 synthesizeMouseAtCenter(textarea, {});
6992 is(events.length, 4,
6993 "runForceCommitTest: wrong event count #2");
6994 is(events[0].type, "text",
6995 "runForceCommitTest: the 1st event must be text #2");
6996 is(events[0].target, textarea,
6997 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
6998 is(events[1].type, "beforeinput",
6999 "runForceCommitTest: the 2nd event must be beforeinput #2");
7000 is(events[1].target, textarea,
7001 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
7002 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7003 "runForceCommitTest #2");
7004 is(events[2].type, "compositionend",
7005 "runForceCommitTest: the 3rd event must be compositionend #2");
7006 is(events[2].target, textarea,
7007 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
7008 is(events[2].data, "\u306E",
7009 "runForceCommitTest: compositionend has wrong data #2");
7010 is(events[3].type, "input",
7011 "runForceCommitTest: the 4th event must be input #2");
7012 is(events[3].target, textarea,
7013 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
7014 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7015 "runForceCommitTest #2");
7016 ok(!getEditor(textarea).isComposing,
7017 "runForceCommitTest: the textarea still has composition #2");
7018 is(textarea.value, "\u306E",
7019 "runForceCommitTest: the textarea doesn't have the committed text #2");
7021 // Make the composition in textarea commit by click in another editor (input)
7022 textarea.focus();
7023 textarea.value = "";
7024 input.value = "";
7026 synthesizeCompositionChange(
7027 { "composition":
7028 { "string": "\u306E",
7029 "clauses":
7031 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7034 "caret": { "start": 1, "length": 0 }
7037 events = [];
7038 synthesizeMouseAtCenter(input, {});
7040 is(events.length, 4,
7041 "runForceCommitTest: wrong event count #3");
7042 is(events[0].type, "text",
7043 "runForceCommitTest: the 1st event must be text #3");
7044 is(events[0].target, textarea,
7045 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #3`);
7046 is(events[1].type, "beforeinput",
7047 "runForceCommitTest: the 2nd event must be beforeinput #3");
7048 is(events[1].target, textarea,
7049 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #3`);
7050 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7051 "runForceCommitTest #3");
7052 is(events[2].type, "compositionend",
7053 "runForceCommitTest: the 3rd event must be compositionend #3");
7054 is(events[2].target, textarea,
7055 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #3`);
7056 is(events[2].data, "\u306E",
7057 "runForceCommitTest: compositionend has wrong data #3");
7058 is(events[3].type, "input",
7059 "runForceCommitTest: the 4th event must be input #3");
7060 is(events[3].target, textarea,
7061 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #3`);
7062 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7063 "runForceCommitTest #3");
7064 ok(!getEditor(textarea).isComposing,
7065 "runForceCommitTest: the textarea still has composition #3");
7066 ok(!getEditor(input).isComposing,
7067 "runForceCommitTest: the input has composition #3");
7068 is(textarea.value, "\u306E",
7069 "runForceCommitTest: the textarea doesn't have the committed text #3");
7070 is(input.value, "",
7071 "runForceCommitTest: the input has the committed text? #3");
7073 // Make the composition in textarea commit by blur()
7074 textarea.focus();
7075 textarea.value = "";
7077 synthesizeCompositionChange(
7078 { "composition":
7079 { "string": "\u306E",
7080 "clauses":
7082 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7085 "caret": { "start": 1, "length": 0 }
7088 events = [];
7089 textarea.blur();
7091 is(events.length, 4,
7092 "runForceCommitTest: wrong event count #4");
7093 is(events[0].type, "text",
7094 "runForceCommitTest: the 1st event must be text #4");
7095 is(events[0].target, textarea,
7096 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #4`);
7097 is(events[1].type, "beforeinput",
7098 "runForceCommitTest: the 2nd event must be beforeinput #4");
7099 is(events[1].target, textarea,
7100 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #4`);
7101 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7102 "runForceCommitTest #4");
7103 is(events[2].type, "compositionend",
7104 "runForceCommitTest: the 3rd event must be compositionend #4");
7105 is(events[2].target, textarea,
7106 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #4`);
7107 is(events[2].data, "\u306E",
7108 "runForceCommitTest: compositionend has wrong data #4");
7109 is(events[3].type, "input",
7110 "runForceCommitTest: the 4th event must be input #4");
7111 is(events[3].target, textarea,
7112 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #4`);
7113 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7114 "runForceCommitTest #4");
7115 ok(!getEditor(textarea).isComposing,
7116 "runForceCommitTest: the textarea still has composition #4");
7117 is(textarea.value, "\u306E",
7118 "runForceCommitTest: the textarea doesn't have the committed text #4");
7120 // Make the composition in textarea commit by input.focus()
7121 textarea.focus();
7122 textarea.value = "";
7123 input.value = "";
7125 synthesizeCompositionChange(
7126 { "composition":
7127 { "string": "\u306E",
7128 "clauses":
7130 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7133 "caret": { "start": 1, "length": 0 }
7136 events = [];
7137 input.focus();
7139 is(events.length, 4,
7140 "runForceCommitTest: wrong event count #5");
7141 is(events[0].type, "text",
7142 "runForceCommitTest: the 1st event must be text #5");
7143 is(events[0].target, textarea,
7144 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #5`);
7145 is(events[1].type, "beforeinput",
7146 "runForceCommitTest: the 2nd event must be beforeinput #5");
7147 is(events[1].target, textarea,
7148 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #5`);
7149 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7150 "runForceCommitTest #5");
7151 is(events[2].type, "compositionend",
7152 "runForceCommitTest: the 3rd event must be compositionend #5");
7153 is(events[2].target, textarea,
7154 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #5`);
7155 is(events[2].data, "\u306E",
7156 "runForceCommitTest: compositionend has wrong data #5");
7157 is(events[3].type, "input",
7158 "runForceCommitTest: the 4th event must be input #5");
7159 is(events[3].target, textarea,
7160 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #5`);
7161 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7162 "runForceCommitTest #5");
7163 ok(!getEditor(textarea).isComposing,
7164 "runForceCommitTest: the textarea still has composition #5");
7165 ok(!getEditor(input).isComposing,
7166 "runForceCommitTest: the input has composition #5");
7167 is(textarea.value, "\u306E",
7168 "runForceCommitTest: the textarea doesn't have the committed text #5");
7169 is(input.value, "",
7170 "runForceCommitTest: the input has the committed text? #5");
7172 // Make the composition in textarea commit by click in another document's editor
7173 textarea.focus();
7174 textarea.value = "";
7175 textareaInFrame.value = "";
7177 synthesizeCompositionChange(
7178 { "composition":
7179 { "string": "\u306E",
7180 "clauses":
7182 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7185 "caret": { "start": 1, "length": 0 }
7188 events = [];
7189 synthesizeMouseAtCenter(textareaInFrame, {}, iframe.contentWindow);
7191 is(events.length, 4,
7192 "runForceCommitTest: wrong event count #6");
7193 is(events[0].type, "text",
7194 "runForceCommitTest: the 1st event must be text #6");
7195 is(events[0].target, textarea,
7196 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #6`);
7197 is(events[1].type, "beforeinput",
7198 "runForceCommitTest: the 2nd event must be beforeinput #6");
7199 is(events[1].target, textarea,
7200 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #6`);
7201 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7202 "runForceCommitTest #6");
7203 is(events[2].type, "compositionend",
7204 "runForceCommitTest: the 3rd event must be compositionend #6");
7205 is(events[2].target, textarea,
7206 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #6`);
7207 is(events[2].data, "\u306E",
7208 "runForceCommitTest: compositionend has wrong data #6");
7209 is(events[3].type, "input",
7210 "runForceCommitTest: the 4th event must be input #6");
7211 is(events[3].target, textarea,
7212 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #6`);
7213 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7214 "runForceCommitTest #6");
7215 ok(!getEditor(textarea).isComposing,
7216 "runForceCommitTest: the textarea still has composition #6");
7217 ok(!getEditor(textareaInFrame).isComposing,
7218 "runForceCommitTest: the textarea in frame has composition #6");
7219 is(textarea.value, "\u306E",
7220 "runForceCommitTest: the textarea doesn't have the committed text #6");
7221 is(textareaInFrame.value, "",
7222 "runForceCommitTest: the textarea in frame has the committed text? #6");
7224 // Make the composition in textarea commit by another document's editor's focus()
7225 textarea.focus();
7226 textarea.value = "";
7227 textareaInFrame.value = "";
7229 synthesizeCompositionChange(
7230 { "composition":
7231 { "string": "\u306E",
7232 "clauses":
7234 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7237 "caret": { "start": 1, "length": 0 }
7240 events = [];
7241 textareaInFrame.focus();
7243 is(events.length, 4,
7244 "runForceCommitTest: wrong event count #7");
7245 is(events[0].type, "text",
7246 "runForceCommitTest: the 1st event must be text #7");
7247 is(events[0].target, textarea,
7248 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #7`);
7249 is(events[1].type, "beforeinput",
7250 "runForceCommitTest: the 2nd event must be beforeinput #7");
7251 is(events[1].target, textarea,
7252 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #7`);
7253 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7254 "runForceCommitTest #7");
7255 is(events[2].type, "compositionend",
7256 "runForceCommitTest: the 3rd event must be compositionend #7");
7257 is(events[2].target, textarea,
7258 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #7`);
7259 is(events[2].data, "\u306E",
7260 "runForceCommitTest: compositionend has wrong data #7");
7261 is(events[3].type, "input",
7262 "runForceCommitTest: the 4th event must be input #7");
7263 is(events[3].target, textarea,
7264 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #7`);
7265 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7266 "runForceCommitTest #7");
7267 ok(!getEditor(textarea).isComposing,
7268 "runForceCommitTest: the textarea still has composition #7");
7269 ok(!getEditor(textareaInFrame).isComposing,
7270 "runForceCommitTest: the textarea in frame has composition #7");
7271 is(textarea.value, "\u306E",
7272 "runForceCommitTest: the textarea doesn't have the committed text #7");
7273 is(textareaInFrame.value, "",
7274 "runForceCommitTest: the textarea in frame has the committed text? #7");
7276 // Make the composition in a textarea commit by click in another editable document
7277 textarea.focus();
7278 textarea.value = "";
7279 iframe2.contentDocument.body.innerHTML = "Text in the Body";
7280 let iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
7282 synthesizeCompositionChange(
7283 { "composition":
7284 { "string": "\u306E",
7285 "clauses":
7287 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7290 "caret": { "start": 1, "length": 0 }
7293 events = [];
7294 synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
7296 is(events.length, 4,
7297 "runForceCommitTest: wrong event count #8");
7298 is(events[0].type, "text",
7299 "runForceCommitTest: the 1st event must be text #8");
7300 is(events[0].target, textarea,
7301 `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #8`);
7302 is(events[1].type, "beforeinput",
7303 "runForceCommitTest: the 2nd event must be beforeinput #8");
7304 is(events[1].target, textarea,
7305 `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #8`);
7306 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7307 "runForceCommitTest #8");
7308 is(events[2].type, "compositionend",
7309 "runForceCommitTest: the 3rd event must be compositionend #8");
7310 is(events[2].target, textarea,
7311 `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #8`);
7312 is(events[2].data, "\u306E",
7313 "runForceCommitTest: compositionend has wrong data #8");
7314 is(events[3].type, "input",
7315 "runForceCommitTest: the 4th event must be input #8");
7316 is(events[3].target, textarea,
7317 `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #8`);
7318 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7319 "runForceCommitTest #8");
7320 ok(!getEditor(textarea).isComposing,
7321 "runForceCommitTest: the textarea still has composition #8");
7322 ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
7323 "runForceCommitTest: the editable document has composition #8");
7324 is(textarea.value, "\u306E",
7325 "runForceCommitTest: the textarea doesn't have the committed text #8");
7326 is(iframe2.contentDocument.body.innerHTML, iframe2BodyInnerHTML,
7327 "runForceCommitTest: the editable document has the committed text? #8");
7329 // Make the composition in an editable document commit by click in it
7330 iframe2.contentWindow.focus();
7331 iframe2.contentDocument.body.innerHTML = "Text in the Body";
7332 iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
7334 synthesizeCompositionChange(
7335 { "composition":
7336 { "string": "\u306E",
7337 "clauses":
7339 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7342 "caret": { "start": 1, "length": 0 }
7343 }, iframe2.contentWindow);
7345 events = [];
7346 synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);
7348 is(events.length, 4,
7349 "runForceCommitTest: wrong event count #9");
7350 is(events[0].type, "text",
7351 "runForceCommitTest: the 1st event must be text #9");
7352 is(events[0].target, iframe2.contentDocument.body,
7353 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #9`);
7354 is(events[1].type, "beforeinput",
7355 "runForceCommitTest: the 2nd event must be beforeinput #9");
7356 is(events[1].target, iframe2.contentDocument.body,
7357 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #9`);
7358 checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
7359 [{startContainer: iframe2.contentDocument.body.firstChild,
7360 startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
7361 endContainer: iframe2.contentDocument.body.firstChild,
7362 endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
7363 "runForceCommitTest #9");
7364 is(events[2].type, "compositionend",
7365 "runForceCommitTest: the 3rd event must be compositionend #9");
7366 is(events[2].target, iframe2.contentDocument.body,
7367 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #9`);
7368 is(events[2].data, "\u306E",
7369 "runForceCommitTest: compositionend has wrong data #9");
7370 is(events[3].type, "input",
7371 "runForceCommitTest: the 4th event must be input #9");
7372 is(events[3].target, iframe2.contentDocument.body,
7373 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #9`);
7374 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7375 "runForceCommitTest #9");
7376 ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
7377 "runForceCommitTest: the editable document still has composition #9");
7378 ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
7379 iframe2.contentDocument.body.innerHTML.includes("\u306E"),
7380 "runForceCommitTest: the editable document doesn't have the committed text #9");
7382 // Make the composition in an editable document commit by click in another document's editor
7383 textarea.value = "";
7384 iframe2.contentWindow.focus();
7385 iframe2.contentDocument.body.innerHTML = "Text in the Body";
7386 iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
7388 synthesizeCompositionChange(
7389 { "composition":
7390 { "string": "\u306E",
7391 "clauses":
7393 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7396 "caret": { "start": 1, "length": 0 }
7397 }, iframe2.contentWindow);
7399 events = [];
7400 synthesizeMouseAtCenter(textarea, {});
7402 is(events.length, 4,
7403 "runForceCommitTest: wrong event count #10");
7404 is(events[0].type, "text",
7405 "runForceCommitTest: the 1st event must be text #10");
7406 is(events[0].target, iframe2.contentDocument.body,
7407 `runForceCommitTest: The ${events[0].type} event was fired on wrong event target #10`);
7408 is(events[1].type, "beforeinput",
7409 "runForceCommitTest: the 2nd event must be beforeinput #10");
7410 is(events[1].target, iframe2.contentDocument.body,
7411 `runForceCommitTest: The ${events[1].type} event was fired on wrong event target #10`);
7412 checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
7413 [{startContainer: iframe2.contentDocument.body.firstChild,
7414 startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
7415 endContainer: iframe2.contentDocument.body.firstChild,
7416 endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
7417 "runForceCommitTest #10");
7418 is(events[2].type, "compositionend",
7419 "runForceCommitTest: the 3rd event must be compositionend #10");
7420 is(events[2].target, iframe2.contentDocument.body,
7421 `runForceCommitTest: The ${events[2].type} event was fired on wrong event target #10`);
7422 is(events[2].data, "\u306E",
7423 "runForceCommitTest: compositionend has wrong data #10");
7424 is(events[3].type, "input",
7425 "runForceCommitTest: the 4th event must be input #10");
7426 is(events[3].target, iframe2.contentDocument.body,
7427 `runForceCommitTest: The ${events[3].type} event was fired on wrong event target #10`);
7428 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7429 "runForceCommitTest #10");
7430 ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
7431 "runForceCommitTest: the editable document still has composition #10");
7432 ok(!getEditor(textarea).isComposing,
7433 "runForceCommitTest: the textarea has composition #10");
7434 ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
7435 iframe2.contentDocument.body.innerHTML.includes("\u306E"),
7436 "runForceCommitTest: the editable document doesn't have the committed text #10");
7437 is(textarea.value, "",
7438 "runForceCommitTest: the textarea has the committed text? #10");
7440 // Make the composition in an editable document commit by click in the another editable document
7441 iframe2.contentWindow.focus();
7442 iframe2.contentDocument.body.innerHTML = "Text in the Body";
7443 iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
7444 iframe3.contentDocument.body.innerHTML = "Text in the Body";
7445 let iframe3BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
7447 synthesizeCompositionChange(
7448 { "composition":
7449 { "string": "\u306E",
7450 "clauses":
7452 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7455 "caret": { "start": 1, "length": 0 }
7456 }, iframe2.contentWindow);
7458 events = [];
7459 synthesizeMouseAtCenter(iframe3.contentDocument.body, {}, iframe3.contentWindow);
7461 is(events.length, 4,
7462 "runForceCommitTest: wrong event count #11");
7463 is(events[0].type, "text",
7464 "runForceCommitTest: the 1st event must be text #11");
7465 is(events[0].target, iframe2.contentDocument.body,
7466 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #11`);
7467 is(events[1].type, "beforeinput",
7468 "runForceCommitTest: the 2nd event must be beforeinput #11");
7469 is(events[1].target, iframe2.contentDocument.body,
7470 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #11`);
7471 checkInputEvent(events[1], true, "insertCompositionText", "\u306E",
7472 [{startContainer: iframe2.contentDocument.body.firstChild,
7473 startOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E"),
7474 endContainer: iframe2.contentDocument.body.firstChild,
7475 endOffset: iframe2.contentDocument.body.firstChild.wholeText.indexOf("\u306E") + 1}],
7476 "runForceCommitTest #11");
7477 is(events[2].type, "compositionend",
7478 "runForceCommitTest: the 3rd event must be compositionend #11");
7479 is(events[2].target, iframe2.contentDocument.body,
7480 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #11`);
7481 is(events[2].data, "\u306E",
7482 "runForceCommitTest: compositionend has wrong data #11");
7483 is(events[3].type, "input",
7484 "runForceCommitTest: the 4th event must be input #11");
7485 is(events[3].target, iframe2.contentDocument.body,
7486 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #11`);
7487 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7488 "runForceCommitTest #11");
7489 ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
7490 "runForceCommitTest: the editable document still has composition #11");
7491 ok(!getHTMLEditorIMESupport(iframe3.contentWindow).isComposing,
7492 "runForceCommitTest: the other editable document has composition #11");
7493 ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
7494 iframe2.contentDocument.body.innerHTML.includes("\u306E"),
7495 "runForceCommitTest: the editable document doesn't have the committed text #11");
7496 is(iframe3.contentDocument.body.innerHTML, iframe3BodyInnerHTML,
7497 "runForceCommitTest: the other editable document has the committed text? #11");
7499 input.focus();
7500 input.value = "";
7502 synthesizeCompositionChange(
7503 { "composition":
7504 { "string": "\u306E",
7505 "clauses":
7507 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7510 "caret": { "start": 1, "length": 0 }
7513 events = [];
7514 input.value = "set value";
7516 is(events.length, 4,
7517 "runForceCommitTest: wrong event count #12");
7518 is(events[0].type, "text",
7519 "runForceCommitTest: the 1st event must be text #12");
7520 is(events[0].target, input,
7521 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #12`);
7522 is(events[1].type, "beforeinput",
7523 "runForceCommitTest: the 2nd event must be beforeinput #12");
7524 is(events[1].target, input,
7525 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #12`);
7526 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7527 "runForceCommitTest #12");
7528 is(events[2].type, "compositionend",
7529 "runForceCommitTest: the 3rd event must be compositionend #12");
7530 is(events[2].target, input,
7531 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #12`);
7532 is(events[2].data, "\u306E",
7533 "runForceCommitTest: compositionend has wrong data #12");
7534 is(events[3].type, "input",
7535 "runForceCommitTest: the 4th event must be input #12");
7536 is(events[3].target, input,
7537 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #12`);
7538 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7539 "runForceCommitTest #12");
7540 ok(!getEditor(input).isComposing,
7541 "runForceCommitTest: the input still has composition #12");
7542 is(input.value, "set value",
7543 "runForceCommitTest: the input doesn't have the set text #12");
7545 textarea.focus();
7546 textarea.value = "";
7548 synthesizeCompositionChange(
7549 { "composition":
7550 { "string": "\u306E",
7551 "clauses":
7553 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7556 "caret": { "start": 1, "length": 0 }
7559 events = [];
7560 textarea.value = "set value";
7562 is(events.length, 4,
7563 "runForceCommitTest: wrong event count #13");
7564 is(events[0].type, "text",
7565 "runForceCommitTest: the 1st event must be text #13");
7566 is(events[0].target, textarea,
7567 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #13`);
7568 is(events[1].type, "beforeinput",
7569 "runForceCommitTest: the 2nd event must be beforeinput #13");
7570 is(events[1].target, textarea,
7571 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #13`);
7572 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7573 "runForceCommitTest #13");
7574 is(events[2].type, "compositionend",
7575 "runForceCommitTest: the 3rd event must be compositionend #13");
7576 is(events[2].target, textarea,
7577 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #13`);
7578 is(events[2].data, "\u306E",
7579 "runForceCommitTest: compositionend has wrong data #13");
7580 is(events[3].type, "input",
7581 "runForceCommitTest: the 4th event must be input #13");
7582 is(events[3].target, textarea,
7583 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #13`);
7584 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7585 "runForceCommitTest #13");
7586 ok(!getEditor(textarea).isComposing,
7587 "runForceCommitTest: the textarea still has composition #13");
7588 is(textarea.value, "set value",
7589 "runForceCommitTest: the textarea doesn't have the set text #13");
7591 input.focus();
7592 input.value = "";
7594 synthesizeCompositionChange(
7595 { "composition":
7596 { "string": "\u306E",
7597 "clauses":
7599 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7602 "caret": { "start": 1, "length": 0 }
7605 events = [];
7606 input.value += " appended value";
7608 is(events.length, 4,
7609 "runForceCommitTest: wrong event count #14");
7610 is(events[0].type, "text",
7611 "runForceCommitTest: the 1st event must be text #14");
7612 is(events[0].target, input,
7613 `runForceCommitTest: The "${events[0].type}" event was fired on wrong event target #14`);
7614 is(events[1].type, "beforeinput",
7615 "runForceCommitTest: the 2nd event must be beforeinput #14");
7616 is(events[1].target, input,
7617 `runForceCommitTest: The "${events[1].type}" event was fired on wrong event target #14`);
7618 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7619 "runForceCommitTest #14");
7620 is(events[2].type, "compositionend",
7621 "runForceCommitTest: the 3rd event must be compositionend #14");
7622 is(events[2].target, input,
7623 `runForceCommitTest: The "${events[2].type}" event was fired on wrong event target #14`);
7624 is(events[2].data, "\u306E",
7625 "runForceCommitTest: compositionend has wrong data #14");
7626 is(events[3].type, "input",
7627 "runForceCommitTest: the 4th event must be input #14");
7628 is(events[3].target, input,
7629 `runForceCommitTest: The "${events[3].type}" event was fired on wrong event target #14`);
7630 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7631 "runForceCommitTest #14");
7632 ok(!getEditor(input).isComposing,
7633 "runForceCommitTest: the input still has composition #14");
7634 is(input.value, "\u306E appended value",
7635 "runForceCommitTest: the input should have both composed text and appended text #14");
7637 input.focus();
7638 input.value = "abcd";
7640 synthesizeCompositionChange(
7641 { "composition":
7642 { "string": "\u306E",
7643 "clauses":
7645 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7648 "caret": { "start": 1, "length": 0 }
7651 events = [];
7652 input.value = "abcd\u306E";
7654 is(events.length, 0,
7655 "runForceCommitTest: setting same value to input with composition shouldn't cause any events #15");
7656 is(input.value, "abcd\u306E",
7657 "runForceCommitTest: the input has unexpected value #15");
7659 input.blur(); // commit composition
7661 textarea.focus();
7662 textarea.value = "abcd";
7664 synthesizeCompositionChange(
7665 { "composition":
7666 { "string": "\u306E",
7667 "clauses":
7669 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7672 "caret": { "start": 1, "length": 0 }
7675 events = [];
7676 textarea.value = "abcd\u306E";
7678 is(events.length, 0,
7679 "runForceCommitTest: setting same value to textarea with composition shouldn't cause any events #16");
7680 is(textarea.value, "abcd\u306E",
7681 "runForceCommitTest: the input has unexpected value #16");
7683 textarea.blur(); // commit composition
7685 window.removeEventListener("compositionstart", eventHandler, true);
7686 window.removeEventListener("compositionupdate", eventHandler, true);
7687 window.removeEventListener("compositionend", eventHandler, true);
7688 window.removeEventListener("beforeinput", eventHandler, true);
7689 window.removeEventListener("input", eventHandler, true);
7690 window.removeEventListener("text", eventHandler, true);
7693 function runNestedSettingValue()
7695 let isTesting = false;
7696 let events = [];
7697 function eventHandler(aEvent)
7699 events.push(aEvent);
7700 if (isTesting) {
7701 aEvent.target.value += aEvent.type + ", ";
7704 window.addEventListener("compositionstart", eventHandler, true);
7705 window.addEventListener("compositionupdate", eventHandler, true);
7706 window.addEventListener("compositionend", eventHandler, true);
7707 window.addEventListener("beforeinput", eventHandler, true);
7708 window.addEventListener("input", eventHandler, true);
7709 window.addEventListener("text", eventHandler, true);
7711 textarea.focus();
7712 textarea.value = "";
7714 synthesizeCompositionChange(
7715 { "composition":
7716 { "string": "\u306E",
7717 "clauses":
7719 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7722 "caret": { "start": 1, "length": 0 }
7725 events = [];
7726 isTesting = true;
7727 textarea.value = "first setting value, ";
7728 isTesting = false;
7730 is(events.length, 4,
7731 "runNestedSettingValue: wrong event count #1");
7732 is(events[0].type, "text",
7733 "runNestedSettingValue: the 1st event must be text #1");
7734 is(events[0].target, textarea,
7735 `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #1`);
7736 is(events[1].type, "beforeinput",
7737 "runNestedSettingValue: the 2nd event must be beforeinput #1");
7738 is(events[1].target, textarea,
7739 `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #1`);
7740 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7741 "runNestedSettingValue #1");
7742 is(events[2].type, "compositionend",
7743 "runNestedSettingValue: the 3rd event must be compositionend #1");
7744 is(events[2].target, textarea,
7745 `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #1`);
7746 is(events[2].data, "\u306E",
7747 "runNestedSettingValue: compositionend has wrong data #1");
7748 is(events[3].type, "input",
7749 "runNestedSettingValue: the 4th event must be input #1");
7750 is(events[3].target, textarea,
7751 `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #1`);
7752 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7753 "runNestedSettingValue #1");
7754 ok(!getEditor(textarea).isComposing,
7755 "runNestedSettingValue: the textarea still has composition #1");
7756 is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
7757 "runNestedSettingValue: the textarea should have all string set to value attribute");
7759 input.focus();
7760 input.value = "";
7762 synthesizeCompositionChange(
7763 { "composition":
7764 { "string": "\u306E",
7765 "clauses":
7767 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7770 "caret": { "start": 1, "length": 0 }
7773 events = [];
7774 isTesting = true;
7775 input.value = "first setting value, ";
7776 isTesting = false;
7778 is(events.length, 4,
7779 "runNestedSettingValue: wrong event count #2");
7780 is(events[0].type, "text",
7781 "runNestedSettingValue: the 1st event must be text #2");
7782 is(events[0].target, input,
7783 `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #2`);
7784 is(events[1].type, "beforeinput",
7785 "runNestedSettingValue: the 2nd event must be beforeinput #2");
7786 is(events[1].target, input,
7787 `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #2`);
7788 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7789 "runNestedSettingValue #2");
7790 is(events[2].type, "compositionend",
7791 "runNestedSettingValue: the 3rd event must be compositionend #2");
7792 is(events[2].target, input,
7793 `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #2`);
7794 is(events[2].data, "\u306E",
7795 "runNestedSettingValue: compositionend has wrong data #2");
7796 is(events[3].type, "input",
7797 "runNestedSettingValue: the 4th event must be input #2");
7798 is(events[3].target, input,
7799 `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #2`);
7800 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7801 "runNestedSettingValue #2");
7802 ok(!getEditor(input).isComposing,
7803 "runNestedSettingValue: the input still has composition #2");
7804 is(textarea.value, "first setting value, text, beforeinput, compositionend, input, ",
7805 "runNestedSettingValue: the input should have all string set to value attribute #2");
7807 textarea.focus();
7808 textarea.value = "";
7810 synthesizeCompositionChange(
7811 { "composition":
7812 { "string": "\u306E",
7813 "clauses":
7815 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7818 "caret": { "start": 1, "length": 0 }
7821 events = [];
7822 isTesting = true;
7823 textarea.setRangeText("first setting value, ");
7824 isTesting = false;
7826 is(events.length, 4,
7827 "runNestedSettingValue: wrong event count #3");
7828 is(events[0].type, "text",
7829 "runNestedSettingValue: the 1st event must be text #3");
7830 is(events[0].target, textarea,
7831 `runNestedSettingValue: The ${events[0].type} event was fired on wrong event target #3`);
7832 is(events[1].type, "beforeinput",
7833 "runNestedSettingValue: the 2nd event must be beforeinput #3");
7834 is(events[1].target, textarea,
7835 `runNestedSettingValue: The ${events[1].type} event was fired on wrong event target #3`);
7836 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7837 "runNestedSettingValue #3");
7838 is(events[2].type, "compositionend",
7839 "runNestedSettingValue: the 3rd event must be compositionend #3");
7840 is(events[2].target, textarea,
7841 `runNestedSettingValue: The ${events[2].type} event was fired on wrong event target #3`);
7842 is(events[2].data, "\u306E",
7843 "runNestedSettingValue: compositionend has wrong data #3");
7844 is(events[3].type, "input",
7845 "runNestedSettingValue: the 4th event must be input #3");
7846 is(events[3].target, textarea,
7847 `runNestedSettingValue: The ${events[3].type} event was fired on wrong event target #3`);
7848 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7849 "runNestedSettingValue #3");
7850 ok(!getEditor(textarea).isComposing,
7851 "runNestedSettingValue: the textarea still has composition #3");
7852 is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
7853 "runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3");
7855 input.focus();
7856 input.value = "";
7858 synthesizeCompositionChange(
7859 { "composition":
7860 { "string": "\u306E",
7861 "clauses":
7863 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7866 "caret": { "start": 1, "length": 0 }
7869 events = [];
7870 isTesting = true;
7871 input.setRangeText("first setting value, ");
7872 isTesting = false;
7874 is(events.length, 4,
7875 "runNestedSettingValue: wrong event count #4");
7876 is(events[0].type, "text",
7877 "runNestedSettingValue: the 1st event must be text #4");
7878 is(events[0].target, input,
7879 `runNestedSettingValue: The "${events[0].type}" event was fired on wrong event target #4`);
7880 is(events[1].type, "beforeinput",
7881 "runNestedSettingValue: the 2nd event must be beforeinput #4");
7882 is(events[1].target, input,
7883 `runNestedSettingValue: The "${events[1].type}" event was fired on wrong event target #4`);
7884 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
7885 "runNestedSettingValue #4");
7886 is(events[2].type, "compositionend",
7887 "runNestedSettingValue: the 3rd event must be compositionend #4");
7888 is(events[2].target, input,
7889 `runNestedSettingValue: The "${events[2].type}" event was fired on wrong event target #4`);
7890 is(events[2].data, "\u306E",
7891 "runNestedSettingValue: compositionend has wrong data #4");
7892 is(events[3].type, "input",
7893 "runNestedSettingValue: the 4th event must be input #4");
7894 is(events[3].target, input,
7895 `runNestedSettingValue: The "${events[3].type}" event was fired on wrong event target #4`);
7896 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
7897 "runNestedSettingValue #4");
7898 ok(!getEditor(input).isComposing,
7899 "runNestedSettingValue: the input still has composition #4");
7900 is(textarea.value, "\u306Efirst setting value, text, beforeinput, compositionend, input, ",
7901 "runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4");
7903 window.removeEventListener("compositionstart", eventHandler, true);
7904 window.removeEventListener("compositionupdate", eventHandler, true);
7905 window.removeEventListener("compositionend", eventHandler, true);
7906 window.removeEventListener("beforeinput", eventHandler, true);
7907 window.removeEventListener("input", eventHandler, true);
7908 window.removeEventListener("text", eventHandler, true);
7912 async function runAsyncForceCommitTest()
7914 let events;
7915 function eventHandler(aEvent)
7917 events.push(aEvent);
7920 // If IME commits composition for a request, TextComposition commits
7921 // composition automatically because most web apps must expect that active
7922 // composition should be committed synchronously. Therefore, in this case,
7923 // a click during composition should cause committing composition
7924 // synchronously and delayed commit shouldn't cause composition events.
7925 let commitRequested = false;
7926 let onFinishTest = null;
7927 function callback(aTIP, aNotification)
7929 ok(true, aNotification.type);
7930 if (aNotification.type != "request-to-commit") {
7931 return true;
7933 commitRequested = true;
7934 if (onFinishTest) {
7935 let resolve = onFinishTest;
7936 onFinishTest = null;
7938 SimpleTest.executeSoon(() => {
7939 events = [];
7940 aTIP.commitComposition();
7942 is(events.length, 0,
7943 "runAsyncForceCommitTest: composition events shouldn't been fired by asynchronous call of nsITextInputProcessor.commitComposition()");
7945 SimpleTest.executeSoon(resolve);
7948 return true;
7951 function promiseCleanUp() {
7952 return new Promise(resolve => { onFinishTest = resolve; });
7955 window.addEventListener("compositionstart", eventHandler, true);
7956 window.addEventListener("compositionupdate", eventHandler, true);
7957 window.addEventListener("compositionend", eventHandler, true);
7958 window.addEventListener("beforeinput", eventHandler, true);
7959 window.addEventListener("input", eventHandler, true);
7960 window.addEventListener("text", eventHandler, true);
7962 // Make the composition in textarea commit by click in the textarea
7963 textarea.focus();
7964 textarea.value = "";
7966 events = [];
7967 synthesizeCompositionChange(
7968 { "composition":
7969 { "string": "\u306E",
7970 "clauses":
7972 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
7975 "caret": { "start": 1, "length": 0 }
7976 }, window, callback);
7978 is(events.length, 5,
7979 "runAsyncForceCommitTest: wrong event count #1");
7980 is(events[0].type, "compositionstart",
7981 "runAsyncForceCommitTest: the 1st event must be compositionstart #1");
7982 is(events[1].type, "compositionupdate",
7983 "runAsyncForceCommitTest: the 2nd event must be compositionupdate #1");
7984 is(events[2].type, "text",
7985 "runAsyncForceCommitTest: the 3rd event must be text #1");
7986 is(events[3].type, "beforeinput",
7987 "runAsyncForceCommitTest: the 4th event must be beforeinput #1");
7988 checkInputEvent(events[3], true, "insertCompositionText", "\u306E", [],
7989 "runAsyncForceCommitTest #1");
7990 is(events[4].type, "input",
7991 "runAsyncForceCommitTest: the 5th event must be input #1");
7992 checkInputEvent(events[4], true, "insertCompositionText", "\u306E", [],
7993 "runAsyncForceCommitTest #1");
7995 events = [];
7996 let waitCleanState = promiseCleanUp();
7998 synthesizeMouseAtCenter(textarea, {});
8000 ok(commitRequested,
8001 "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
8002 is(events.length, 4,
8003 "runAsyncForceCommitTest: wrong event count #2");
8004 is(events[0].type, "text",
8005 "runAsyncForceCommitTest: the 1st event must be text #2");
8006 is(events[0].target, textarea,
8007 `runAsyncForceCommitTest: The "${events[0].type}" event was fired on wrong event target #2`);
8008 is(events[1].type, "beforeinput",
8009 "runAsyncForceCommitTest: the 2nd event must be beforeinput #2");
8010 is(events[1].target, textarea,
8011 `runAsyncForceCommitTest: The "${events[1].type}" event was fired on wrong event target #2`);
8012 checkInputEvent(events[1], true, "insertCompositionText", "\u306E", [],
8013 "runAsyncForceCommitTest #2");
8014 is(events[2].type, "compositionend",
8015 "runAsyncForceCommitTest: the 3rd event must be compositionend #2");
8016 is(events[2].target, textarea,
8017 `runAsyncForceCommitTest: The "${events[2].type}" event was fired on wrong event target #2`);
8018 is(events[2].data, "\u306E",
8019 "runAsyncForceCommitTest: compositionend has wrong data #2");
8020 is(events[3].type, "input",
8021 "runAsyncForceCommitTest: the 4th event must be input #2");
8022 is(events[3].target, textarea,
8023 `runAsyncForceCommitTest: The "${events[3].type}" event was fired on wrong event target #2`);
8024 checkInputEvent(events[3], false, "insertCompositionText", "\u306E", [],
8025 "runAsyncForceCommitTest #2");
8026 ok(!getEditor(textarea).isComposing,
8027 "runAsyncForceCommitTest: the textarea still has composition #2");
8028 is(textarea.value, "\u306E",
8029 "runAsyncForceCommitTest: the textarea doesn't have the committed text #2");
8031 await waitCleanState;
8033 window.removeEventListener("compositionstart", eventHandler, true);
8034 window.removeEventListener("compositionupdate", eventHandler, true);
8035 window.removeEventListener("compositionend", eventHandler, true);
8036 window.removeEventListener("beforeinput", eventHandler, true);
8037 window.removeEventListener("input", eventHandler, true);
8038 window.removeEventListener("text", eventHandler, true);
8041 function runBug811755Test()
8043 iframe2.contentDocument.body.innerHTML = "<div>content<br/></div>";
8044 iframe2.contentWindow.focus();
8045 // Query everything
8046 let textContent = synthesizeQueryTextContent(0, 10);
8047 if (!checkQueryContentResult(textContent, "runBug811755Test: synthesizeQueryTextContent #1")) {
8048 return;
8050 // Query everything but specify exact end offset, which should be immediately after the <br> node
8051 // If PreContentIterator is used, the next node after <br> is the node after </div>.
8052 // If ContentIterator is used, the next node is the <div> node itself. In this case, the end
8053 // node ends up being before the start node, and an empty string is returned.
8054 let queryContent = synthesizeQueryTextContent(0, textContent.text.length);
8055 if (!checkQueryContentResult(queryContent, "runBug811755Test: synthesizeQueryTextContent #2")) {
8056 return;
8058 is(queryContent.text, textContent.text, "runBug811755Test: two queried texts don't match");
8061 function runIsComposingTest()
8063 let expectedIsComposing = false;
8064 let description = "";
8066 function eventHandler(aEvent)
8068 if (aEvent.type == "keydown" || aEvent.type == "keyup") {
8069 is(aEvent.isComposing, expectedIsComposing,
8070 "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
8071 } else if (aEvent.type == "keypress") {
8072 // keypress event shouldn't be fired during composition so that isComposing should be always false.
8073 is(aEvent.isComposing, false,
8074 "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
8075 } else {
8076 checkInputEvent(aEvent, expectedIsComposing, "insertCompositionText", "\u3042", [],
8077 `runIsComposingTest: ${description}`);
8081 function onComposition(aEvent)
8083 if (aEvent.type == "compositionstart") {
8084 expectedIsComposing = true;
8085 } else if (aEvent.type == "compositionend") {
8086 expectedIsComposing = false;
8090 textarea.addEventListener("keydown", eventHandler, true);
8091 textarea.addEventListener("keypress", eventHandler, true);
8092 textarea.addEventListener("keyup", eventHandler, true);
8093 textarea.addEventListener("beforeinput", eventHandler, true);
8094 textarea.addEventListener("input", eventHandler, true);
8095 textarea.addEventListener("compositionstart", onComposition, true);
8096 textarea.addEventListener("compositionend", onComposition, true);
8098 textarea.focus();
8099 textarea.value = "";
8101 // XXX These cases shouldn't occur in actual native key events because we
8102 // don't dispatch key events while composition (bug 354358).
8103 description = "events before dispatching compositionstart";
8104 synthesizeKey("KEY_ArrowLeft");
8106 description = "events after dispatching compositionchange";
8107 synthesizeCompositionChange(
8108 { "composition":
8109 { "string": "\u3042",
8110 "clauses":
8112 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8115 "caret": { "start": 1, "length": 0 },
8116 "key": { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
8119 // Although, firing keypress event during composition is a bug.
8120 synthesizeKey("KEY_Insert");
8122 description = "events for committing composition string";
8124 synthesizeComposition({ type: "compositioncommitasis",
8125 key: { key: "KEY_Enter", code: "Enter", type: "keydown" } });
8127 // input event will be fired by synthesizing compositionend event.
8128 // Then, its isComposing should be false.
8129 description = "events after dispatching compositioncommitasis";
8130 synthesizeKey("KEY_Enter", {type: "keyup"});
8132 textarea.removeEventListener("keydown", eventHandler, true);
8133 textarea.removeEventListener("keypress", eventHandler, true);
8134 textarea.removeEventListener("keyup", eventHandler, true);
8135 textarea.removeEventListener("beforeinput", eventHandler, true);
8136 textarea.removeEventListener("input", eventHandler, true);
8137 textarea.removeEventListener("compositionstart", onComposition, true);
8138 textarea.removeEventListener("compositionend", onComposition, true);
8140 textarea.value = "";
8143 function runRedundantChangeTest()
8145 textarea.focus();
8147 let result = [];
8148 function clearResult()
8150 result = [];
8153 function handler(aEvent)
8155 result.push(aEvent);
8158 textarea.addEventListener("compositionupdate", handler, true);
8159 textarea.addEventListener("compositionend", handler, true);
8160 textarea.addEventListener("beforeinput", handler, true);
8161 textarea.addEventListener("input", handler, true);
8162 textarea.addEventListener("text", handler, true);
8164 textarea.value = "";
8166 // synthesize change event
8167 clearResult();
8168 synthesizeCompositionChange(
8169 { "composition":
8170 { "string": "\u3042",
8171 "clauses":
8173 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8176 "caret": { "start": 1, "length": 0 }
8179 is(result.length, 4,
8180 "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #1");
8181 is(result[0].type, "compositionupdate",
8182 "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
8183 is(result[1].type, "text",
8184 "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
8185 is(result[2].type, "beforeinput",
8186 "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #1");
8187 checkInputEvent(result[2], true, "insertCompositionText", "\u3042", [],
8188 "runRedundantChangeTest: after synthesizing composition change #1");
8189 is(result[3].type, "input",
8190 "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
8191 checkInputEvent(result[3], true, "insertCompositionText", "\u3042", [],
8192 "runRedundantChangeTest: after synthesizing composition change #1");
8193 is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
8195 // synthesize another change event
8196 clearResult();
8197 synthesizeCompositionChange(
8198 { "composition":
8199 { "string": "\u3042\u3044",
8200 "clauses":
8202 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8205 "caret": { "start": 2, "length": 0 }
8208 is(result.length, 4,
8209 "runRedundantChangeTest: 4 events should be fired after synthesizing composition change #2");
8210 is(result[0].type, "compositionupdate",
8211 "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
8212 is(result[1].type, "text",
8213 "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
8214 is(result[2].type, "beforeinput",
8215 "runRedundantChangeTest: beforeinput should be fired after synthesizing composition change #2");
8216 checkInputEvent(result[2], true, "insertCompositionText", "\u3042\u3044", [],
8217 "runRedundantChangeTest: after synthesizing composition change #2");
8218 is(result[3].type, "input",
8219 "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
8220 checkInputEvent(result[3], true, "insertCompositionText", "\u3042\u3044", [],
8221 "runRedundantChangeTest: after synthesizing composition change #2");
8222 is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
8224 // synthesize same change event again
8225 clearResult();
8226 synthesizeCompositionChange(
8227 { "composition":
8228 { "string": "\u3042\u3044",
8229 "clauses":
8231 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8234 "caret": { "start": 2, "length": 0 }
8237 is(result.length, 0, "runRedundantChangeTest: no events should be fired after synthesizing composition change again");
8238 is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");
8240 // synthesize commit-as-is
8241 clearResult();
8242 synthesizeComposition({ type: "compositioncommitasis" });
8243 is(result.length, 4,
8244 "runRedundantChangeTest: 4 events should be fired after synthesizing composition commit-as-is");
8245 is(result[0].type, "text",
8246 "runRedundantChangeTest: text should be fired after synthesizing composition commit-as-is for removing the ranges");
8247 is(result[1].type, "beforeinput",
8248 "runRedundantChangeTest: beforeinput should be fired after synthesizing composition commit-as-is for removing the ranges");
8249 checkInputEvent(result[1], true, "insertCompositionText", "\u3042\u3044", [],
8250 "runRedundantChangeTest: at synthesizing commit-as-is");
8251 is(result[2].type, "compositionend",
8252 "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
8253 is(result[3].type, "input",
8254 "runRedundantChangeTest: input should be fired before compositionend at synthesizing commit-as-is");
8255 checkInputEvent(result[3], false, "insertCompositionText", "\u3042\u3044", [],
8256 "runRedundantChangeTest: at synthesizing commit-as-is");
8257 is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");
8259 textarea.removeEventListener("compositionupdate", handler, true);
8260 textarea.removeEventListener("compositionend", handler, true);
8261 textarea.removeEventListener("beforeinput", handler, true);
8262 textarea.removeEventListener("input", handler, true);
8263 textarea.removeEventListener("text", handler, true);
8266 function runNotRedundantChangeTest()
8268 textarea.focus();
8270 let result = [];
8271 function clearResult()
8273 result = [];
8276 function handler(aEvent)
8278 result.push(aEvent);
8281 textarea.addEventListener("compositionupdate", handler, true);
8282 textarea.addEventListener("compositionend", handler, true);
8283 textarea.addEventListener("beforeinput", handler, true);
8284 textarea.addEventListener("input", handler, true);
8285 textarea.addEventListener("text", handler, true);
8287 textarea.value = "abcde";
8289 // synthesize change event with non-null ranges
8290 clearResult();
8291 synthesizeCompositionChange(
8292 { "composition":
8293 { "string": "ABCDE",
8294 "clauses":
8296 { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8299 "caret": { "start": 5, "length": 0 }
8302 is(result.length, 4,
8303 "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges");
8304 is(result[0].type, "compositionupdate",
8305 "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
8306 is(result[1].type, "text",
8307 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
8308 is(result[2].type, "beforeinput",
8309 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges");
8310 checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
8311 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
8312 is(result[3].type, "input",
8313 "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
8314 checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
8315 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
8316 is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");
8318 // synthesize change event with null ranges
8319 clearResult();
8320 synthesizeCompositionChange(
8321 { "composition":
8322 { "string": "ABCDE",
8323 "clauses":
8325 { "length": 0, "attr": 0 }
8329 is(result.length, 3,
8330 "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
8331 is(result[0].type, "text",
8332 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
8333 is(result[1].type, "beforeinput",
8334 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with null ranges after non-null ranges");
8335 checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
8336 "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
8337 is(result[2].type, "input",
8338 "runNotRedundantChangeTest: input should be fired after synthesizing composition change with null ranges after non-null ranges");
8339 checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
8340 "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
8341 is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");
8343 // synthesize change event with non-null ranges
8344 clearResult();
8345 synthesizeCompositionChange(
8346 { "composition":
8347 { "string": "ABCDE",
8348 "clauses":
8350 { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8353 "caret": { "start": 5, "length": 0 }
8356 is(result.length, 3,
8357 "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with null ranges after non-null ranges");
8358 is(result[0].type, "text",
8359 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
8360 is(result[1].type, "beforeinput",
8361 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after null ranges");
8362 checkInputEvent(result[1], true, "insertCompositionText", "ABCDE", [],
8363 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
8364 is(result[2].type, "input",
8365 "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
8366 checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
8367 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
8368 is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #3");
8370 // synthesize change event with empty data and null ranges
8371 clearResult();
8372 synthesizeCompositionChange(
8373 { "composition":
8374 { "string": "",
8375 "clauses":
8377 { "length": 0, "attr": 0 }
8381 is(result.length, 4,
8382 "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
8383 is(result[0].type, "compositionupdate",
8384 "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
8385 is(result[1].type, "text",
8386 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data and null ranges after non-null ranges");
8387 is(result[2].type, "beforeinput",
8388 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
8389 checkInputEvent(result[2], true, "insertCompositionText", "", [],
8390 "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
8391 is(result[3].type, "input",
8392 "runNotRedundantChangeTest: input should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
8393 checkInputEvent(result[3], true, "insertCompositionText", "", [],
8394 "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
8395 is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #1");
8397 // synthesize change event with non-null ranges
8398 clearResult();
8399 synthesizeCompositionChange(
8400 { "composition":
8401 { "string": "ABCDE",
8402 "clauses":
8404 { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8407 "caret": { "start": 5, "length": 0 }
8410 is(result.length, 4,
8411 "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
8412 is(result[0].type, "compositionupdate",
8413 "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
8414 is(result[1].type, "text",
8415 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges after empty data and null ranges");
8416 is(result[2].type, "beforeinput",
8417 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
8418 checkInputEvent(result[2], true, "insertCompositionText", "ABCDE", [],
8419 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
8420 is(result[3].type, "input",
8421 "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
8422 checkInputEvent(result[3], true, "insertCompositionText", "ABCDE", [],
8423 "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
8424 is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #4");
8426 clearResult();
8427 synthesizeComposition({ type: "compositioncommit", data: "" });
8429 is(result.length, 5,
8430 "runNotRedundantChangeTest: 5 events should be fired after synthesizing composition commit with empty data after non-empty data");
8431 is(result[0].type, "compositionupdate",
8432 "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
8433 is(result[1].type, "text",
8434 "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with empty data after non-empty data");
8435 is(result[2].type, "beforeinput",
8436 "runNotRedundantChangeTest: beforeinput should be fired after synthesizing composition commit with empty data after non-empty data");
8437 checkInputEvent(result[2], true, "insertCompositionText", "", [],
8438 "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
8439 is(result[3].type, "compositionend",
8440 "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
8441 is(result[4].type, "input",
8442 "runNotRedundantChangeTest: input should be fired after compositionend after synthesizing composition change with empty data after non-empty data");
8443 checkInputEvent(result[4], false, "insertCompositionText", "", [],
8444 "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
8445 is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #2");
8447 textarea.removeEventListener("compositionupdate", handler, true);
8448 textarea.removeEventListener("compositionend", handler, true);
8449 textarea.removeEventListener("beforeinput", handler, true);
8450 textarea.removeEventListener("input", handler, true);
8451 textarea.removeEventListener("text", handler, true);
8454 function runNativeLineBreakerTest()
8456 textarea.focus();
8458 let result = {};
8459 function clearResult()
8461 result = { compositionupdate: null, compositionend: null };
8464 function handler(aEvent)
8466 result[aEvent.type] = aEvent.data;
8469 Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", false);
8471 textarea.addEventListener("compositionupdate", handler, true);
8472 textarea.addEventListener("compositionend", handler, true);
8474 // '\n' in composition string shouldn't be changed.
8475 clearResult();
8476 textarea.value = "";
8477 let clauses = [ "abc\n", "def\n\ng", "hi\n", "\njkl" ];
8478 let caret = clauses[0] + clauses[1] + clauses[2];
8479 synthesizeCompositionChange(
8480 { "composition":
8481 { "string": clauses.join(''),
8482 "clauses":
8484 { "length": clauses[0].length,
8485 "attr": COMPOSITION_ATTR_RAW_CLAUSE },
8486 { "length": clauses[1].length,
8487 "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
8488 { "length": clauses[2].length,
8489 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
8490 { "length": clauses[3].length,
8491 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
8494 "caret": { "start": caret.length, "length": 0 }
8497 checkSelection(caret.replace(/\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#1");
8498 checkIMESelection("RawClause", true, 0, clauses[0].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
8499 checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\n/g, kLF).length, clauses[1].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
8500 checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\n/g, kLF).length, clauses[2].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
8501 checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\n/g, kLF).length, clauses[3].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
8502 is(result.compositionupdate, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionupdate.data shouldn't be removed nor replaced with other characters #1");
8503 is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #1");
8505 synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
8506 checkSelection(clauses.join('').replace(/\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#2");
8507 is(result.compositionend, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionend.data shouldn't be removed nor replaced with other characters #2");
8508 is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #2");
8510 // \r\n in composition string should be replaced with \n.
8511 clearResult();
8512 textarea.value = "";
8513 clauses = [ "abc\r\n", "def\r\n\r\ng", "hi\r\n", "\r\njkl" ];
8514 caret = clauses[0] + clauses[1] + clauses[2];
8515 synthesizeCompositionChange(
8516 { "composition":
8517 { "string": clauses.join(''),
8518 "clauses":
8520 { "length": clauses[0].length,
8521 "attr": COMPOSITION_ATTR_RAW_CLAUSE },
8522 { "length": clauses[1].length,
8523 "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
8524 { "length": clauses[2].length,
8525 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
8526 { "length": clauses[3].length,
8527 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
8530 "caret": { "start": caret.length, "length": 0 }
8533 checkSelection(caret.replace(/\r\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#3");
8534 checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
8535 checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r\n/g, kLF).length, clauses[1].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
8536 checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r\n/g, kLF).length, clauses[2].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
8537 checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r\n/g, kLF).length, clauses[3].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
8538 is(result.compositionupdate, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionudpate.data should be replaced with \\n #3");
8539 is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #3");
8541 synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
8542 checkSelection(clauses.join('').replace(/\r\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#4");
8543 is(result.compositionend, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionend.data should be replaced with \\n #4");
8544 is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #4");
8546 // \r (not followed by \n) in composition string should be replaced with \n.
8547 clearResult();
8548 textarea.value = "";
8549 clauses = [ "abc\r", "def\r\rg", "hi\r", "\rjkl" ];
8550 caret = clauses[0] + clauses[1] + clauses[2];
8551 synthesizeCompositionChange(
8552 { "composition":
8553 { "string": clauses.join(''),
8554 "clauses":
8556 { "length": clauses[0].length,
8557 "attr": COMPOSITION_ATTR_RAW_CLAUSE },
8558 { "length": clauses[1].length,
8559 "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
8560 { "length": clauses[2].length,
8561 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
8562 { "length": clauses[3].length,
8563 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
8566 "caret": { "start": caret.length, "length": 0 }
8569 checkSelection(caret.replace(/\r/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#5");
8570 checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
8571 checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r/g, kLF).length, clauses[1].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
8572 checkIMESelection("ConvertedClause", true, (clauses[0] + clauses[1]).replace(/\r/g, kLF).length, clauses[2].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
8573 checkIMESelection("SelectedClause", true, (clauses[0] + clauses[1] + clauses[2]).replace(/\r/g, kLF).length, clauses[3].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
8574 is(result.compositionupdate, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionupdate.data should be replaced with \\n #5");
8575 is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #5");
8577 synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
8578 checkSelection(clauses.join('').replace(/\r/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#6");
8579 is(result.compositionend, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionend.data should be replaced with \\n #6");
8580 is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #6");
8582 textarea.removeEventListener("compositionupdate", handler, true);
8583 textarea.removeEventListener("compositionend", handler, true);
8585 Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
8588 function runControlCharTest()
8590 textarea.focus();
8592 let result = {};
8593 function clearResult()
8595 result = { compositionupdate: null, compositionend: null };
8598 function handler(aEvent)
8600 result[aEvent.type] = aEvent.data;
8603 textarea.addEventListener("compositionupdate", handler, true);
8604 textarea.addEventListener("compositionend", handler, true);
8606 textarea.value = "";
8608 let controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
8609 let allowedChars = "\t\n\n";
8611 let data = "AB" + controlChars + "CD" + controlChars + "EF";
8612 let removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";
8614 let DIndex = data.indexOf("D");
8615 let removedDIndex = removedData.indexOf("D");
8617 // input string contains control characters
8618 clearResult();
8619 synthesizeCompositionChange(
8620 { "composition":
8621 { "string": data,
8622 "clauses":
8624 { "length": DIndex,
8625 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
8626 { "length": data.length - DIndex,
8627 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
8630 "caret": { "start": DIndex, "length": 0 }
8633 checkSelection(removedDIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#1")
8635 is(result.compositionupdate, removedData, "runControlCharTest: control characters in event.data should be removed in compositionupdate event #1");
8636 is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #1");
8638 synthesizeComposition({ type: "compositioncommit", data });
8640 is(result.compositionend, removedData, "runControlCharTest: control characters in event.data should be removed in compositionend event #2");
8641 is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #2");
8643 textarea.value = "";
8645 clearResult();
8647 Services.prefs.setBoolPref("dom.compositionevent.allow_control_characters", true);
8649 // input string contains control characters, allowing control characters
8650 clearResult();
8651 synthesizeCompositionChange(
8652 { "composition":
8653 { "string": data,
8654 "clauses":
8656 { "length": DIndex,
8657 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
8658 { "length": data.length - DIndex,
8659 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
8662 "caret": { "start": DIndex, "length": 0 }
8665 checkSelection(DIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#3")
8667 is(result.compositionupdate, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionupdate event #3");
8668 is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #3");
8670 synthesizeComposition({ type: "compositioncommit", data });
8672 is(result.compositionend, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionend event #4");
8673 is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #4");
8675 Services.prefs.clearUserPref("dom.compositionevent.allow_control_characters");
8677 textarea.removeEventListener("compositionupdate", handler, true);
8678 textarea.removeEventListener("compositionend", handler, true);
8681 async function runRemoveContentTest()
8683 let events = [];
8684 function eventHandler(aEvent)
8686 events.push(aEvent);
8688 textarea.addEventListener("compositionstart", eventHandler, true);
8689 textarea.addEventListener("compositionupdate", eventHandler, true);
8690 textarea.addEventListener("compositionend", eventHandler, true);
8691 textarea.addEventListener("beforeinput", eventHandler, true);
8692 textarea.addEventListener("input", eventHandler, true);
8693 textarea.addEventListener("text", eventHandler, true);
8695 textarea.focus();
8696 textarea.value = "";
8698 synthesizeCompositionChange(
8699 { "composition":
8700 { "string": "\u306E",
8701 "clauses":
8703 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8706 "caret": { "start": 1, "length": 0 }
8709 let nextSibling = textarea.nextSibling;
8710 let parent = textarea.parentElement;
8712 events = [];
8713 parent.removeChild(textarea);
8715 await waitForEventLoops(50);
8717 // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
8718 is(events.length, 3,
8719 "runRemoveContentTest: wrong event count #1");
8720 is(events[0].type, "compositionupdate",
8721 "runRemoveContentTest: the 1st event must be compositionupdate #1");
8722 is(events[0].target, textarea,
8723 `runRemoveContentTest: The "${events[0].type}" event was fired on wrong event target #1`);
8724 is(events[0].data, "",
8725 "runRemoveContentTest: compositionupdate has wrong data #1");
8726 is(events[1].type, "text",
8727 "runRemoveContentTest: the 2nd event must be text #1");
8728 is(events[1].target, textarea,
8729 `runRemoveContentTest: The "${events[1].type}" event was fired on wrong event target #1`);
8730 todo_is(events[2].type, "beforeinput",
8731 "runRemoveContentTest: the 3rd event must be beforeinput #1");
8732 // is(events[2].target, textarea,
8733 // `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
8734 // checkInputEvent(events[2], true, "insertCompositionText", "", [],
8735 // "runRemoveContentTest: #1");
8736 is(events[2].type, "compositionend",
8737 "runRemoveContentTest: the 4th event must be compositionend #1");
8738 is(events[2].target, textarea,
8739 `runRemoveContentTest: The "${events[2].type}" event was fired on wrong event target #1`);
8740 is(events[2].data, "",
8741 "runRemoveContentTest: compositionend has wrong data #1");
8742 ok(!getEditor(textarea).isComposing,
8743 "runRemoveContentTest: the textarea still has composition #1");
8744 todo_is(textarea.value, "",
8745 "runRemoveContentTest: the textarea has the committed text? #1");
8747 parent.insertBefore(textarea, nextSibling);
8749 textarea.focus();
8750 textarea.value = "";
8752 synthesizeComposition({ type: "compositionstart" });
8754 events = [];
8755 parent.removeChild(textarea);
8757 await waitForEventLoops(50);
8759 // XXX Currently, "input" event and "beforeinput" event are not fired on removed content.
8760 is(events.length, 2,
8761 "runRemoveContentTest: wrong event count #2");
8762 is(events[0].type, "text",
8763 "runRemoveContentTest: the 1st event must be text #2");
8764 is(events[0].target, textarea,
8765 `runRemoveContentTest: The ${events[0].type} event was fired on wrong event target #2`);
8766 todo_is(events[1].type, "beforeinput",
8767 "runRemoveContentTest: the 2nd event must be beforeinput #2");
8768 // is(events[1].target, textarea,
8769 // `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
8770 // checkInputEvent(events[1], true, "insertCompositionText", "", [],
8771 // "runRemoveContentTest: #2");
8772 is(events[1].type, "compositionend",
8773 "runRemoveContentTest: the 3rd event must be compositionend #2");
8774 is(events[1].target, textarea,
8775 `runRemoveContentTest: The ${events[1].type} event was fired on wrong event target #2`);
8776 is(events[1].data, "",
8777 "runRemoveContentTest: compositionupdate has wrong data #2");
8778 ok(!getEditor(textarea).isComposing,
8779 "runRemoveContentTest: the textarea still has composition #2");
8780 is(textarea.value, "",
8781 "runRemoveContentTest: the textarea has the committed text? #2");
8783 parent.insertBefore(textarea, nextSibling);
8785 textarea.removeEventListener("compositionstart", eventHandler, true);
8786 textarea.removeEventListener("compositionupdate", eventHandler, true);
8787 textarea.removeEventListener("compositionend", eventHandler, true);
8788 textarea.removeEventListener("beforeinput", eventHandler, true);
8789 textarea.removeEventListener("input", eventHandler, true);
8790 textarea.removeEventListener("text", eventHandler, true);
8792 await waitForTick();
8795 function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
8797 aFocusedEditor.value = "";
8799 // The frames and panel are cross-origin, and we no longer
8800 // propagate flushes to parent cross-origin iframes explicitly,
8801 // so flush our own layout here so the positions are correct.
8802 document.documentElement.getBoundingClientRect();
8804 let editorRect = synthesizeQueryEditorRect();
8805 if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
8806 return;
8809 let r = aPanelOrFrame.getBoundingClientRect();
8810 let parentRect = {
8811 left: r.left * window.devicePixelRatio,
8812 top: r.top * window.devicePixelRatio,
8813 width: (r.right - r.left) * window.devicePixelRatio,
8814 height: (r.bottom - r.top) * window.devicePixelRatio,
8816 checkRectContainsRect(editorRect, parentRect, aTestName +
8817 ": the editor rect coordinates are wrong");
8819 // input characters
8820 synthesizeCompositionChange(
8821 { "composition":
8822 { "string": "\u3078\u3093\u3057\u3093",
8823 "clauses":
8825 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8828 "caret": { "start": 4, "length": 0 }
8831 if (!checkContent("\u3078\u3093\u3057\u3093", aTestName, "#1-1") ||
8832 !checkSelection(4, "", aTestName, "#1-1")) {
8833 return;
8836 // convert them #1
8837 synthesizeCompositionChange(
8838 { "composition":
8839 { "string": "\u8FD4\u4FE1",
8840 "clauses":
8842 { "length": 2,
8843 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8846 "caret": { "start": 2, "length": 0 }
8849 if (!checkContent("\u8FD4\u4FE1", aTestName, "#1-2") ||
8850 !checkSelection(2, "", aTestName, "#1-2")) {
8851 return;
8854 // convert them #2
8855 synthesizeCompositionChange(
8856 { "composition":
8857 { "string": "\u5909\u8EAB",
8858 "clauses":
8860 { "length": 2,
8861 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
8864 "caret": { "start": 2, "length": 0 }
8867 if (!checkContent("\u5909\u8EAB", aTestName, "#1-3") ||
8868 !checkSelection(2, "", aTestName, "#1-3")) {
8869 return;
8872 // commit them
8873 synthesizeComposition({ type: "compositioncommitasis" });
8874 if (!checkContent("\u5909\u8EAB", aTestName, "#1-4") ||
8875 !checkSelection(2, "", aTestName, "#1-4")) {
8876 return;
8879 is(aFocusedEditor.value, "\u5909\u8EAB",
8880 aTestName + ": composition isn't in the focused editor");
8881 if (aFocusedEditor.value != "\u5909\u8EAB") {
8882 return;
8885 let textRect = synthesizeQueryTextRect(0, 1);
8886 let caretRect = synthesizeQueryCaretRect(2);
8887 if (!checkQueryContentResult(textRect,
8888 aTestName + ": synthesizeQueryTextRect") ||
8889 !checkQueryContentResult(caretRect,
8890 aTestName + ": synthesizeQueryCaretRect")) {
8891 return;
8893 checkRectContainsRect(textRect, editorRect, aTestName + ":testRect");
8894 checkRectContainsRect(caretRect, editorRect, aTestName + ":caretRect");
8897 function runFrameTest()
8899 textareaInFrame.focus();
8900 runTestOnAnotherContext(iframe, textareaInFrame, "runFrameTest");
8901 runCharAtPointTest(textareaInFrame, "textarea in the iframe");
8904 async function runPanelTest()
8906 panel.hidden = false;
8907 let waitOpenPopup = new Promise(resolve => {
8908 panel.addEventListener("popupshown", resolve, {once: true});
8910 let waitFocusTextBox = new Promise(resolve => {
8911 textbox.addEventListener("focus", resolve, {once: true});
8913 panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
8914 await waitOpenPopup;
8915 textbox.focus();
8916 await waitFocusTextBox;
8917 is(panel.state, "open", "The panel should be open (after textbox.focus())");
8918 await waitForTick();
8919 is(panel.state, "open", "The panel should be open (after waitForTick())");
8920 runTestOnAnotherContext(panel, textbox, "runPanelTest");
8921 is(panel.state, "open", "The panel should be open (after runTestOnAnotherContext())");
8922 runCharAtPointTest(textbox, "textbox in the panel");
8923 is(panel.state, "open", "The panel should be open (after runCharAtPointTest())");
8924 let waitClosePopup = new Promise(resolve => {
8925 panel.addEventListener("popuphidden", resolve, {once: true});
8927 panel.hidePopup();
8928 await waitClosePopup;
8929 await waitForTick();
8932 // eslint-disable-next-line complexity
8933 function runMaxLengthTest()
8935 input.maxLength = 1;
8936 input.value = "";
8937 input.focus();
8939 let kDesc ="runMaxLengthTest";
8941 // input first character
8942 synthesizeCompositionChange(
8943 { "composition":
8944 { "string": "\u3089",
8945 "clauses":
8947 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8950 "caret": { "start": 1, "length": 0 }
8953 if (!checkContent("\u3089", kDesc, "#1-1") ||
8954 !checkSelection(1, "", kDesc, "#1-1")) {
8955 return;
8958 // input second character
8959 synthesizeCompositionChange(
8960 { "composition":
8961 { "string": "\u3089\u30FC",
8962 "clauses":
8964 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8967 "caret": { "start": 2, "length": 0 }
8970 if (!checkContent("\u3089\u30FC", kDesc, "#1-2") ||
8971 !checkSelection(2, "", kDesc, "#1-2")) {
8972 return;
8975 // input third character
8976 synthesizeCompositionChange(
8977 { "composition":
8978 { "string": "\u3089\u30FC\u3081",
8979 "clauses":
8981 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
8984 "caret": { "start": 3, "length": 0 }
8987 if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-3") ||
8988 !checkSelection(3, "", kDesc, "#1-3")) {
8989 return;
8992 // input fourth character
8993 synthesizeCompositionChange(
8994 { "composition":
8995 { "string": "\u3089\u30FC\u3081\u3093",
8996 "clauses":
8998 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9001 "caret": { "start": 4, "length": 0 }
9004 if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-4") ||
9005 !checkSelection(4, "", kDesc, "#1-4")) {
9006 return;
9010 // backspace
9011 synthesizeCompositionChange(
9012 { "composition":
9013 { "string": "\u3089\u30FC\u3081",
9014 "clauses":
9016 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9019 "caret": { "start": 3, "length": 0 }
9022 if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-5") ||
9023 !checkSelection(3, "", kDesc, "#1-5")) {
9024 return;
9027 // re-input
9028 synthesizeCompositionChange(
9029 { "composition":
9030 { "string": "\u3089\u30FC\u3081\u3093",
9031 "clauses":
9033 { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9036 "caret": { "start": 4, "length": 0 }
9039 if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-6") ||
9040 !checkSelection(4, "", kDesc, "#1-6")) {
9041 return;
9044 synthesizeCompositionChange(
9045 { "composition":
9046 { "string": "\u3089\u30FC\u3081\u3093\u3055",
9047 "clauses":
9049 { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9052 "caret": { "start": 5, "length": 0 }
9055 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", kDesc, "#1-7") ||
9056 !checkSelection(5, "", kDesc, "#1-7")) {
9057 return;
9060 synthesizeCompositionChange(
9061 { "composition":
9062 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
9063 "clauses":
9065 { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9068 "caret": { "start": 6, "length": 0 }
9071 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", kDesc, "#1-8") ||
9072 !checkSelection(6, "", kDesc, "#1-8")) {
9073 return;
9076 synthesizeCompositionChange(
9077 { "composition":
9078 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
9079 "clauses":
9081 { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9084 "caret": { "start": 7, "length": 0 }
9087 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
9088 kDesc, "#1-8") ||
9089 !checkSelection(7, "", kDesc, "#1-8")) {
9090 return;
9093 synthesizeCompositionChange(
9094 { "composition":
9095 { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
9096 "clauses":
9098 { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9101 "caret": { "start": 8, "length": 0 }
9104 if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
9105 kDesc, "#1-9") ||
9106 !checkSelection(8, "", kDesc, "#1-9")) {
9107 return;
9110 // convert
9111 synthesizeCompositionChange(
9112 { "composition":
9113 { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
9114 "clauses":
9116 { "length": 4,
9117 "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
9118 { "length": 2,
9119 "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
9122 "caret": { "start": 4, "length": 0 }
9125 if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8", kDesc, "#1-10") ||
9126 !checkSelection(4, "", kDesc, "#1-10")) {
9127 return;
9130 // commit the composition string
9131 synthesizeComposition({ type: "compositioncommitasis" });
9132 if (!checkContent("\u30E9", kDesc, "#1-11") ||
9133 !checkSelection(1, "", kDesc, "#1-11")) {
9134 return;
9137 // input characters
9138 synthesizeCompositionChange(
9139 { "composition":
9140 { "string": "\u3057",
9141 "clauses":
9143 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9146 "caret": { "start": 1, "length": 0 }
9149 if (!checkContent("\u30E9\u3057", kDesc, "#2-1") ||
9150 !checkSelection(1 + 1, "", kDesc, "#2-1")) {
9151 return;
9154 // commit the composition string
9155 synthesizeComposition({ type: "compositioncommit", data: "\u3058" });
9156 if (!checkContent("\u30E9", kDesc, "#2-2") ||
9157 !checkSelection(1 + 0, "", kDesc, "#2-2")) {
9158 return;
9161 // Undo
9162 synthesizeKey("Z", {accelKey: true});
9164 // XXX this is unexpected behavior, see bug 258291
9165 if (!checkContent("\u30E9", kDesc, "#3-1") ||
9166 !checkSelection(1 + 0, "", kDesc, "#3-1")) {
9167 return;
9170 // Undo
9171 synthesizeKey("Z", {accelKey: true});
9172 if (!checkContent("", kDesc, "#3-2") ||
9173 !checkSelection(0, "", kDesc, "#3-2")) {
9174 return;
9177 // Redo
9178 synthesizeKey("Z", {accelKey: true, shiftKey: true});
9179 if (!checkContent("\u30E9", kDesc, "#3-3") ||
9180 !checkSelection(1, "", kDesc, "#3-3")) {
9181 return;
9184 // Redo
9185 synthesizeKey("Z", {accelKey: true, shiftKey: true});
9186 if (!checkContent("\u30E9", kDesc, "#3-4") ||
9187 !checkSelection(1 + 0, "", kDesc, "#3-4")) {
9188 return;
9191 // The input element whose content length is already maxlength and
9192 // the carest is at start of the content.
9193 input.value = "X";
9194 input.selectionStart = input.selectionEnd = 0;
9196 // input characters
9197 synthesizeCompositionChange(
9198 { "composition":
9199 { "string": "\u9B54",
9200 "clauses":
9202 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9205 "caret": { "start": 1, "length": 0 }
9208 if (!checkContent("\u9B54X", kDesc, "#4-1") ||
9209 !checkSelection(1, "", kDesc, "#4-1")) {
9210 return;
9213 // commit the composition string
9214 synthesizeComposition({ type: "compositioncommitasis" });
9216 // The input text must be discarded. Then, the caret position shouldn't be
9217 // updated from its position at compositionstart.
9218 if (!checkContent("X", kDesc, "#4-2") ||
9219 !checkSelection(0, "", kDesc, "#4-2")) {
9220 return;
9223 // input characters
9224 synthesizeCompositionChange(
9225 { "composition":
9226 { "string": "\u9B54\u6CD5",
9227 "clauses":
9229 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9232 "caret": { "start": 2, "length": 0 }
9235 if (!checkContent("\u9B54\u6CD5X", kDesc, "#5-1") ||
9236 !checkSelection(2, "", kDesc, "#5-1")) {
9237 return;
9240 // commit the composition string
9241 synthesizeComposition({ type: "compositioncommitasis" });
9243 if (checkContent("X", kDesc, "#5-2")) {
9244 checkSelection(0, "", kDesc, "#5-2");
9248 async function runEditorReframeTests()
9250 async function runEditorReframeTest(aEditor, aWindow, aEventType)
9252 function getValue()
9254 return aEditor == contenteditable ?
9255 aEditor.innerHTML.replace("<br>", "") : aEditor.value;
9258 let description = "runEditorReframeTest(" + aEditor.id + ", \"" + aEventType + "\"): ";
9260 let tests = [
9261 { test () {
9262 synthesizeCompositionChange(
9263 { "composition":
9264 { "string": "a",
9265 "clauses":
9267 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9270 "caret": { "start": 1, "length": 0 }
9273 check () {
9274 is(getValue(aEditor), "a", description + "Typing 'a'");
9277 { test () {
9278 synthesizeCompositionChange(
9279 { "composition":
9280 { "string": "ab",
9281 "clauses":
9283 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9286 "caret": { "start": 2, "length": 0 }
9289 check () {
9290 is(getValue(aEditor), "ab", description + "Typing 'b' next to 'a'");
9293 { test () {
9294 synthesizeCompositionChange(
9295 { "composition":
9296 { "string": "abc",
9297 "clauses":
9299 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9302 "caret": { "start": 3, "length": 0 }
9305 check () {
9306 is(getValue(aEditor), "abc", description + "Typing 'c' next to 'ab'");
9309 { test () {
9310 synthesizeCompositionChange(
9311 { "composition":
9312 { "string": "abc",
9313 "clauses":
9315 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
9316 { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
9319 "caret": { "start": 2, "length": 0 }
9322 check () {
9323 is(getValue(aEditor), "abc", description + "Starting to convert 'ab][c'");
9326 { test () {
9327 synthesizeCompositionChange(
9328 { "composition":
9329 { "string": "ABc",
9330 "clauses":
9332 { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
9333 { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
9336 "caret": { "start": 2, "length": 0 }
9339 check () {
9340 is(getValue(aEditor), "ABc", description + "Starting to convert 'AB][c'");
9343 { test () {
9344 synthesizeCompositionChange(
9345 { "composition":
9346 { "string": "ABC",
9347 "clauses":
9349 { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
9350 { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
9353 "caret": { "start": 3, "length": 0 }
9356 check () {
9357 is(getValue(aEditor), "ABC", description + "Starting to convert 'AB][C'");
9360 { test () {
9361 // Commit composition
9362 synthesizeComposition({ type: "compositioncommitasis" });
9364 check () {
9365 is(getValue(aEditor), "ABC", description + "Committed as 'ABC'");
9368 { test () {
9369 synthesizeCompositionChange(
9370 { "composition":
9371 { "string": "d",
9372 "clauses":
9374 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9377 "caret": { "start": 1, "length": 0 }
9380 check () {
9381 is(getValue(aEditor), "ABCd", description + "Typing 'd' next to ABC");
9384 { test () {
9385 synthesizeCompositionChange(
9386 { "composition":
9387 { "string": "de",
9388 "clauses":
9390 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9393 "caret": { "start": 2, "length": 0 }
9396 check () {
9397 is(getValue(aEditor), "ABCde", description + "Typing 'e' next to ABCd");
9400 { test () {
9401 synthesizeCompositionChange(
9402 { "composition":
9403 { "string": "def",
9404 "clauses":
9406 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9409 "caret": { "start": 3, "length": 0 }
9412 check () {
9413 is(getValue(aEditor), "ABCdef", description + "Typing 'f' next to ABCde");
9416 { test () {
9417 // Commit composition
9418 synthesizeComposition({ type: "compositioncommitasis" });
9420 check () {
9421 is(getValue(aEditor), "ABCdef", description + "Commit 'def' without convert");
9424 { test () {
9425 // Select "Cd"
9426 synthesizeKey("KEY_ArrowLeft");
9427 synthesizeKey("KEY_ArrowLeft");
9428 synthesizeKey("KEY_Shift", {type: "keydown", shiftKey: true});
9429 synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
9430 synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
9431 synthesizeKey("KEY_Shift", {type: "keyup"});
9433 check () {
9436 { test () {
9437 synthesizeCompositionChange(
9438 { "composition":
9439 { "string": "g",
9440 "clauses":
9442 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9445 "caret": { "start": 1, "length": 0 }
9448 check () {
9449 is(getValue(aEditor), "ABgef", description + "Typing 'g' next to AB");
9452 { test () {
9453 synthesizeCompositionChange(
9454 { "composition":
9455 { "string": "gh",
9456 "clauses":
9458 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9461 "caret": { "start": 2, "length": 0 }
9464 check () {
9465 is(getValue(aEditor), "ABghef", description + "Typing 'h' next to ABg");
9468 { test () {
9469 synthesizeCompositionChange(
9470 { "composition":
9471 { "string": "ghi",
9472 "clauses":
9474 { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
9477 "caret": { "start": 3, "length": 0 }
9480 check () {
9481 is(getValue(aEditor), "ABghief", description + "Typing 'i' next to ABgh");
9484 { test () {
9485 synthesizeCompositionChange(
9486 { "composition":
9487 { "string": "GHI",
9488 "clauses":
9490 { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
9493 "caret": { "start": 3, "length": 0 }
9496 check () {
9497 is(getValue(aEditor), "ABGHIef", description + "Convert 'ghi' to 'GHI'");
9500 { test () {
9501 // Commit composition
9502 synthesizeComposition({ type: "compositioncommitasis" });
9504 check () {
9505 is(getValue(aEditor), "ABGHIef", description + "Commit 'GHI'");
9510 function doReframe(aEvent)
9512 aEvent.target.style.overflow =
9513 aEvent.target.style.overflow != "hidden" ? "hidden" : "auto";
9515 aEditor.focus();
9516 aEditor.addEventListener(aEventType, doReframe);
9518 for (const currentTest of tests) {
9519 currentTest.test();
9520 await waitForEventLoops(20);
9521 currentTest.check();
9522 await waitForTick();
9525 await new Promise(resolve => {
9526 aEditor.style.overflow = "auto";
9527 aEditor.removeEventListener(aEventType, doReframe);
9528 requestAnimationFrame(() => { SimpleTest.executeSoon(resolve); });
9532 // TODO: Add "beforeinput" case.
9533 input.value = "";
9534 await runEditorReframeTest(input, window, "input");
9535 input.value = "";
9536 await runEditorReframeTest(input, window, "compositionupdate");
9537 textarea.value = "";
9538 await runEditorReframeTest(textarea, window, "input");
9539 textarea.value = "";
9540 await runEditorReframeTest(textarea, window, "compositionupdate");
9541 contenteditable.innerHTML = "";
9542 await runEditorReframeTest(contenteditable, windowOfContenteditable, "input");
9543 contenteditable.innerHTML = "";
9544 await runEditorReframeTest(contenteditable, windowOfContenteditable, "compositionupdate");
9547 async function runIMEContentObserverTest()
9549 let notifications = [];
9550 let onReceiveNotifications = null;
9551 function callback(aTIP, aNotification)
9553 if (aNotification.type != "notify-end-input-transaction") {
9554 notifications.push(aNotification);
9556 switch (aNotification.type) {
9557 case "request-to-commit":
9558 aTIP.commitComposition();
9559 break;
9560 case "request-to-cancel":
9561 aTIP.cancelComposition();
9562 break;
9564 if (onReceiveNotifications) {
9565 let resolve = onReceiveNotifications;
9566 onReceiveNotifications = null;
9567 SimpleTest.executeSoon(() => {
9568 resolve();
9571 return true;
9574 function dumpUnexpectedNotifications(aDescription, aExpectedCount)
9576 if (notifications.length <= aExpectedCount) {
9577 return;
9579 for (let i = aExpectedCount; i < notifications.length; i++) {
9580 ok(false,
9581 aDescription + " caused unexpected notification: " + notifications[i].type);
9585 function promiseReceiveNotifications()
9587 notifications = [];
9588 return new Promise(resolve => {
9589 onReceiveNotifications = resolve;
9593 function flushNotifications()
9595 return new Promise(resolve => {
9596 // FYI: Dispatching non-op keyboard events causes forcibly flushing pending
9597 // notifications.
9598 synthesizeKey("KEY_Unidentified", { code: "" });
9599 SimpleTest.executeSoon(()=>{
9600 notifications = [];
9601 resolve();
9606 function ensureToRemovePrecedingPositionChangeNotification(aDescription)
9608 if (!notifications.length) {
9609 return;
9611 if (notifications[0].type != "notify-position-change") {
9612 return;
9614 // Sometimes, notify-position-change is notified first separately if
9615 // the operation causes scroll or something. Tests can ignore this.
9616 ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
9617 notifications.shift();
9620 // Bug 1374057 - On ubuntu 16.04 there are notify-position-change events that are
9621 // recorded after all the other events so we remove them through this function.
9622 function ensureToRemovePostPositionChangeNotification(aDescription, expectedCount)
9624 if (!notifications.length) {
9625 return;
9627 if (notifications.length <= expectedCount) {
9628 return;
9630 if (notifications[notifications.length-1].type != "notify-position-change") {
9631 return;
9633 ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
9634 notifications.pop();
9637 function getNativeText(aXPText)
9639 if (kLF == "\n") {
9640 return aXPText;
9642 return aXPText.replace(/\n/g, kLF);
9645 function checkPositionChangeNotification(aNotification, aDescription)
9647 is(!aNotification || aNotification.type, "notify-position-change",
9648 aDescription + " should cause position change notification");
9651 function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
9653 is(!aNotification || aNotification.type, "notify-selection-change",
9654 aDescription + " should cause selection change notification");
9655 if (!aNotification || (aNotification.type != "notify-selection-change")) {
9656 return;
9658 is(aNotification.offset, aExpected.offset,
9659 aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
9660 is(aNotification.text, aExpected.text,
9661 aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
9662 is(aNotification.collapsed, !aExpected.text.length,
9663 aDescription + " should cause selection change notification whose collapsed is " + (!aExpected.text.length));
9664 is(aNotification.length, aExpected.text.length,
9665 aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
9666 is(aNotification.reversed, aExpected.reversed || false,
9667 aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
9668 is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
9669 aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
9672 function checkTextChangeNotification(aNotification, aDescription, aExpected)
9674 is(!aNotification || aNotification.type, "notify-text-change",
9675 aDescription + " should cause text change notification");
9676 if (!aNotification || aNotification.type != "notify-text-change") {
9677 return;
9679 is(aNotification.offset, aExpected.offset,
9680 aDescription + " should cause text change notification whose offset is " + aExpected.offset);
9681 is(aNotification.removedLength, aExpected.removedLength,
9682 aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
9683 is(aNotification.addedLength, aExpected.addedLength,
9684 aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
9687 async function testWithPlaintextEditor(aDescription, aElement, aTestLineBreaker)
9689 aElement.value = "";
9690 aElement.blur();
9691 let doc = aElement.ownerDocument;
9692 let win = doc.defaultView;
9693 aElement.focus();
9694 await flushNotifications();
9696 // "a[]"
9697 let description = aDescription + "typing 'a'";
9698 let waitNotifications = promiseReceiveNotifications();
9699 synthesizeKey("a", {}, win, callback);
9700 await waitNotifications;
9701 ensureToRemovePrecedingPositionChangeNotification();
9702 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 0, addedLength: 1 });
9703 checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
9704 checkPositionChangeNotification(notifications[2], description);
9705 ensureToRemovePostPositionChangeNotification(description, 3);
9706 dumpUnexpectedNotifications(description, 3);
9708 // "ab[]"
9709 description = aDescription + "typing 'b'";
9710 waitNotifications = promiseReceiveNotifications();
9711 synthesizeKey("b", {}, win, callback);
9712 await waitNotifications;
9713 ensureToRemovePrecedingPositionChangeNotification();
9714 checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 0, addedLength: 1 });
9715 checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
9716 checkPositionChangeNotification(notifications[2], description);
9717 ensureToRemovePostPositionChangeNotification(description, 3);
9718 dumpUnexpectedNotifications(description, 3);
9720 // "abc[]"
9721 description = aDescription + "typing 'c'";
9722 waitNotifications = promiseReceiveNotifications();
9723 synthesizeKey("c", {}, win, callback);
9724 await waitNotifications;
9725 ensureToRemovePrecedingPositionChangeNotification();
9726 checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: 1 });
9727 checkSelectionChangeNotification(notifications[1], description, { offset: 3, text: "" });
9728 checkPositionChangeNotification(notifications[2], description);
9729 ensureToRemovePostPositionChangeNotification(description, 3);
9730 dumpUnexpectedNotifications(description, 3);
9732 // "ab[c]"
9733 description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
9734 waitNotifications = promiseReceiveNotifications();
9735 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9736 await waitNotifications;
9737 ensureToRemovePrecedingPositionChangeNotification();
9738 checkSelectionChangeNotification(notifications[0], description, { offset: 2, text: "c", reversed: true });
9739 ensureToRemovePostPositionChangeNotification(description, 1);
9740 dumpUnexpectedNotifications(description, 1);
9742 // "a[bc]"
9743 description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
9744 waitNotifications = promiseReceiveNotifications();
9745 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9746 await waitNotifications;
9747 ensureToRemovePrecedingPositionChangeNotification();
9748 checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: "bc", reversed: true });
9749 ensureToRemovePostPositionChangeNotification(description, 1);
9750 dumpUnexpectedNotifications(description, 1);
9752 // "[abc]"
9753 description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
9754 waitNotifications = promiseReceiveNotifications();
9755 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9756 await waitNotifications;
9757 ensureToRemovePrecedingPositionChangeNotification();
9758 checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "abc", reversed: true });
9759 ensureToRemovePostPositionChangeNotification(description, 1);
9760 dumpUnexpectedNotifications(description, 1);
9762 // "[]abc"
9763 description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
9764 waitNotifications = promiseReceiveNotifications();
9765 synthesizeKey("KEY_ArrowLeft", {}, win, callback);
9766 await waitNotifications;
9767 ensureToRemovePrecedingPositionChangeNotification();
9768 checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "" });
9769 ensureToRemovePostPositionChangeNotification(description, 1);
9770 dumpUnexpectedNotifications(description, 1);
9772 // "[a]bc"
9773 description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
9774 waitNotifications = promiseReceiveNotifications();
9775 synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
9776 await waitNotifications;
9777 ensureToRemovePrecedingPositionChangeNotification();
9778 checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "a" });
9779 ensureToRemovePostPositionChangeNotification(description, 1);
9780 dumpUnexpectedNotifications(description, 1);
9782 // "[ab]c"
9783 description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
9784 waitNotifications = promiseReceiveNotifications();
9785 synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
9786 await waitNotifications;
9787 ensureToRemovePrecedingPositionChangeNotification();
9788 checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "ab" });
9789 ensureToRemovePostPositionChangeNotification(description, 1);
9790 dumpUnexpectedNotifications(description, 1);
9792 // "[]c"
9793 description = aDescription + "deleting 'ab' with pressing Delete";
9794 waitNotifications = promiseReceiveNotifications();
9795 synthesizeKey("KEY_Delete", {}, win, callback);
9796 await waitNotifications;
9797 ensureToRemovePrecedingPositionChangeNotification();
9798 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
9799 checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
9800 checkPositionChangeNotification(notifications[2], description);
9801 ensureToRemovePostPositionChangeNotification(description, 3);
9802 dumpUnexpectedNotifications(description, 3);
9804 // "[]"
9805 description = aDescription + "deleting following 'c' with pressing Delete";
9806 waitNotifications = promiseReceiveNotifications();
9807 synthesizeKey("KEY_Delete", {}, win, callback);
9808 await waitNotifications;
9809 ensureToRemovePrecedingPositionChangeNotification();
9810 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 1, addedLength: 0 });
9811 checkPositionChangeNotification(notifications[1], description);
9812 ensureToRemovePostPositionChangeNotification(description, 2);
9813 dumpUnexpectedNotifications(description, 2);
9815 // "abc[]"
9816 synthesizeKey("a", {}, win, callback);
9817 synthesizeKey("b", {}, win, callback);
9818 synthesizeKey("c", {}, win, callback);
9819 await flushNotifications();
9821 // "ab[]"
9822 description = aDescription + "deleting 'c' with pressing Backspace";
9823 waitNotifications = promiseReceiveNotifications();
9824 synthesizeKey("KEY_Backspace", {}, win, callback);
9825 await waitNotifications;
9826 ensureToRemovePrecedingPositionChangeNotification();
9827 checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 1, addedLength: 0 });
9828 checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
9829 checkPositionChangeNotification(notifications[2], description);
9830 ensureToRemovePostPositionChangeNotification(description, 3);
9831 dumpUnexpectedNotifications(description, 3);
9833 // "[ab]"
9834 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9835 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9836 await flushNotifications();
9838 // "[]"
9839 description = aDescription + "deleting 'ab' with pressing Backspace";
9840 waitNotifications = promiseReceiveNotifications();
9841 synthesizeKey("KEY_Backspace", {}, win, callback);
9842 await waitNotifications;
9843 ensureToRemovePrecedingPositionChangeNotification();
9844 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
9845 checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
9846 checkPositionChangeNotification(notifications[2], description);
9847 ensureToRemovePostPositionChangeNotification(description, 3);
9848 dumpUnexpectedNotifications(description, 3);
9850 // "abcd[]"
9851 synthesizeKey("a", {}, win, callback);
9852 synthesizeKey("b", {}, win, callback);
9853 synthesizeKey("c", {}, win, callback);
9854 synthesizeKey("d", {}, win, callback);
9855 await flushNotifications();
9857 // "a[bc]d"
9858 synthesizeKey("KEY_ArrowLeft", {}, win, callback);
9859 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9860 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9861 await flushNotifications();
9863 // "a[]d"
9864 description = aDescription + "deleting 'bc' with pressing Backspace";
9865 waitNotifications = promiseReceiveNotifications();
9866 synthesizeKey("KEY_Backspace", {}, win, callback);
9867 await waitNotifications;
9868 ensureToRemovePrecedingPositionChangeNotification();
9869 checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 0 });
9870 checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
9871 checkPositionChangeNotification(notifications[2], description);
9872 ensureToRemovePostPositionChangeNotification(description, 3);
9873 dumpUnexpectedNotifications(description, 3);
9875 // "a[bc]d"
9876 synthesizeKey("b", {}, win, callback);
9877 synthesizeKey("c", {}, win, callback);
9878 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9879 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9880 await flushNotifications();
9882 // "aB[]d"
9883 description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
9884 waitNotifications = promiseReceiveNotifications();
9885 synthesizeKey("B", {shiftKey: true}, win, callback);
9886 await waitNotifications;
9887 ensureToRemovePrecedingPositionChangeNotification();
9888 checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 1 });
9889 checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
9890 checkPositionChangeNotification(notifications[2], description);
9891 ensureToRemovePostPositionChangeNotification(description, 3);
9892 dumpUnexpectedNotifications(description, 3);
9894 if (!aTestLineBreaker) {
9895 return;
9898 // "aB\n[]d"
9899 description = aDescription + "inserting a line break after 'B' with pressing Enter";
9900 waitNotifications = promiseReceiveNotifications();
9901 synthesizeKey("KEY_Enter", {}, win, callback);
9902 await waitNotifications;
9903 ensureToRemovePrecedingPositionChangeNotification();
9904 checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: kLFLen });
9905 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length, text: "" });
9906 checkPositionChangeNotification(notifications[2], description);
9907 ensureToRemovePostPositionChangeNotification(description, 3);
9908 dumpUnexpectedNotifications(description, 3);
9910 // "aB[]d"
9911 description = aDescription + "removing a line break after 'B' with pressing Backspace";
9912 waitNotifications = promiseReceiveNotifications();
9913 synthesizeKey("KEY_Backspace", {}, win, callback);
9914 await waitNotifications;
9915 ensureToRemovePrecedingPositionChangeNotification();
9916 checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: kLFLen, addedLength: 0 });
9917 checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
9918 checkPositionChangeNotification(notifications[2], description);
9919 ensureToRemovePostPositionChangeNotification(description, 3);
9920 dumpUnexpectedNotifications(description, 3);
9922 // "a[B]d"
9923 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9924 await flushNotifications();
9926 // "a\n[]d"
9927 description = aDescription + "replacing 'B' with a line break with pressing Enter";
9928 waitNotifications = promiseReceiveNotifications();
9929 synthesizeKey("KEY_Enter", {}, win, callback);
9930 await waitNotifications;
9931 ensureToRemovePrecedingPositionChangeNotification();
9932 checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 1, addedLength: kLFLen });
9933 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length, text: "" });
9934 checkPositionChangeNotification(notifications[2], description);
9935 ensureToRemovePostPositionChangeNotification(description, 3);
9936 dumpUnexpectedNotifications(description, 3);
9938 // "a[\n]d"
9939 description = aDescription + "selecting '\n' with pressing Shift+ArrowLeft";
9940 waitNotifications = promiseReceiveNotifications();
9941 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
9942 await waitNotifications;
9943 ensureToRemovePrecedingPositionChangeNotification();
9944 checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: kLF, reversed: true });
9945 ensureToRemovePostPositionChangeNotification(description, 1);
9946 dumpUnexpectedNotifications(description, 1);
9948 // "a[]d"
9949 description = aDescription + "removing selected '\n' with pressing Delete";
9950 waitNotifications = promiseReceiveNotifications();
9951 synthesizeKey("KEY_Delete", {shiftKey: true}, win, callback);
9952 await waitNotifications;
9953 ensureToRemovePrecedingPositionChangeNotification();
9954 checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: kLFLen, addedLength: 0 });
9955 checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
9956 checkPositionChangeNotification(notifications[2], description);
9957 ensureToRemovePostPositionChangeNotification(description, 3);
9958 dumpUnexpectedNotifications(description, 3);
9960 // ab\ncd\nef\ngh\n[]
9961 description = aDescription + "setting the value property to 'ab\ncd\nef\ngh\n'";
9962 waitNotifications = promiseReceiveNotifications();
9963 aElement.value = "ab\ncd\nef\ngh\n";
9964 await waitNotifications;
9965 ensureToRemovePrecedingPositionChangeNotification();
9966 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: getNativeText("ab\ncd\nef\ngh\n").length });
9967 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("ab\ncd\nef\ngh\n").length, text: "" });
9968 checkPositionChangeNotification(notifications[2], description);
9969 ensureToRemovePostPositionChangeNotification(description, 3);
9970 dumpUnexpectedNotifications(description, 3);
9972 // []
9973 description = aDescription + "setting the value property to ''";
9974 waitNotifications = promiseReceiveNotifications();
9975 aElement.value = "";
9976 await waitNotifications;
9977 ensureToRemovePrecedingPositionChangeNotification();
9978 checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length, addedLength: 0 });
9979 checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
9980 checkPositionChangeNotification(notifications[2], description);
9981 ensureToRemovePostPositionChangeNotification(description, 3);
9982 dumpUnexpectedNotifications(description, 3);
9985 async function testWithHTMLEditor(aDescription, aElement, aDefaultParagraphSeparator)
9987 let doc = aElement.ownerDocument;
9988 let win = doc.defaultView;
9989 let sel = doc.getSelection();
9990 let inDesignMode = doc.designMode == "on";
9991 let offsetAtStart = 0;
9992 let offsetAtContainer = 0;
9993 let isDefaultParagraphSeparatorBlock = aDefaultParagraphSeparator != "br";
9994 doc.execCommand("defaultParagraphSeparator", false, aDefaultParagraphSeparator);
9996 // "[]", "<p>[]</p>" or "<div>[]</div>"
9997 switch (aDefaultParagraphSeparator) {
9998 case "br":
9999 aElement.innerHTML = "";
10000 break;
10001 case "p":
10002 case "div":
10003 aElement.innerHTML = "<" + aDefaultParagraphSeparator + "></" + aDefaultParagraphSeparator + ">";
10004 sel.collapse(aElement.firstChild, 0);
10005 offsetAtContainer = offsetAtStart + kLFLen;
10006 break;
10007 default:
10008 ok(false, aDescription + "aDefaultParagraphSeparator is illegal value");
10009 await flushNotifications();
10010 return;
10013 if (inDesignMode) {
10014 win.focus();
10015 } else {
10016 aElement.focus();
10018 await flushNotifications();
10020 // "a[]"
10021 let description = aDescription + "typing 'a'";
10022 let waitNotifications = promiseReceiveNotifications();
10023 synthesizeKey("a", {}, win, callback);
10024 await waitNotifications;
10025 ensureToRemovePrecedingPositionChangeNotification();
10026 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 0, addedLength: 1 });
10027 checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
10028 checkPositionChangeNotification(notifications[2], description);
10029 dumpUnexpectedNotifications(description, 3);
10031 // "ab[]"
10032 description = aDescription + "typing 'b'";
10033 waitNotifications = promiseReceiveNotifications();
10034 synthesizeKey("b", {}, win, callback);
10035 await waitNotifications;
10036 ensureToRemovePrecedingPositionChangeNotification();
10037 checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 0, addedLength: 1 });
10038 checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
10039 checkPositionChangeNotification(notifications[2], description);
10040 dumpUnexpectedNotifications(description, 3);
10042 // "abc[]"
10043 description = aDescription + "typing 'c'";
10044 waitNotifications = promiseReceiveNotifications();
10045 synthesizeKey("c", {}, win, callback);
10046 await waitNotifications;
10047 ensureToRemovePrecedingPositionChangeNotification();
10048 checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 0, addedLength: 1 });
10049 checkSelectionChangeNotification(notifications[1], description, { offset: 3 + offsetAtContainer, text: "" });
10050 checkPositionChangeNotification(notifications[2], description);
10051 dumpUnexpectedNotifications(description, 3);
10053 // "ab[c]"
10054 description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
10055 waitNotifications = promiseReceiveNotifications();
10056 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10057 await waitNotifications;
10058 ensureToRemovePrecedingPositionChangeNotification();
10059 checkSelectionChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, text: "c", reversed: true });
10060 dumpUnexpectedNotifications(description, 1);
10062 // "a[bc]"
10063 description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
10064 waitNotifications = promiseReceiveNotifications();
10065 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10066 await waitNotifications;
10067 ensureToRemovePrecedingPositionChangeNotification();
10068 checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: "bc", reversed: true });
10069 dumpUnexpectedNotifications(description, 1);
10071 // "[abc]"
10072 description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
10073 waitNotifications = promiseReceiveNotifications();
10074 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10075 await waitNotifications;
10076 ensureToRemovePrecedingPositionChangeNotification();
10077 checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "abc", reversed: true });
10078 dumpUnexpectedNotifications(description, 1);
10080 // "[]abc"
10081 description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
10082 waitNotifications = promiseReceiveNotifications();
10083 synthesizeKey("KEY_ArrowLeft", {}, win, callback);
10084 await waitNotifications;
10085 ensureToRemovePrecedingPositionChangeNotification();
10086 checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "" });
10087 dumpUnexpectedNotifications(description, 1);
10089 // "[a]bc"
10090 description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
10091 waitNotifications = promiseReceiveNotifications();
10092 synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
10093 await waitNotifications;
10094 ensureToRemovePrecedingPositionChangeNotification();
10095 checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "a" });
10096 dumpUnexpectedNotifications(description, 1);
10098 // "[ab]c"
10099 description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
10100 waitNotifications = promiseReceiveNotifications();
10101 synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
10102 await waitNotifications;
10103 ensureToRemovePrecedingPositionChangeNotification();
10104 checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "ab" });
10105 dumpUnexpectedNotifications(description, 1);
10107 // "[]c"
10108 description = aDescription + "deleting 'ab' with pressing Delete";
10109 waitNotifications = promiseReceiveNotifications();
10110 synthesizeKey("KEY_Delete", {}, win, callback);
10111 await waitNotifications;
10112 ensureToRemovePrecedingPositionChangeNotification();
10113 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
10114 checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
10115 checkPositionChangeNotification(notifications[2], description);
10116 dumpUnexpectedNotifications(description, 3);
10118 // "[]"
10119 description = aDescription + "deleting following 'c' with pressing Delete";
10120 waitNotifications = promiseReceiveNotifications();
10121 synthesizeKey("KEY_Delete", {}, win, callback);
10122 await waitNotifications;
10123 ensureToRemovePrecedingPositionChangeNotification();
10124 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
10125 checkPositionChangeNotification(notifications[1], description);
10126 dumpUnexpectedNotifications(description, 2);
10128 // "abc[]"
10129 synthesizeKey("a", {}, win, callback);
10130 synthesizeKey("b", {}, win, callback);
10131 synthesizeKey("c", {}, win, callback);
10132 await flushNotifications();
10134 // "ab[]"
10135 description = aDescription + "deleting 'c' with pressing Backspace";
10136 waitNotifications = promiseReceiveNotifications();
10137 synthesizeKey("KEY_Backspace", {}, win, callback);
10138 await waitNotifications;
10139 ensureToRemovePrecedingPositionChangeNotification();
10140 checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 1, addedLength: 0 });
10141 checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
10142 checkPositionChangeNotification(notifications[2], description);
10143 dumpUnexpectedNotifications(description, 3);
10145 // "[ab]"
10146 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10147 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10148 await flushNotifications();
10150 // "[]"
10151 description = aDescription + "deleting 'ab' with pressing Backspace";
10152 waitNotifications = promiseReceiveNotifications();
10153 synthesizeKey("KEY_Backspace", {}, win, callback);
10154 await waitNotifications;
10155 ensureToRemovePrecedingPositionChangeNotification();
10156 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
10157 checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
10158 checkPositionChangeNotification(notifications[2], description);
10159 dumpUnexpectedNotifications(description, 3);
10161 // "abcd[]"
10162 synthesizeKey("a", {}, win, callback);
10163 synthesizeKey("b", {}, win, callback);
10164 synthesizeKey("c", {}, win, callback);
10165 synthesizeKey("d", {}, win, callback);
10166 await flushNotifications();
10168 // "a[bc]d"
10169 synthesizeKey("KEY_ArrowLeft", {}, win, callback);
10170 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10171 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10172 await flushNotifications();
10174 // "a[]d"
10175 description = aDescription + "deleting 'bc' with pressing Backspace";
10176 waitNotifications = promiseReceiveNotifications();
10177 synthesizeKey("KEY_Backspace", {}, win, callback);
10178 await waitNotifications;
10179 ensureToRemovePrecedingPositionChangeNotification();
10180 checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 0 });
10181 checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
10182 checkPositionChangeNotification(notifications[2], description);
10183 dumpUnexpectedNotifications(description, 3);
10185 // "a[bc]d"
10186 synthesizeKey("b", {}, win, callback);
10187 synthesizeKey("c", {}, win, callback);
10188 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10189 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10190 await flushNotifications();
10192 // "aB[]d"
10193 description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
10194 waitNotifications = promiseReceiveNotifications();
10195 synthesizeKey("B", {shiftKey: true}, win, callback);
10196 await waitNotifications;
10197 ensureToRemovePrecedingPositionChangeNotification();
10198 checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 1 });
10199 checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
10200 checkPositionChangeNotification(notifications[2], description);
10201 dumpUnexpectedNotifications(description, 3);
10203 // "aB<br>[]d" or "<block>aB</block><block>[]d</block>"
10204 description = aDescription + "inserting a line break after 'B' with pressing Enter";
10205 waitNotifications = promiseReceiveNotifications();
10206 synthesizeKey("KEY_Enter", {}, win, callback);
10207 await waitNotifications;
10208 ensureToRemovePrecedingPositionChangeNotification();
10209 if (isDefaultParagraphSeparatorBlock) {
10210 // Splitting current block causes removing "d</block>" and inserting "</block><block>d</block>".
10211 checkTextChangeNotification(notifications[0], description, {
10212 offset: offsetAtContainer + "aB".length,
10213 removedLength: getNativeText("d\n").length,
10214 addedLength: getNativeText("\nd\n").length,
10216 } else {
10217 // Inserting <br> causes removing "d" and inserting "<br>d"
10218 checkTextChangeNotification(notifications[0], description, {
10219 offset: offsetAtContainer + "aB".length,
10220 removedLength: "d".length,
10221 addedLength: getNativeText("\nd").length,
10224 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length + offsetAtContainer, text: "" });
10225 checkPositionChangeNotification(notifications[2], description);
10226 dumpUnexpectedNotifications(description, 3);
10228 // "aB[]d"
10229 description = aDescription + "removing a line break after 'B' with pressing Backspace";
10230 waitNotifications = promiseReceiveNotifications();
10231 synthesizeKey("KEY_Backspace", {}, win, callback);
10232 await waitNotifications;
10233 ensureToRemovePrecedingPositionChangeNotification();
10234 if (isDefaultParagraphSeparatorBlock) {
10235 // Joining two blocks causes removing "aB</block><block>d</block>" and inserting "aBd</block>"
10236 checkTextChangeNotification(notifications[0], description, {
10237 offset: offsetAtContainer,
10238 removedLength: getNativeText("aB\nd\n").length,
10239 addedLength: getNativeText("aBd\n").length,
10241 checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
10242 checkPositionChangeNotification(notifications[2], description);
10243 dumpUnexpectedNotifications(description, 3);
10244 } else {
10245 checkTextChangeNotification(notifications[0], description, {
10246 offset: offsetAtContainer + "aB".length,
10247 removedLength: kLFLen,
10248 addedLength: 0,
10250 is(notifications.length, 3, description + " should cause 3 notifications");
10251 is(notifications[1] && notifications[1].type, "notify-selection-change", description + " should cause selection change notification");
10252 is(notifications[2] && notifications[2].type, "notify-position-change", description + " should cause position change notification");
10255 // "a[B]d"
10256 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10257 await flushNotifications();
10259 // "a<br>[]d" or "<block>a</block><block>[]d</block>"
10260 description = aDescription + "replacing 'B' with a line break with pressing Enter";
10261 waitNotifications = promiseReceiveNotifications();
10262 synthesizeKey("KEY_Enter", {}, win, callback);
10263 await waitNotifications;
10264 ensureToRemovePrecedingPositionChangeNotification();
10265 if (isDefaultParagraphSeparatorBlock) {
10266 // Splitting current block causes removing "Bd</block>" and inserting "</block><block>d</block>".
10267 checkTextChangeNotification(notifications[0], description, {
10268 offset: offsetAtContainer + "a".length,
10269 removedLength: getNativeText("Bd\n").length,
10270 addedLength: getNativeText("\nd\n").length,
10272 } else {
10273 checkTextChangeNotification(notifications[0], description, {
10274 offset: offsetAtContainer + "a".length,
10275 removedLength: "B".length,
10276 addedLength: kLFLen,
10279 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length + offsetAtContainer, text: "" });
10280 checkPositionChangeNotification(notifications[2], description);
10281 dumpUnexpectedNotifications(description, 3);
10283 // "a[<br>]d" or "<block>a[</block><block>]d</block>"
10284 description = aDescription + "selecting '\\n' with pressing Shift+ArrowLeft";
10285 waitNotifications = promiseReceiveNotifications();
10286 synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
10287 await waitNotifications;
10288 ensureToRemovePrecedingPositionChangeNotification();
10289 checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: kLF, reversed: true });
10290 dumpUnexpectedNotifications(description, 1);
10292 // "a[]d"
10293 description = aDescription + "removing selected '\\n' with pressing Delete";
10294 waitNotifications = promiseReceiveNotifications();
10295 synthesizeKey("KEY_Delete", {}, win, callback);
10296 await waitNotifications;
10297 ensureToRemovePrecedingPositionChangeNotification();
10298 if (isDefaultParagraphSeparatorBlock) {
10299 // Joining the blocks causes removing "a</block><block>d</block>" and inserting "<block>ad</block>".
10300 checkTextChangeNotification(notifications[0], description, {
10301 offset: offsetAtContainer,
10302 removedLength: getNativeText("a\nd\n").length,
10303 addedLength: getNativeText("ad\n").length,
10305 } else {
10306 checkTextChangeNotification(notifications[0], description, {
10307 offset: offsetAtContainer + "a".length,
10308 removedLength: kLFLen,
10309 addedLength: 0,
10312 checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
10313 checkPositionChangeNotification(notifications[2], description);
10314 dumpUnexpectedNotifications(description, 3);
10316 // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>"
10317 description = aDescription + "inserting HTML which has nested block elements";
10318 waitNotifications = promiseReceiveNotifications();
10319 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
10320 await waitNotifications;
10321 ensureToRemovePrecedingPositionChangeNotification();
10322 // There is <br> after the end of the line. Therefore, removed length includes a line breaker length.
10323 if (isDefaultParagraphSeparatorBlock) {
10324 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\nad\n").length, addedLength: getNativeText("\n1\n2\n345").length });
10325 } else {
10326 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2 + kLFLen, addedLength: getNativeText("\n1\n2\n345").length });
10328 checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
10329 checkPositionChangeNotification(notifications[2], description);
10330 dumpUnexpectedNotifications(description, 3);
10332 // "<div>1[<div>2<div>3</div>4</div>]5</div>" and removing selection
10333 sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
10334 await flushNotifications();
10335 description = aDescription + "deleting child nodes with pressing Delete key";
10336 waitNotifications = promiseReceiveNotifications();
10337 synthesizeKey("KEY_Delete", {}, win, callback);
10338 await waitNotifications;
10339 ensureToRemovePrecedingPositionChangeNotification();
10340 is(aElement.innerHTML, "<div>15</div>", description + " should remove '<div>2<div>3</div>4</div>'");
10341 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 0 });
10342 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
10343 checkPositionChangeNotification(notifications[2], description);
10344 dumpUnexpectedNotifications(description, 3);
10346 // "<div>1[<div>2<div>3</div>]4</div>5</div>" and removing selection
10347 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
10348 sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(2), 0);
10349 await flushNotifications();
10350 description = aDescription + "deleting child nodes (partially #1) with pressing Delete key";
10351 waitNotifications = promiseReceiveNotifications();
10352 synthesizeKey("KEY_Delete", {}, win, callback);
10353 await waitNotifications;
10354 ensureToRemovePrecedingPositionChangeNotification();
10355 is(aElement.innerHTML, "<div>145</div>", description + " should remove '<div>2<div>3</div></div>'");
10356 // It causes removing '<div>2<div>3</div>4</div>' and inserting '4'.
10357 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 1 });
10358 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
10359 checkPositionChangeNotification(notifications[2], description);
10360 dumpUnexpectedNotifications(description, 3);
10362 // "<div>1[<div>2<div>]3</div>4</div>5</div>" and removing selection
10363 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
10364 sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 0);
10365 await flushNotifications();
10366 description = aDescription + "deleting child nodes (partially #2) with pressing Delete key";
10367 waitNotifications = promiseReceiveNotifications();
10368 synthesizeKey("KEY_Delete", {}, win, callback);
10369 await waitNotifications;
10370 ensureToRemovePrecedingPositionChangeNotification();
10371 is(aElement.innerHTML, "<div>13<div>4</div>5</div>", description + " should remove '<div>2</div>'");
10372 // It causes removing '1<div>2<div>3</div></div>' and inserting '13<div>'.
10373 checkTextChangeNotification(notifications[0], description, { offset: kLFLen + offsetAtStart, removedLength: getNativeText("1\n2\n3").length, addedLength: getNativeText("13\n").length });
10374 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
10375 checkPositionChangeNotification(notifications[2], description);
10376 dumpUnexpectedNotifications(description, 3);
10378 // "<div>1<div>2<div>3[</div>4</div>]5</div>" and removing selection
10379 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
10380 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
10381 await flushNotifications();
10382 description = aDescription + "deleting child nodes (partially #3) with pressing Delete key";
10383 waitNotifications = promiseReceiveNotifications();
10384 synthesizeKey("KEY_Delete", {}, win, callback);
10385 await waitNotifications;
10386 ensureToRemovePrecedingPositionChangeNotification();
10387 is(aElement.innerHTML, "<div>1<div>2<div>35</div></div></div>", description + " should remove '4'");
10388 // It causes removing '45' and inserting '5'.
10389 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: 2, addedLength: 1 });
10390 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
10391 checkPositionChangeNotification(notifications[2], description);
10392 dumpUnexpectedNotifications(description, 3);
10394 // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>"
10395 description = aDescription + "inserting HTML which has a pair of nested block elements";
10396 waitNotifications = promiseReceiveNotifications();
10397 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
10398 await waitNotifications;
10399 ensureToRemovePrecedingPositionChangeNotification();
10400 checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtStart, removedLength: getNativeText("\n1\n2\n35").length, addedLength: getNativeText("\n1\n2\n345\n6\n789").length });
10401 checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtStart, text: "" });
10402 checkPositionChangeNotification(notifications[2], description);
10403 dumpUnexpectedNotifications(description, 3);
10405 // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
10406 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
10407 await flushNotifications();
10408 description = aDescription + "deleting child nodes (between same level descendants) with pressing Delete key";
10409 waitNotifications = promiseReceiveNotifications();
10410 synthesizeKey("KEY_Delete", {}, win, callback);
10411 await waitNotifications;
10412 ensureToRemovePrecedingPositionChangeNotification();
10413 is(aElement.innerHTML, "<div>1<div>2<div>37</div></div><div>8</div>9</div>", description + " should remove '456<div>7'");
10414 // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>37</div><div>'.
10415 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n7").length, addedLength: getNativeText("\n37\n").length });
10416 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
10417 checkPositionChangeNotification(notifications[2], description);
10418 dumpUnexpectedNotifications(description, 3);
10420 // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
10421 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
10422 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
10423 await flushNotifications();
10424 description = aDescription + "deleting child nodes (between different level descendants #1) with pressing Delete key";
10425 waitNotifications = promiseReceiveNotifications();
10426 synthesizeKey("KEY_Delete", {}, win, callback);
10427 await waitNotifications;
10428 ensureToRemovePrecedingPositionChangeNotification();
10429 is(aElement.innerHTML, "<div>1<div>27</div><div>8</div>9</div>", description + " should remove '<div>2<div>3</div>4</div>5<div>6<div>7</div>'");
10430 // It causes removing '<div>2<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>27</div>'.
10431 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n345\n6\n7").length, addedLength: getNativeText("\n27\n").length });
10432 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
10433 checkPositionChangeNotification(notifications[2], description);
10434 dumpUnexpectedNotifications(description, 3);
10436 // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
10437 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
10438 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
10439 await flushNotifications();
10440 description = aDescription + "deleting child nodes (between different level descendants #2) with pressing Delete key";
10441 waitNotifications = promiseReceiveNotifications();
10442 synthesizeKey("KEY_Delete", {}, win, callback);
10443 await waitNotifications;
10444 ensureToRemovePrecedingPositionChangeNotification();
10445 is(aElement.innerHTML, "<div>1<div>29</div></div>", description + " should remove '<div>3</div>4</div>5<div>6<div>7</div>8</div>'");
10446 // It causes removing '<div>3</div>4</div>5</div>6<div>7</div>8</div>9' and inserting '9</div>'.
10447 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n789").length, addedLength: 1 });
10448 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
10449 checkPositionChangeNotification(notifications[2], description);
10450 dumpUnexpectedNotifications(description, 3);
10452 // "<div>1<div>2<div>3[</div>4</div>5<div>]6<div>7</div>8</div>9</div>" and removing selection
10453 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
10454 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).firstChild, 0);
10455 await flushNotifications();
10456 description = aDescription + "deleting child nodes (between different level descendants #3) with pressing Delete key";
10457 waitNotifications = promiseReceiveNotifications();
10458 synthesizeKey("KEY_Delete", {}, win, callback);
10459 await waitNotifications;
10460 ensureToRemovePrecedingPositionChangeNotification();
10461 is(aElement.innerHTML, "<div>1<div>2<div>36<div>7</div>8</div></div>9</div>", description + " should remove '<div>36<div>7</div>8</div>'");
10462 // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
10463 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n78").length, addedLength: getNativeText("\n36\n78").length });
10464 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
10465 checkPositionChangeNotification(notifications[2], description);
10466 dumpUnexpectedNotifications(description, 3);
10468 // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
10469 aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
10470 sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
10471 await flushNotifications();
10472 description = aDescription + "deleting child nodes (between different level descendants #4) with pressing Delete key";
10473 waitNotifications = promiseReceiveNotifications();
10474 synthesizeKey("KEY_Delete", {}, win, callback);
10475 await waitNotifications;
10476 ensureToRemovePrecedingPositionChangeNotification();
10477 is(aElement.innerHTML, "<div>1<div>2<div>39</div></div></div>", description + " should remove '</div>4</div>5<div>6<div>7</div>8</div>'");
10478 // It causes removing '</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
10479 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: getNativeText("45\n6\n789").length, addedLength: getNativeText("9").length });
10480 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
10481 checkPositionChangeNotification(notifications[2], description);
10482 dumpUnexpectedNotifications(description, 3);
10484 // "<p>abc</p><p><br></p><p>{<br>}</p>" and removing second paragraph with DOM API
10485 aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
10486 sel.collapse(aElement.firstChild.nextSibling.nextSibling, 0);
10487 await flushNotifications();
10488 description = aDescription + "deleting previous paragraph with DOM API";
10489 waitNotifications = promiseReceiveNotifications();
10490 synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
10491 aElement.firstChild.nextSibling.remove();
10492 await waitNotifications;
10493 ensureToRemovePrecedingPositionChangeNotification();
10494 is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the second paragraph should've been removed");
10495 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
10496 checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\nabc\n").length + offsetAtStart, text: "" });
10497 checkPositionChangeNotification(notifications[2], description);
10498 dumpUnexpectedNotifications(description, 3);
10500 // "<p>abc</p><p>{<br>}</p><p><br></p>" and removing last paragraph with DOM API
10501 aElement.innerHTML = "<p>abc</p><p><br></p><p><br></p>";
10502 sel.collapse(aElement.firstChild.nextSibling, 0);
10503 await flushNotifications();
10504 description = aDescription + "deleting next paragraph with DOM API";
10505 waitNotifications = promiseReceiveNotifications();
10506 synthesizeKey("KEY_Unidentified", { code: "" }, win, callback); // For setting the callback to recode notifications
10507 aElement.firstChild.nextSibling.nextSibling.remove();
10508 await waitNotifications;
10509 ensureToRemovePrecedingPositionChangeNotification();
10510 is(aElement.innerHTML, "<p>abc</p><p><br></p>", description + " the last paragraph should've been removed");
10511 checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc\n\n").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
10512 checkPositionChangeNotification(notifications[1], description);
10513 dumpUnexpectedNotifications(description, 2);
10516 await testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
10517 await testWithPlaintextEditor("runIMEContentObserverTest with textarea element: ", textarea, true);
10518 await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is br): ", contenteditable, "br");
10519 await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is p): ", contenteditable, "p");
10520 await testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is div): ", contenteditable, "div");
10521 // XXX Due to the difference of HTML editor behavior between designMode and contenteditable,
10522 // testWithHTMLEditor() gets some unexpected behavior. However, IMEContentObserver is
10523 // not depend on editor's detail. So, we should investigate this issue later. It's not
10524 // so important for now.
10525 // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is br): ", iframe2.contentDocument.body, "br");
10526 // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is p): ", iframe2.contentDocument.body, "p");
10527 // await testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is div): ", iframe2.contentDocument.body, "div");
10530 async function runPasswordMaskDelayTest() {
10531 await SpecialPowers.pushPrefEnv({
10532 set: [["editor.password.mask_delay", 600],
10533 ["editor.password.testing.mask_delay", true],
10537 let iframe5 = document.getElementById("iframe5");
10538 let iframe6 = document.getElementById("iframe6");
10539 let inputWindow = iframe5.contentWindow;
10540 let passwordWindow = iframe6.contentWindow;
10542 let inputElement = iframe5.contentDocument.getElementById("input");
10543 let passwordElement = iframe6.contentDocument.getElementById("password");
10545 const kMask = passwordElement.editor.passwordMask;
10547 function promiseAllPasswordMasked() {
10548 return new Promise(resolve => {
10549 passwordElement.addEventListener("MozLastInputMasked", resolve, {once: true});
10553 function checkSnapshots(aResult, aReference, aMatch, aDescription) {
10554 let [correct, data1, data2] = compareSnapshots(aResult, aReference, true);
10555 is(correct, aMatch, `${aDescription}\nREFTEST IMAGE 1 (TEST): ${data1}\nREFTEST IMAGE 2 (REFERENCE): ${data2}`);
10558 // First character input
10559 passwordElement.value = "";
10560 passwordElement.focus();
10561 let waitForMaskingLastInput = promiseAllPasswordMasked();
10562 synthesizeKey("a");
10563 let unmaskedResult = await snapshotWindow(passwordWindow, true);
10564 await waitForMaskingLastInput;
10565 let maskedResult = await snapshotWindow(passwordWindow, true);
10567 inputElement.value = "a";
10568 inputElement.focus();
10569 inputElement.setSelectionRange(1, 1);
10570 let unmaskedReference = await snapshotWindow(inputWindow, true);
10571 inputElement.value = kMask;
10572 inputElement.setSelectionRange(1, 1);
10573 let maskedReference = await snapshotWindow(inputWindow, true);
10574 checkSnapshots(unmaskedResult, unmaskedReference, true,
10575 "runPasswordMaskDelayTest(): first inputted character should be unmasked for a while");
10576 checkSnapshots(maskedResult, maskedReference, true,
10577 "runPasswordMaskDelayTest(): first inputted character should be masked after a while");
10579 // Second character input
10580 passwordElement.value = "a";
10581 passwordElement.focus();
10582 passwordElement.setSelectionRange(1, 1);
10583 waitForMaskingLastInput = promiseAllPasswordMasked();
10584 synthesizeKey("b");
10585 unmaskedResult = await snapshotWindow(passwordWindow, true);
10586 await waitForMaskingLastInput;
10587 maskedResult = await snapshotWindow(passwordWindow, true);
10589 inputElement.value = `${kMask}b`;
10590 inputElement.focus();
10591 inputElement.setSelectionRange(2, 2);
10592 unmaskedReference = await snapshotWindow(inputWindow, true);
10593 inputElement.value = `${kMask}${kMask}`;
10594 inputElement.setSelectionRange(2, 2);
10595 maskedReference = await snapshotWindow(inputWindow, true);
10596 checkSnapshots(unmaskedResult, unmaskedReference, true,
10597 "runPasswordMaskDelayTest(): second inputted character should be unmasked for a while");
10598 checkSnapshots(maskedResult, maskedReference, true,
10599 "runPasswordMaskDelayTest(): second inputted character should be masked after a while");
10601 // Typing new character should mask the previous unmasked characters
10602 passwordElement.value = "ab";
10603 passwordElement.focus();
10604 passwordElement.setSelectionRange(2, 2);
10605 waitForMaskingLastInput = promiseAllPasswordMasked();
10606 synthesizeKey("c");
10607 synthesizeKey("d");
10608 unmaskedResult = await snapshotWindow(passwordWindow, true);
10609 await waitForMaskingLastInput;
10610 maskedResult = await snapshotWindow(passwordWindow, true);
10612 inputElement.value = `${kMask}${kMask}${kMask}d`;
10613 inputElement.focus();
10614 inputElement.setSelectionRange(4, 4);
10615 unmaskedReference = await snapshotWindow(inputWindow, true);
10616 inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
10617 inputElement.setSelectionRange(4, 4);
10618 maskedReference = await snapshotWindow(inputWindow, true);
10619 checkSnapshots(unmaskedResult, unmaskedReference, true,
10620 "runPasswordMaskDelayTest(): forth character input should mask the third character");
10621 checkSnapshots(maskedResult, maskedReference, true,
10622 "runPasswordMaskDelayTest(): forth inputted character should be masked after a while");
10624 // Typing middle of password should unmask the last input character
10625 passwordElement.value = "abcd";
10626 passwordElement.focus();
10627 passwordElement.setSelectionRange(2, 2);
10628 waitForMaskingLastInput = promiseAllPasswordMasked();
10629 synthesizeKey("e");
10630 unmaskedResult = await snapshotWindow(passwordWindow, true);
10631 await waitForMaskingLastInput;
10632 maskedResult = await snapshotWindow(passwordWindow, true);
10634 inputElement.value = `${kMask}${kMask}e${kMask}${kMask}`;
10635 inputElement.focus();
10636 inputElement.setSelectionRange(3, 3);
10637 unmaskedReference = await snapshotWindow(inputWindow, true);
10638 inputElement.value = `${kMask}${kMask}${kMask}${kMask}${kMask}`;
10639 inputElement.setSelectionRange(3, 3);
10640 maskedReference = await snapshotWindow(inputWindow, true);
10641 checkSnapshots(unmaskedResult, unmaskedReference, true,
10642 "runPasswordMaskDelayTest(): inserted character should be unmasked for a while");
10643 checkSnapshots(maskedResult, maskedReference, true,
10644 "runPasswordMaskDelayTest(): inserted character should be masked after a while");
10646 // Composition string should be unmasked for a while, and shouldn't be committed at masking
10647 passwordElement.value = "ab";
10648 passwordElement.focus();
10649 passwordElement.setSelectionRange(1, 1);
10650 waitForMaskingLastInput = promiseAllPasswordMasked();
10651 synthesizeCompositionChange(
10652 { composition:
10653 { string: "c",
10654 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10656 caret: { start: 1, length: 0 },
10658 unmaskedResult = await snapshotWindow(passwordWindow, true);
10659 await waitForMaskingLastInput;
10660 maskedResult = await snapshotWindow(passwordWindow, true);
10661 is(getEditor(passwordElement).composing, true,
10662 "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #1");
10663 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10665 inputElement.value = `${kMask}${kMask}`;
10666 inputElement.focus();
10667 inputElement.setSelectionRange(1, 1);
10668 synthesizeCompositionChange(
10669 { composition:
10670 { string: "c",
10671 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10673 caret: { start: 1, length: 0 },
10675 unmaskedReference = await snapshotWindow(inputWindow, true);
10676 synthesizeCompositionChange(
10677 { composition:
10678 { string: kMask,
10679 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10681 caret: { start: 1, length: 0 },
10683 maskedReference = await snapshotWindow(inputWindow, true);
10684 checkSnapshots(unmaskedResult, unmaskedReference, true,
10685 "runPasswordMaskDelayTest(): composing character should be unmasked for a while");
10686 checkSnapshots(maskedResult, maskedReference, true,
10687 "runPasswordMaskDelayTest(): composing character should be masked after a while");
10688 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10690 // Updating composition string should unmask the composition string for a while
10691 passwordElement.value = "ab";
10692 passwordElement.focus();
10693 passwordElement.setSelectionRange(1, 1);
10694 waitForMaskingLastInput = promiseAllPasswordMasked();
10695 synthesizeCompositionChange(
10696 { composition:
10697 { string: "c",
10698 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10700 caret: { start: 1, length: 0 },
10702 await waitForMaskingLastInput;
10703 waitForMaskingLastInput = promiseAllPasswordMasked();
10704 synthesizeCompositionChange(
10705 { composition:
10706 { string: "d",
10707 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10709 caret: { start: 1, length: 0 },
10711 unmaskedResult = await snapshotWindow(passwordWindow, true);
10712 await waitForMaskingLastInput;
10713 maskedResult = await snapshotWindow(passwordWindow, true);
10714 is(getEditor(passwordElement).composing, true,
10715 "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #2");
10716 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10718 inputElement.value = `${kMask}${kMask}`;
10719 inputElement.focus();
10720 inputElement.setSelectionRange(1, 1);
10721 synthesizeCompositionChange(
10722 { composition:
10723 { string: "d",
10724 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10726 caret: { start: 1, length: 0 },
10728 unmaskedReference = await snapshotWindow(inputWindow, true);
10729 synthesizeCompositionChange(
10730 { composition:
10731 { string: kMask,
10732 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10734 caret: { start: 1, length: 0 },
10736 maskedReference = await snapshotWindow(inputWindow, true);
10737 checkSnapshots(unmaskedResult, unmaskedReference, true,
10738 "runPasswordMaskDelayTest(): updated composing character should be unmasked for a while");
10739 checkSnapshots(maskedResult, maskedReference, true,
10740 "runPasswordMaskDelayTest(): updated composing character should be masked after a while");
10741 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10743 // Composing multi-characters should be unmasked for a while.
10744 passwordElement.value = "ab";
10745 passwordElement.focus();
10746 passwordElement.setSelectionRange(1, 1);
10747 waitForMaskingLastInput = promiseAllPasswordMasked();
10748 synthesizeCompositionChange(
10749 { composition:
10750 { string: "c",
10751 clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10753 caret: { start: 1, length: 0 },
10755 await waitForMaskingLastInput;
10756 waitForMaskingLastInput = promiseAllPasswordMasked();
10757 synthesizeCompositionChange(
10758 { composition:
10759 { string: "cd",
10760 clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10762 caret: { start: 2, length: 0 },
10764 unmaskedResult = await snapshotWindow(passwordWindow, true);
10765 await waitForMaskingLastInput;
10766 maskedResult = await snapshotWindow(passwordWindow, true);
10767 is(getEditor(passwordElement).composing, true,
10768 "runPasswordMaskDelayTest(): composition shouldn't be commited at masking the composing string #3");
10769 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10771 inputElement.value = `${kMask}${kMask}`;
10772 inputElement.focus();
10773 inputElement.setSelectionRange(1, 1);
10774 synthesizeCompositionChange(
10775 { composition:
10776 { string: "cd",
10777 clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10779 caret: { start: 2, length: 0 },
10781 unmaskedReference = await snapshotWindow(inputWindow, true);
10782 synthesizeCompositionChange(
10783 { composition:
10784 { string: `${kMask}${kMask}`,
10785 clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10787 caret: { start: 2, length: 0 },
10789 maskedReference = await snapshotWindow(inputWindow, true);
10790 checkSnapshots(unmaskedResult, unmaskedReference, true,
10791 "runPasswordMaskDelayTest(): all of composing string should be unmasked for a while");
10792 checkSnapshots(maskedResult, maskedReference, true,
10793 "runPasswordMaskDelayTest(): all of composing string should be masked after a while");
10794 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10796 // Committing composition should make the commit string unmasked.
10797 passwordElement.value = "ab";
10798 passwordElement.focus();
10799 passwordElement.setSelectionRange(1, 1);
10800 waitForMaskingLastInput = promiseAllPasswordMasked();
10801 synthesizeCompositionChange(
10802 { composition:
10803 { string: "cd",
10804 clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }],
10806 caret: { start: 2, length: 0 },
10808 await waitForMaskingLastInput;
10809 waitForMaskingLastInput = promiseAllPasswordMasked();
10810 synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
10811 unmaskedResult = await snapshotWindow(passwordWindow, true);
10812 await waitForMaskingLastInput;
10813 maskedResult = await snapshotWindow(passwordWindow, true);
10815 inputElement.value = `${kMask}cd${kMask}`;
10816 inputElement.focus();
10817 inputElement.setSelectionRange(3, 3);
10818 unmaskedReference = await snapshotWindow(inputWindow, true);
10819 inputElement.value = `${kMask}${kMask}${kMask}${kMask}`;
10820 inputElement.setSelectionRange(3, 3);
10821 maskedReference = await snapshotWindow(inputWindow, true);
10822 checkSnapshots(unmaskedResult, unmaskedReference, true,
10823 "runPasswordMaskDelayTest(): committed string should be unmasked for a while");
10824 checkSnapshots(maskedResult, maskedReference, true,
10825 "runPasswordMaskDelayTest(): committed string should be masked after a while");
10828 async function runInputModeTest()
10830 let result = [];
10832 function handler(aEvent)
10834 result.push(aEvent);
10837 textarea.inputMode = "text";
10838 textarea.value = "";
10839 textarea.focus();
10841 textarea.addEventListener("compositionupdate", handler, true);
10842 textarea.addEventListener("compositionend", handler, true);
10844 synthesizeCompositionChange({
10845 composition: {string: "a ", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
10848 is(result[0].type, "compositionupdate", "Set initial composition for inputmode test");
10849 result = [];
10851 textarea.inputMode = "tel";
10852 is(result.length, 0, "No compositonend event even if inputmode is updated");
10854 // Clean up
10855 synthesizeComposition({ type: "compositioncommitasis" });
10856 textarea.inputMode = "";
10857 textarea.value = "";
10858 textarea.removeEventListener("compositionupdate", handler, true);
10859 textarea.removeEventListener("compositionend", handler, true);
10863 async function runTest()
10865 window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
10867 contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");
10868 windowOfContenteditable = document.getElementById("iframe4").contentWindow;
10869 textareaInFrame = iframe.contentDocument.getElementById("textarea");
10871 contenteditableBySpan = document.getElementById("iframe7").contentDocument.getElementById("contenteditable");
10872 windowOfContenteditableBySpan = document.getElementById("iframe7").contentWindow;
10874 await runIMEContentObserverTest();
10875 await runEditorReframeTests();
10876 await runAsyncForceCommitTest();
10877 await runRemoveContentTest();
10878 await runPanelTest();
10879 await runPasswordMaskDelayTest();
10880 await runBug1584901Test();
10881 await runInputModeTest();
10882 await runCompositionTest();
10883 await runCompositionCommitTest();
10884 await runSetSelectionEventTest();
10886 runUndoRedoTest();
10887 runCompositionCommitAsIsTest();
10888 runCompositionEventTest();
10889 runCompositionTestWhoseTextNodeModified();
10890 runQueryTextRectInContentEditableTest();
10891 runCharAtPointTest(textarea, "textarea in the document");
10892 runCharAtPointAtOutsideTest();
10893 runQueryTextContentEventTest();
10894 runQuerySelectionEventTest();
10895 runQueryIMESelectionTest();
10896 runQueryContentEventRelativeToInsertionPoint();
10897 runQueryPasswordTest();
10898 runCSSTransformTest();
10899 runBug722639Test();
10900 runBug1375825Test();
10901 runBug1530649Test();
10902 runBug1571375Test();
10903 runBug1675313Test();
10904 runCommitCompositionWithSpaceKey();
10905 runCompositionWithSelectionChange();
10906 runForceCommitTest();
10907 runNestedSettingValue();
10908 runBug811755Test();
10909 runIsComposingTest();
10910 runRedundantChangeTest();
10911 runNotRedundantChangeTest();
10912 runNativeLineBreakerTest();
10913 runControlCharTest();
10914 runFrameTest();
10915 runMaxLengthTest();
10917 window.close();
10920 window.arguments[0].SimpleTest.waitForFocus(runTest, window);
10923 </script>
10925 </window>