widget/tests/window_composition_text_querycontent.xul
author Brindusan Cristian <cbrindusan@mozilla.com>
Tue, 05 Mar 2019 22:42:15 +0200
changeset 520316 be4b780a7783cdf7d3ead37c98917dc0d2e94a1d
parent 520240 9b7d9eccb34e0593c0b13e163469c37d396ccaee
child 527288 70d5e0b1c0f874fe3845489c0a721ef143e53e6e
permissions -rw-r--r--
Backed out 5 changesets (bug 1508976, bug 1522581) for android geckoview bustages at /usr/bin/python2.7. CLOSED TREE Backed out changeset 7a6be593b0be (bug 1522581) Backed out changeset c47b37ac1775 (bug 1522581) Backed out changeset 91c31d2a7706 (bug 1508976) Backed out changeset da57df805c56 (bug 1508976) Backed out changeset 4e5d97c93515 (bug 1508976)

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                 type="text/css"?>
<window title="Testing composition, text and query content events"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  onunload="onunload();">

  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />

  <panel id="panel" hidden="true"
         orient="vertical"
         onpopupshown="onPanelShown(event);"
         onpopuphidden="onPanelHidden(event);">
    <vbox id="vbox">
      <html:textarea id="textbox" onfocus="onFocusPanelTextbox(event);"
                     cols="20" rows="4" style="font-size: 36px;"/>
    </vbox>
  </panel>

<body  xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
<textarea style="margin: 0; font-family: -moz-fixed;" id="textarea" cols="20" rows="4"></textarea><br/>
<iframe id="iframe" width="300" height="150"
        src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
<iframe id="iframe2" width="300" height="150"
        src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
<iframe id="iframe3" width="300" height="150"
        src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
<iframe id="iframe4" width="300" height="150"
        src="data:text/html,&lt;div contenteditable id='contenteditable'&gt;&lt;/div&gt;"></iframe><br/>
<input id="input" type="text"/><br/>
</p>
<div id="content" style="display: none">

</div>
<pre id="test">
</pre>
</body>

<script class="testbody" type="application/javascript">
<![CDATA[

window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window);

function ok(aCondition, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
}

function is(aLeft, aRight, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
}

function isnot(aLeft, aRight, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
}

function todo(aCondition, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.todo(aCondition, aMessage);
}

function todo_is(aLeft, aRight, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.todo_is(aLeft, aRight, aMessage);
}

function todo_isnot(aLeft, aRight, aMessage)
{
  window.opener.wrappedJSObject.SimpleTest.todo_isnot(aLeft, aRight, aMessage);
}

function isSimilarTo(aLeft, aRight, aAllowedDifference, aMessage)
{
  if (Math.abs(aLeft - aRight) <= aAllowedDifference) {
    ok(true, aMessage);
  } else {
    ok(false, aMessage + ", got=" + aLeft + ", expected=" + (aRight - aAllowedDifference) + "~" + (aRight + aAllowedDifference));
  }
}

function isGreaterThan(aLeft, aRight, aMessage)
{
  ok(aLeft > aRight, aMessage + ", got=" + aLeft + ", expected minimum value=" + aRight);
}

function finish()
{
  window.close();
}

function onunload()
{
  window.opener.wrappedJSObject.SimpleTest.finish();
}

var div = document.getElementById("div");
var textarea = document.getElementById("textarea");
var panel = document.getElementById("panel");
var textbox = document.getElementById("textbox");
var iframe = document.getElementById("iframe");
var iframe2 = document.getElementById("iframe2");
var iframe3 = document.getElementById("iframe3");
var contenteditable;
var windowOfContenteditable;
var input = document.getElementById("input");
var textareaInFrame;

const nsITextInputProcessorCallback = Ci.nsITextInputProcessorCallback;
const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
const nsIWebNavigation = Ci.nsIWebNavigation;
const nsIDocShell = Ci.nsIDocShell;

function hitEventLoop(aFunc, aTimes)
{
  if (--aTimes) {
    setTimeout(hitEventLoop, 0, aFunc, aTimes);
  } else {
    setTimeout(aFunc, 20);
  }
}

function getEditor(aNode)
{
  return aNode.editor;
}

function getHTMLEditorIMESupport(aWindow)
{
  return aWindow.docShell.editor;
}

const kIsWin = (navigator.platform.indexOf("Win") == 0);
const kIsMac = (navigator.platform.indexOf("Mac") == 0);

const kLFLen = kIsWin ? 2 : 1;
const kLF = kIsWin ? "\r\n" : "\n";

function checkQueryContentResult(aResult, aMessage)
{
  ok(aResult, aMessage + ": the result is null");
  if (!aResult) {
    return false;
  }
  ok(aResult.succeeded, aMessage + ": the query content failed");
  return aResult.succeeded;
}

function checkContent(aExpectedText, aMessage, aID)
{
  if (!aID) {
    aID = "";
  }
  var textContent = synthesizeQueryTextContent(0, 100);
  if (!checkQueryContentResult(textContent, aMessage +
                               ": synthesizeQueryTextContent " + aID)) {
    return false;
  }
  is(textContent.text, aExpectedText,
     aMessage + ": composition string is wrong " + aID);
  return textContent.text == aExpectedText;
}

function checkContentRelativeToSelection(aRelativeOffset, aLength, aExpectedOffset, aExpectedText, aMessage, aID)
{
  if (!aID) {
    aID = "";
  }
  aMessage += " (aRelativeOffset=" + aRelativeOffset + "): "
  var textContent = synthesizeQueryTextContent(aRelativeOffset, aLength, true);
  if (!checkQueryContentResult(textContent, aMessage +
                               "synthesizeQueryTextContent " + aID)) {
    return false;
  }
  is(textContent.offset, aExpectedOffset,
     aMessage + "offset is wrong " + aID);
  is(textContent.text, aExpectedText,
     aMessage + "text is wrong " + aID);
  return textContent.offset == aExpectedOffset &&
         textContent.text == aExpectedText;
}

function checkSelection(aExpectedOffset, aExpectedText, aMessage, aID)
{
  if (!aID) {
    aID = "";
  }
  var selectedText = synthesizeQuerySelectedText();
  if (!checkQueryContentResult(selectedText, aMessage +
                               ": synthesizeQuerySelectedText " + aID)) {
    return false;
  }
  is(selectedText.offset, aExpectedOffset,
     aMessage + ": selection offset is wrong " + aID);
  is(selectedText.text, aExpectedText,
     aMessage + ": selected text is wrong " + aID);
  return selectedText.offset == aExpectedOffset &&
         selectedText.text == aExpectedText;
}

function checkIMESelection(aSelectionType, aExpectedFound, aExpectedOffset, aExpectedText, aMessage, aID)
{
  if (!aID) {
    aID = "";
  }
  aMessage += " (" + aSelectionType + ")";
  var selectionType = 0;
  switch (aSelectionType) {
    case "RawClause":
      selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT;
      break;
    case "SelectedRawClause":
      selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT;
      break;
    case "ConvertedClause":
      selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT;
      break;
    case "SelectedClause":
      selectionType = QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT;
      break;
    default:
      ok(false, aMessage + ": invalid selection type, " + aSelectionType);
  }
  isnot(selectionType, 0, aMessage + ": wrong value");
  var selectedText = synthesizeQuerySelectedText(selectionType);
  if (!checkQueryContentResult(selectedText, aMessage +
                               ": synthesizeQuerySelectedText " + aID)) {
    return false;
  }
  is(selectedText.notFound, !aExpectedFound,
     aMessage + ": selection should " + (aExpectedFound ? "" : "not") + " be found " + aID);
  if (selectedText.notFound) {
    return selectedText.notFound == !aExpectedFound;
  }

  is(selectedText.offset, aExpectedOffset,
     aMessage + ": selection offset is wrong " + aID);
  is(selectedText.text, aExpectedText,
     aMessage + ": selected text is wrong " + aID);
  return selectedText.offset == aExpectedOffset &&
         selectedText.text == aExpectedText;
}

function checkRect(aRect, aExpectedRect, aMessage)
{
  is(aRect.left, aExpectedRect.left, aMessage + ": left is wrong");
  is(aRect.top, aExpectedRect.top, aMessage + " top is wrong");
  is(aRect.width, aExpectedRect.width, aMessage + ": width is wrong");
  is(aRect.height, aExpectedRect.height, aMessage + ": height is wrong");
  return aRect.left == aExpectedRect.left &&
         aRect.top == aExpectedRect.top &&
         aRect.width == aExpectedRect.width &&
         aRect.height == aExpectedRect.height;
}

function checkRectArray(aQueryTextRectArrayResult, aExpectedTextRectArray, aMessage)
{
  for (var i = 1; i < aExpectedTextRectArray.length; ++i) {
    var rect = { left: {}, top: {}, width: {}, height: {} };
    try {
      aQueryTextRectArrayResult.getCharacterRect(i, rect.left, rect.top, rect.width, rect.height);
    } catch (e) {
      ok(false, aMessage + ": failed to retrieve " + i + "th rect (" + e + ")");
      return false;
    }
    function toRect(aRect)
    {
      return { left: aRect.left.value, top: aRect.top.value, width: aRect.width.value, height: aRect.height.value };
    }
    if (!checkRect(toRect(rect), aExpectedTextRectArray[i], aMessage + " " + i + "th rect")) {
      return false;
    }
  }
  return true;
}

function checkRectContainsRect(aRect, aContainer, aMessage)
{
  var container = { left: Math.ceil(aContainer.left),
                    top:  Math.ceil(aContainer.top),
                    width: Math.floor(aContainer.width),
                    height: Math.floor(aContainer.height) };

  var ret = container.left <= aRect.left &&
            container.top <= aRect.top &&
            container.left + container.width >= aRect.left + aRect.width &&
            container.top + container.height >= aRect.top + aRect.height;
  ret = ret && aMessage;
  ok(ret, aMessage + " container={ left=" + container.left + ", top=" +
     container.top + ", width=" + container.width + ", height=" +
     container.height + " } rect={ left=" + aRect.left + ", top=" + aRect.top +
     ", width=" + aRect.width + ", height=" + aRect.height + " }");
  return ret;
}

function runUndoRedoTest()
{
  textarea.value = "";
  textarea.focus();

  // input raw characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306D",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "," },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306D\u3053",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "b" },
    });

  // convert
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u732B",
        "clauses":
        [
          { "length": 1,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: " " },
    });

  // commit
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  // input raw characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u307E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "j" },
    });

  // cancel the composition
  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });

  // input raw characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3080",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "]" },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3080\u3059",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "r" },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3080\u3059\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
      "key": { key: "/" },
    });

  // convert
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u5A18",
        "clauses":
        [
          { "length": 1,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: " " },
    });

  // commit
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  sendString(" meant");
  synthesizeKey("KEY_Backspace");
  synthesizeKey("s \"cat-girl\". She is a ");

  // input raw characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3088",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "9" },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3088\u3046",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "4" },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3088\u3046\u304b",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
      "key": { key: "t" },
    });

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3088\u3046\u304b\u3044",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 },
      "key": { key: "e" },
    });

  // convert
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u5996\u602a",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: " " },
    });

  // commit
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });

  synthesizeKey("KEY_Backspace", {repeat: 12});

  var i = 0;
  if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B\u5A18 mean",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B\u5A18 meant",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B\u5A18",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("\u732B",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  // XXX this is unexpected behavior, see bug 258291
  if (!checkContent("\u732B",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true});

  if (!checkContent("",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(0, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  // XXX this is unexpected behavior, see bug 258291
  if (!checkContent("\u732B",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(1, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(2, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 meant",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(8, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 mean",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(7, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a ",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(30, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\". She is a \u5996\u602A",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(32, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
    return;
  }

  synthesizeKey("Z", {accelKey: true, shiftKey: true});

  if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
                    "runUndoRedoTest", "#" + ++i) ||
      !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
    return;
  }
}

function checkInputEvent(aEvent, aIsComposing, aInputType, aData, aDescription) {
  if (aEvent.type != "input") {
    return;
  }
  ok(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface: ${aDescription}`);
  is(aEvent.cancelable, false, `"input" event should be never cancelable: ${aDescription}`);
  is(aEvent.bubbles, true, `"input" event should always bubble: ${aDescription}`);
  is(aEvent.isComposing, aIsComposing, `isComposing should be ${aIsComposing}: ${aDescription}`);
  is(aEvent.inputType, aInputType, `inputType should be "${aInputType}": ${aDescription}`);
  is(aEvent.data, aData, `data should be ${aData}: ${aDescription}`);
  is(aEvent.dataTransfer, null, `dataTransfer should be null: ${aDescription}`);
}

function runCompositionCommitAsIsTest()
{
  textarea.focus();

  var result = [];
  function clearResult()
  {
    result = [];
  }

  function handler(aEvent)
  {
    result.push(aEvent);
  }

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);
  textarea.addEventListener("input", handler, true);
  textarea.addEventListener("text", handler, true);

  // compositioncommitasis with composing string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");

  clearResult();
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });

  is(result.length, 3,
     "runCompositionCommitAsIsTest: 3 events should be fired after dispatching compositioncommitasis #1");
  is(result[0].type, "text",
     "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
  is(result[1].type, "compositionend",
     "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
  is(result[2].type, "input",
     "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
  checkInputEvent(result[2], false, "insertCompositionText", "\u3042",
                  "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #1");
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");

  // compositioncommitasis with committed string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "KEY_Enter", type: "keydown" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");

  clearResult();
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });

  is(result.length, 2,
     "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #2");
  is(result[0].type, "compositionend",
     "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
  is(result[1].type, "input",
     "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
  checkInputEvent(result[1], false, "insertCompositionText", "\u3042",
                  "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #2");
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");

  // compositioncommitasis with committed string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "KEY_Escape", type: "keydown" },
    });
  is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");

  clearResult();
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });

  is(result.length, 2,
     "runCompositionCommitAsIsTest: 2 events should be fired after dispatching compositioncommitasis #3");
  is(result[0].type, "compositionend",
     "runCompositionCommitAsIsTest: compositionend shouldn't be fired after dispatching compositioncommitasis #3");
  is(result[1].type, "input",
     "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
  checkInputEvent(result[1], false, "insertCompositionText", "",
                  "runCompositionCommitAsIsTest: after dispatching compositioncommitasis #3");
  is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);
  textarea.removeEventListener("input", handler, true);
  textarea.removeEventListener("text", handler, true);
}

function runCompositionCommitTest()
{
  textarea.focus();

  var result = [];
  function clearResult()
  {
    result = [];
  }

  function handler(aEvent)
  {
    result.push(aEvent);
  }

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);
  textarea.addEventListener("input", handler, true);
  textarea.addEventListener("text", handler, true);

  // compositioncommit with different composing string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a", type: "keydown" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #1");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit because it's dispatched when there is composing string #1");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #1");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
  checkInputEvent(result[3], false, "insertCompositionText", "\u3043",
                  "runCompositionCommitTest: after dispatching compositioncommit #1");
  is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");

  // compositioncommit with different committed string when there is already committed string
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "KEY_Enter", type: "keydown" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #2");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
  checkInputEvent(result[3], false, "insertCompositionText", "\u3043",
                  "runCompositionCommitTest: after dispatching compositioncommit #2");
  is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");

  // compositioncommit with empty composition string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "KEY_Enter", type: "keydown" },
    });
  is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #3");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
  checkInputEvent(result[3], false, "insertCompositionText", "\u3043",
                  "runCompositionCommitTest: after dispatching compositioncommit #3");
  is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");

  // inserting empty string with simple composition.
  textarea.value = "abc";
  textarea.setSelectionRange(3, 3);
  synthesizeComposition({ type: "compositionstart" });

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "" });

  is(result.length, 3,
     "runCompositionCommitTest: 3 events should be fired when inserting empty string with composition");
  is(result[0].type, "text",
     "runCompositionCommitTest: text should be fired when inserting empty string with composition");
  is(result[1].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired when inserting empty string with composition");
  is(result[2].type, "input",
     "runCompositionCommitTest: input should be fired when inserting empty string with composition");
  checkInputEvent(result[2], false, "insertCompositionText", "",
                  "runCompositionCommitTest: when inserting empty string with composition");
  is(textarea.value, "abc",
     "runCompositionCommitTest: textarea should keep original value when inserting empty string with composition");

  // replacing selection with empty string with simple composition.
  textarea.value = "abc";
  textarea.setSelectionRange(0, 3);
  synthesizeComposition({ type: "compositionstart" });

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "" });

  is(result.length, 3,
     "runCompositionCommitTest: 3 events should be fired when replacing with empty string with composition");
  is(result[0].type, "text",
     "runCompositionCommitTest: text should be fired when replacing with empty string with composition");
  is(result[1].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired when replacing with empty string with composition");
  is(result[2].type, "input",
     "runCompositionCommitTest: input should be fired when replacing with empty string with composition");
  checkInputEvent(result[2], false, "insertCompositionText", "",
                  "runCompositionCommitTest: when replacing with empty string with composition");
  is(textarea.value, "",
     "runCompositionCommitTest: textarea should become empty when replacing selection with empty string with composition");

  // replacing selection with same string with simple composition.
  textarea.value = "abc";
  textarea.setSelectionRange(0, 3);
  synthesizeComposition({ type: "compositionstart" });

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "abc" });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired when replacing selection with same string with composition");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired when replacing selection with same string with composition");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired when replacing selection with same string with composition");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired when replacing selection with same string with composition");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired when replacing selection with same string with composition");
  checkInputEvent(result[3], false, "insertCompositionText", "abc",
                  "runCompositionCommitTest: when replacing selection with same string with composition");
  is(textarea.value, "abc",
     "runCompositionCommitTest: textarea should keep same value when replacing selection with same string with composition");

  // compositioncommit with non-empty composition string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #4");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
  checkInputEvent(result[3], false, "insertCompositionText", "",
                  "runCompositionCommitTest: after dispatching compositioncommit #4");
  is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");

  // compositioncommit immediately without compositionstart
  textarea.value = "";

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });

  is(result.length, 4,
     "runCompositionCommitTest: 4 events should be fired after dispatching compositioncommit #5");
  is(result[0].type, "compositionupdate",
     "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
  is(result[1].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
  is(result[2].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
  is(result[3].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
  checkInputEvent(result[3], false, "insertCompositionText", "\u3042",
                  "runCompositionCommitTest: after dispatching compositioncommit #5");
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");

  // compositioncommit with same composition string.
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });

  is(result.length, 3,
     "runCompositionCommitTest: 3 events should be fired after dispatching compositioncommit #6");
  is(result[0].type, "text",
     "runCompositionCommitTest: text should be fired after dispatching compositioncommit #6");
  is(result[1].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
  is(result[2].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
  checkInputEvent(result[2], false, "insertCompositionText", "\u3042",
                  "runCompositionCommitTest: after dispatching compositioncommit #6");
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");

  // compositioncommit with same composition string when there is committed string
  textarea.value = "";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "KEY_Enter", type: "keydown" },
    });
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });

  is(result.length, 2,
     "runCompositionCommitTest: 2 events should be fired after dispatching compositioncommit #7");
  is(result[0].type, "compositionend",
     "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #7");
  is(result[1].type, "input",
     "runCompositionCommitTest: input should be fired after dispatching compositioncommit #7");
  checkInputEvent(result[1], false, "insertCompositionText", "\u3042",
                  "runCompositionCommitTest: after dispatching compositioncommit #7");
  is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);
  textarea.removeEventListener("input", handler, true);
  textarea.removeEventListener("text", handler, true);
}

function runCompositionTest()
{
  textarea.value = "";
  textarea.focus();
  var caretRects = [];

  var caretRect = synthesizeQueryCaretRect(0);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #0")) {
    return false;
  }
  caretRects[0] = caretRect;

  // input first character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "o" },
    });

  if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
      !checkSelection(1, "", "runCompositionTest", "#1-1")) {
    return;
  }

  caretRect = synthesizeQueryCaretRect(1);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #1-1")) {
    return false;
  }
  caretRects[1] = caretRect;

  // input second character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
    });

  if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
      !checkSelection(2, "", "runCompositionTest", "#1-2")) {
    return;
  }

  caretRect = synthesizeQueryCaretRect(2);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #1-2")) {
    return false;
  }
  caretRects[2] = caretRect;

  isnot(caretRects[2].left, caretRects[1].left,
        "runCompositionTest: caret isn't moved (#1-2)");
  is(caretRects[2].top, caretRects[1].top,
     "runCompositionTest: caret is moved to another line (#1-2)");
  is(caretRects[2].width, caretRects[1].width,
     "runCompositionTest: caret width is wrong (#1-2)");
  is(caretRects[2].height, caretRects[1].height,
     "runCompositionTest: caret width is wrong (#1-2)");

  // input third character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
      "key": { key: "/" },
    });

  if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
      !checkSelection(3, "", "runCompositionTest", "#1-3")) {
    return;
  }

  caretRect = synthesizeQueryCaretRect(3);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #1-3")) {
    return false;
  }
  caretRects[3] = caretRect;

  isnot(caretRects[3].left, caretRects[2].left,
        "runCompositionTest: caret isn't moved (#1-3)");
  is(caretRects[3].top, caretRects[2].top,
     "runCompositionTest: caret is moved to another line (#1-3)");
  is(caretRects[3].width, caretRects[2].width,
     "runCompositionTest: caret width is wrong (#1-3)");
  is(caretRects[3].height, caretRects[2].height,
     "runCompositionTest: caret height is wrong (#1-3)");

  // moves the caret left
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "KEY_ArrowLeft" },
    });

  if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
      !checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
    return;
  }


  caretRect = synthesizeQueryCaretRect(2);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #1-3-1")) {
    return false;
  }

  is(caretRect.left, caretRects[2].left,
     "runCompositionTest: caret rects are different (#1-3-1, left)");
  is(caretRect.top, caretRects[2].top,
     "runCompositionTest: caret rects are different (#1-3-1, top)");
  // by bug 335359, the caret width depends on the right side's character.
  is(caretRect.width, caretRects[2].width + 1,
     "runCompositionTest: caret rects are different (#1-3-1, width)");
  is(caretRect.height, caretRects[2].height,
     "runCompositionTest: caret rects are different (#1-3-1, height)");

  // moves the caret left
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "KEY_ArrowLeft" },
    });

  if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
      !checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
    return;
  }


  caretRect = synthesizeQueryCaretRect(1);
  if (!checkQueryContentResult(caretRect,
        "runCompositionTest: synthesizeQueryCaretRect #1-3-2")) {
    return false;
  }

  is(caretRect.left, caretRects[1].left,
     "runCompositionTest: caret rects are different (#1-3-2, left)");
  is(caretRect.top, caretRects[1].top,
     "runCompositionTest: caret rects are different (#1-3-2, top)");
  // by bug 335359, the caret width depends on the right side's character.
  is(caretRect.width, caretRects[1].width + 1,
     "runCompositionTest: caret rects are different (#1-3-2, width)");
  is(caretRect.height, caretRects[1].height,
     "runCompositionTest: caret rects are different (#1-3-2, height)");

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 },
      "key": { key: "y" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
      !checkSelection(4, "", "runCompositionTest", "#1-4")) {
    return;
  }


  // backspace
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
      "key": { key: "KEY_Backspace" },
    });

  if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
      !checkSelection(3, "", "runCompositionTest", "#1-5")) {
    return;
  }

  // re-input
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 },
      "key": { key: "y" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
      !checkSelection(4, "", "runCompositionTest", "#1-6")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055",
        "clauses":
        [
          { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 },
      "key": { key: "x" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
      !checkSelection(5, "", "runCompositionTest", "#1-7")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
        "clauses":
        [
          { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 6, "length": 0 },
      "key": { key: "e" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
      !checkSelection(6, "", "runCompositionTest", "#1-8")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
        "clauses":
        [
          { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 7, "length": 0 },
      "key": { key: "b" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
      !checkSelection(7, "", "runCompositionTest", "#1-8")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
        "clauses":
        [
          { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 8, "length": 0 },
      "key": { key: "4" },
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
                    "runCompositionTest", "#1-9") ||
      !checkSelection(8, "", "runCompositionTest", "#1-9")) {
    return;
  }

  // convert
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
        "clauses":
        [
          { "length": 4,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 2,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 },
      "key": { key: " " },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
                    "runCompositionTest", "#1-10") ||
      !checkSelection(4, "", "runCompositionTest", "#1-10")) {
    return;
  }

  // change the selected clause
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
        "clauses":
        [
          { "length": 4,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": 2,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 6, "length": 0 },
      "key": { key: "KEY_ArrowLeft", shiftKey: true },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
                    "runCompositionTest", "#1-11") ||
      !checkSelection(6, "", "runCompositionTest", "#1-11")) {
    return;
  }

  // reset clauses
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
        "clauses":
        [
          { "length": 5,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 3,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 },
      "key": { key: "KEY_ArrowRight" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
                    "runCompositionTest", "#1-12") ||
      !checkSelection(5, "", "runCompositionTest", "#1-12")) {
    return;
  }


  var textRect1 = synthesizeQueryTextRect(0, 1);
  var textRect2 = synthesizeQueryTextRect(1, 1);
  if (!checkQueryContentResult(textRect1,
        "runCompositionTest: synthesizeQueryTextRect #1-12-1") ||
      !checkQueryContentResult(textRect2,
        "runCompositionTest: synthesizeQueryTextRect #1-12-2")) {
    return false;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommitasis" });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
                    "runCompositionTest", "#1-13") ||
      !checkSelection(8, "", "runCompositionTest", "#1-13")) {
    return;
  }

  var textRect3 = synthesizeQueryTextRect(0, 1);
  var textRect4 = synthesizeQueryTextRect(1, 1);

  if (!checkQueryContentResult(textRect3,
        "runCompositionTest: synthesizeQueryTextRect #1-13-1") ||
      !checkQueryContentResult(textRect4,
        "runCompositionTest: synthesizeQueryTextRect #1-13-2")) {
    return false;
  }

  checkRect(textRect3, textRect1, "runCompositionTest: textRect #1-13-1");
  checkRect(textRect4, textRect2, "runCompositionTest: textRect #1-13-2");

  // restart composition and input characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3057",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "d" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
                    "runCompositionTest", "#2-1") ||
      !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3058",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "r" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
                    "runCompositionTest", "#2-2") ||
      !checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3058\u3087",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
                    "runCompositionTest", "#2-3") ||
      !checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3058\u3087\u3046",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
      "key": { key: "4" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
                    "runCompositionTest", "#2-4") ||
      !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
    return;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
                    "runCompositionTest", "#2-4") ||
      !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
    return;
  }

  // set selection
  var selectionSetTest = synthesizeSelectionSet(4, 7, false);
  ok(selectionSetTest, "runCompositionTest: selectionSetTest failed");

  if (!checkSelection(4, "\u3055\u884C\u3053\u3046\u3058\u3087\u3046", "runCompositionTest", "#3-1")) {
    return;
  }

  // start composition with selection
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u304A",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "6" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
                    "runCompositionTest", "#3-2") ||
      !checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
    return;
  }

  // remove the composition string
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "KEY_Backspace" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#3-3") ||
      !checkSelection(4, "", "runCompositionTest", "#3-3")) {
    return;
  }

  // re-input the composition string
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3046",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "4" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
                    "runCompositionTest", "#3-4") ||
      !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
    return;
  }

  // cancel the composition
  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#3-5") ||
      !checkSelection(4, "", "runCompositionTest", "#3-5")) {
    return;
  }

  // bug 271815, some Chinese IMEs for Linux make empty composition string
  // and compty clause information when it lists up Chinese characters on
  // its candidate window.
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "a" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#4-1") ||
      !checkSelection(4, "", "runCompositionTest", "#4-1")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "b" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#4-2") ||
      !checkSelection(4, "", "runCompositionTest", "#4-2")) {
    return;
  }

  synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#4-3") ||
      !checkSelection(5, "", "runCompositionTest", "#4-3")) {
    return;
  }

  // testing the canceling case
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "a" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#4-5") ||
      !checkSelection(5, "", "runCompositionTest", "#4-5")) {
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#4-6") ||
      !checkSelection(5, "", "runCompositionTest", "#4-6")) {
    return;
  }

  // testing whether the empty composition string deletes selected string.
  synthesizeKey("KEY_ArrowLeft", {shiftKey: true});

  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "a" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#4-8") ||
      !checkSelection(4, "", "runCompositionTest", "#4-8")) {
    return;
  }

  synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
                    "runCompositionTest", "#4-9") ||
      !checkSelection(5, "", "runCompositionTest", "#4-9")) {
    return;
  }

  synthesizeKey("KEY_Backspace");
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#4-11") ||
      !checkSelection(4, "", "runCompositionTest", "#4-11")) {
    return;
  }

  // bug 23558, ancient Japanese IMEs on Window may send empty text event
  // twice at canceling composition.
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u6700",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#5-1") ||
      !checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
      "caret": { "start": 0, "length": 0 },
      "key": { key: "KEY_Backspace", type: "keydown" },
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#5-2") ||
      !checkSelection(4, "", "runCompositionTest", "#5-2")) {
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#5-3") ||
      !checkSelection(4, "", "runCompositionTest", "#5-3")) {
    return;
  }

  // Undo tests for the testcases for bug 23558 and bug 271815
  synthesizeKey("z", { accelKey: true });

  // XXX this is unexpected behavior, see bug 258291
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#6-1") ||
      !checkSelection(4, "", "runCompositionTest", "#6-1")) {
    return;
  }

  synthesizeKey("z", { accelKey: true });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
                    "runCompositionTest", "#6-2") ||
      !checkSelection(5, "", "runCompositionTest", "#6-2")) {
    return;
  }

  synthesizeKey("z", { accelKey: true });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#6-3") ||
      !checkSelection(4, "\u6700", "runCompositionTest", "#6-3")) {
    return;
  }

  synthesizeKey("z", { accelKey: true });

  // XXX this is unexpected behavior, see bug 258291
  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                    "runCompositionTest", "#6-4") ||
      !checkSelection(5, "", "runCompositionTest", "#6-4")) {
    return;
  }

  synthesizeKey("z", { accelKey: true });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                    "runCompositionTest", "#6-5") ||
      !checkSelection(4, "", "runCompositionTest", "#6-5")) {
    return;
  }
}

function runCompositionEventTest()
{
  const kDescription = "runCompositionEventTest: ";
  const kEvents = ["compositionstart", "compositionupdate", "compositionend",
                   "input"];

  input.value = "";
  input.focus();

  var windowEventCounts = [], windowEventData = [], windowEventLocale = [];
  var inputEventCounts = [], inputEventData = [], inputEventLocale = [];
  var preventDefault = false;
  var stopPropagation = false;

  function initResults()
  {
    for (var i = 0; i < kEvents.length; i++) {
      windowEventCounts[kEvents[i]] = 0;
      windowEventData[kEvents[i]] = "";
      windowEventLocale[kEvents[i]] = "";
      inputEventCounts[kEvents[i]] = 0;
      inputEventData[kEvents[i]] = "";
      inputEventLocale[kEvents[i]] = "";
    }
  }

  function compositionEventHandlerForWindow(aEvent)
  {
    windowEventCounts[aEvent.type]++;
    windowEventData[aEvent.type] = aEvent.data;
    windowEventLocale[aEvent.type] = aEvent.locale;
    if (preventDefault) {
      aEvent.preventDefault();
    }
    if (stopPropagation) {
      aEvent.stopPropagation();
    }
  }

  function formEventHandlerForWindow(aEvent)
  {
    ok(aEvent.isTrusted, "input events must be trusted events");
    windowEventCounts[aEvent.type]++;
    windowEventData[aEvent.type] = input.value;
  }

  function compositionEventHandlerForInput(aEvent)
  {
    inputEventCounts[aEvent.type]++;
    inputEventData[aEvent.type] = aEvent.data;
    inputEventLocale[aEvent.type] = aEvent.locale;
    if (preventDefault) {
      aEvent.preventDefault();
    }
    if (stopPropagation) {
      aEvent.stopPropagation();
    }
  }

  function formEventHandlerForInput(aEvent)
  {
    inputEventCounts[aEvent.type]++;
    inputEventData[aEvent.type] = input.value;
  }

  window.addEventListener("compositionstart", compositionEventHandlerForWindow,
                          true, true);
  window.addEventListener("compositionend", compositionEventHandlerForWindow,
                          true, true);
  window.addEventListener("compositionupdate", compositionEventHandlerForWindow,
                          true, true);
  window.addEventListener("input", formEventHandlerForWindow,
                          true, true);

  input.addEventListener("compositionstart", compositionEventHandlerForInput,
                         true, true);
  input.addEventListener("compositionend", compositionEventHandlerForInput,
                         true, true);
  input.addEventListener("compositionupdate", compositionEventHandlerForInput,
                         true, true);
  input.addEventListener("input", formEventHandlerForInput,
                         true, true);

  // test for normal case
  initResults();

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "o" },
    });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by window #1");
  is(windowEventData["compositionstart"], "",
     kDescription + "data of compositionstart isn't empty (window) #1");
  is(windowEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (window) #1");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by input #1");
  is(inputEventData["compositionstart"], "",
     kDescription + "data of compositionstart isn't empty (input) #1");
  is(inputEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (input) #1");

  is(windowEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by window #1");
  is(windowEventData["compositionupdate"], "\u3089",
     kDescription + "data of compositionupdate doesn't match (window) #1");
  is(windowEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (window) #1");
  is(inputEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by input #1");
  is(inputEventData["compositionupdate"], "\u3089",
     kDescription + "data of compositionupdate doesn't match (input) #1");
  is(inputEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (input) #1");

  is(windowEventCounts["compositionend"], 0,
     kDescription + "compositionend has been handled by window #1");
  is(inputEventCounts["compositionend"], 0,
     kDescription + "compositionend has been handled by input #1");

  is(windowEventCounts["input"], 1,
     kDescription + "input hasn't been handled by window #1");
  is(windowEventData["input"], "\u3089",
     kDescription + "value of input element wasn't modified (window) #1");
  is(inputEventCounts["input"], 1,
     kDescription + "input hasn't been handled by input #1");
  is(inputEventData["input"], "\u3089",
     kDescription + "value of input element wasn't modified (input) #1");

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
      "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
    });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart has been handled more than once by window #2");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart has been handled more than once by input #2");

  is(windowEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate hasn't been handled by window #2");
  is(windowEventData["compositionupdate"], "\u3089\u30FC",
     kDescription + "data of compositionupdate doesn't match (window) #2");
  is(windowEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (window) #2");
  is(inputEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate hasn't been handled by input #2");
  is(inputEventData["compositionupdate"], "\u3089\u30FC",
     kDescription + "data of compositionupdate doesn't match (input) #2");
  is(inputEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (input) #2");

  is(windowEventCounts["compositionend"], 0,
     kDescription + "compositionend has been handled during composition by window #2");
  is(inputEventCounts["compositionend"], 0,
     kDescription + "compositionend has been handled during composition by input #2");

  is(windowEventCounts["input"], 2,
     kDescription + "input hasn't been handled by window #2");
  is(windowEventData["input"], "\u3089\u30FC",
     kDescription + "value of input element wasn't modified (window) #2");
  is(inputEventCounts["input"], 2,
     kDescription + "input hasn't been handled by input #2");
  is(inputEventData["input"], "\u3089\u30FC",
     kDescription + "value of input element wasn't modified (input) #2");

  // text event shouldn't cause composition update, e.g., at committing.
  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart has been handled more than once by window #3");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart has been handled more than once by input #3");

  is(windowEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate has been fired unexpectedly on window #3");
  is(inputEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate has been fired unexpectedly on input #3");

  is(windowEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by window #3");
  is(windowEventData["compositionend"], "\u3089\u30FC",
     kDescription + "data of compositionend doesn't match (window) #3");
  is(windowEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (window) #3");
  is(inputEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by input #3");
  is(inputEventData["compositionend"], "\u3089\u30FC",
     kDescription + "data of compositionend doesn't match (input) #3");
  is(inputEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (input) #3");

  is(windowEventCounts["input"], 3,
     kDescription + "input hasn't been handled by window #3");
  is(windowEventData["input"], "\u3089\u30FC",
     kDescription + "value of input element wasn't modified (window) #3");
  is(inputEventCounts["input"], 3,
     kDescription + "input hasn't been handled by input #3");
  is(inputEventData["input"], "\u3089\u30FC",
     kDescription + "value of input element wasn't modified (input) #3");

  // select the second character, then, data of composition start should be
  // the selected character.
  initResults();
  synthesizeKey("KEY_ArrowLeft", {shiftKey: true});

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "o" },
    });

  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by window #4");
  is(windowEventData["compositionstart"], "\u30FC",
     kDescription + "data of compositionstart is empty (window) #4");
  is(windowEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (window) #4");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by input #4");
  is(inputEventData["compositionstart"], "\u30FC",
     kDescription + "data of compositionstart is empty (input) #4");
  is(inputEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (input) #4");

  is(windowEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by window #4");
  is(windowEventData["compositionupdate"], "\u3089",
     kDescription + "data of compositionupdate doesn't match (window) #4");
  is(windowEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (window) #4");
  is(inputEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by input #4");
  is(inputEventData["compositionupdate"], "\u3089",
     kDescription + "data of compositionupdate doesn't match (input) #4");
  is(inputEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (input) #4");

  is(windowEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by window #4");
  is(windowEventData["compositionend"], "\u3089",
     kDescription + "data of compositionend doesn't match (window) #4");
  is(windowEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (window) #4");
  is(inputEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by input #4");
  is(inputEventData["compositionend"], "\u3089",
     kDescription + "data of compositionend doesn't match (input) #4");
  is(inputEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (input) #4");

  is(windowEventCounts["input"], 2,
     kDescription + "input hasn't been handled by window #4");
  is(windowEventData["input"], "\u3089\u3089",
     kDescription + "value of input element wasn't modified (window) #4");
  is(inputEventCounts["input"], 2,
     kDescription + "input hasn't been handled by input #4");
  is(inputEventData["input"], "\u3089\u3089",
     kDescription + "value of input element wasn't modified (input) #4");

  // preventDefault() should effect nothing.
  preventDefault = true;

  initResults();
  synthesizeKey("a", { accelKey: true }); // Select All

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306D",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "," },
    });

  synthesizeComposition({ type: "compositioncommitasis" });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by window #5");
  is(windowEventData["compositionstart"], "\u3089\u3089",
     kDescription + "data of compositionstart is empty (window) #5");
  is(windowEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (window) #5");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by input #5");
  is(inputEventData["compositionstart"], "\u3089\u3089",
     kDescription + "data of compositionstart is empty (input) #5");
  is(inputEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty (input) #5");

  is(windowEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by window #5");
  is(windowEventData["compositionupdate"], "\u306D",
     kDescription + "data of compositionupdate doesn't match (window) #5");
  is(windowEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (window) #5");
  is(inputEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by input #5");
  is(inputEventData["compositionupdate"], "\u306D",
     kDescription + "data of compositionupdate doesn't match (input) #5");
  is(inputEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty (input) #5");

  is(windowEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by window #5");
  is(windowEventData["compositionend"], "\u306D",
     kDescription + "data of compositionend doesn't match (window) #5");
  is(windowEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (window) #5");
  is(inputEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by input #5");
  is(inputEventData["compositionend"], "\u306D",
     kDescription + "data of compositionend doesn't match (input) #5");
  is(inputEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty (input) #5");

  is(windowEventCounts["input"], 2,
     kDescription + "input hasn't been handled by window #5");
  is(windowEventData["input"], "\u306D",
     kDescription + "value of input element wasn't modified (window) #5");
  is(inputEventCounts["input"], 2,
     kDescription + "input hasn't been handled by input #5");
  is(inputEventData["input"], "\u306D",
     kDescription + "value of input element wasn't modified (input) #5");

  prevnetDefault = false;

  // stopPropagation() should effect nothing (except event count)
  stopPropagation = true;

  initResults();
  synthesizeKey("a", { accelKey: true }); // Select All

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
    });

  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by window #6");
  is(windowEventData["compositionstart"], "\u306D",
     kDescription + "data of compositionstart is empty #6");
  is(windowEventLocale["compositionstart"], "",
     kDescription + "locale of compositionstart isn't empty #6");
  is(inputEventCounts["compositionstart"], 0,
     kDescription + "compositionstart has been handled by input #6");

  is(windowEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by window #6");
  is(windowEventData["compositionupdate"], "\u306E",
     kDescription + "data of compositionupdate doesn't match #6");
  is(windowEventLocale["compositionupdate"], "",
     kDescription + "locale of compositionupdate isn't empty #6");
  is(inputEventCounts["compositionupdate"], 0,
     kDescription + "compositionupdate has been handled by input #6");

  is(windowEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by window #6");
  is(windowEventData["compositionend"], "\u306E",
     kDescription + "data of compositionend doesn't match #6");
  is(windowEventLocale["compositionend"], "",
     kDescription + "locale of compositionend isn't empty #6");
  is(inputEventCounts["compositionend"], 0,
     kDescription + "compositionend has been handled by input #6");

  is(windowEventCounts["input"], 2,
     kDescription + "input hasn't been handled by window #6");
  is(windowEventData["input"], "\u306E",
     kDescription + "value of input element wasn't modified (window) #6");
  is(inputEventCounts["input"], 2,
     kDescription + "input hasn't been handled by input #6");
  is(inputEventData["input"], "\u306E",
     kDescription + "value of input element wasn't modified (input) #6");

  stopPropagation = false;

  // create event and dispatch it.
  initResults();

  input.value = "value of input";
  synthesizeKey("a", { accelKey: true }); // Select All

  var compositionstart = document.createEvent("CompositionEvent");
  compositionstart.initCompositionEvent("compositionstart",
                                        true, true, document.defaultView,
                                        "start data", "start locale");
  is(compositionstart.type, "compositionstart",
     kDescription + "type doesn't match #7");
  is(compositionstart.data, "start data",
     kDescription + "data doesn't match #7");
  is(compositionstart.locale, "start locale",
     kDescription + "locale doesn't match #7");
  is(compositionstart.detail, 0,
     kDescription + "detail isn't 0 #7");

  input.dispatchEvent(compositionstart);

  is(windowEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by window #7");
  is(windowEventData["compositionstart"], "start data",
     kDescription + "data of compositionstart was changed (window) #7");
  is(windowEventLocale["compositionstart"], "start locale",
     kDescription + "locale of compositionstart was changed (window) #7");
  is(inputEventCounts["compositionstart"], 1,
     kDescription + "compositionstart hasn't been handled by input #7");
  is(inputEventData["compositionstart"], "start data",
     kDescription + "data of compositionstart was changed (input) #7");
  is(inputEventLocale["compositionstart"], "start locale",
     kDescription + "locale of compositionstart was changed (input) #7");

  is(input.value, "value of input",
     kDescription + "input value was changed #7");

  var compositionupdate1 = document.createEvent("compositionevent");
  compositionupdate1.initCompositionEvent("compositionupdate",
                                          true, false, document.defaultView,
                                          "composing string", "composing locale");
  is(compositionupdate1.type, "compositionupdate",
     kDescription + "type doesn't match #8");
  is(compositionupdate1.data, "composing string",
     kDescription + "data doesn't match #8");
  is(compositionupdate1.locale, "composing locale",
     kDescription + "locale doesn't match #8");
  is(compositionupdate1.detail, 0,
     kDescription + "detail isn't 0 #8");

  input.dispatchEvent(compositionupdate1);

  is(windowEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by window #8");
  is(windowEventData["compositionupdate"], "composing string",
     kDescription + "data of compositionupdate was changed (window) #8");
  is(windowEventLocale["compositionupdate"], "composing locale",
     kDescription + "locale of compositionupdate was changed (window) #8");
  is(inputEventCounts["compositionupdate"], 1,
     kDescription + "compositionupdate hasn't been handled by input #8");
  is(inputEventData["compositionupdate"], "composing string",
     kDescription + "data of compositionupdate was changed (input) #8");
  is(inputEventLocale["compositionupdate"], "composing locale",
     kDescription + "locale of compositionupdate was changed (input) #8");

  is(input.value, "value of input",
     kDescription + "input value was changed #8");

  var compositionupdate2 = document.createEvent("compositionEvent");
  compositionupdate2.initCompositionEvent("compositionupdate",
                                          true, false, document.defaultView,
                                          "commit string", "commit locale");
  is(compositionupdate2.type, "compositionupdate",
     kDescription + "type doesn't match #9");
  is(compositionupdate2.data, "commit string",
     kDescription + "data doesn't match #9");
  is(compositionupdate2.locale, "commit locale",
     kDescription + "locale doesn't match #9");
  is(compositionupdate2.detail, 0,
     kDescription + "detail isn't 0 #9");

  input.dispatchEvent(compositionupdate2);

  is(windowEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate hasn't been handled by window #9");
  is(windowEventData["compositionupdate"], "commit string",
     kDescription + "data of compositionupdate was changed (window) #9");
  is(windowEventLocale["compositionupdate"], "commit locale",
     kDescription + "locale of compositionupdate was changed (window) #9");
  is(inputEventCounts["compositionupdate"], 2,
     kDescription + "compositionupdate hasn't been handled by input #9");
  is(inputEventData["compositionupdate"], "commit string",
     kDescription + "data of compositionupdate was changed (input) #9");
  is(inputEventLocale["compositionupdate"], "commit locale",
     kDescription + "locale of compositionupdate was changed (input) #9");

  is(input.value, "value of input",
     kDescription + "input value was changed #9");

  var compositionend = document.createEvent("Compositionevent");
  compositionend.initCompositionEvent("compositionend",
                                      true, false, document.defaultView,
                                      "end data", "end locale");
  is(compositionend.type, "compositionend",
     kDescription + "type doesn't match #10");
  is(compositionend.data, "end data",
     kDescription + "data doesn't match #10");
  is(compositionend.locale, "end locale",
     kDescription + "locale doesn't match #10");
  is(compositionend.detail, 0,
     kDescription + "detail isn't 0 #10");

  input.dispatchEvent(compositionend);

  is(windowEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by window #10");
  is(windowEventData["compositionend"], "end data",
     kDescription + "data of compositionend was changed (window) #10");
  is(windowEventLocale["compositionend"], "end locale",
     kDescription + "locale of compositionend was changed (window) #10");
  is(inputEventCounts["compositionend"], 1,
     kDescription + "compositionend hasn't been handled by input #10");
  is(inputEventData["compositionend"], "end data",
     kDescription + "data of compositionend was changed (input) #10");
  is(inputEventLocale["compositionend"], "end locale",
     kDescription + "locale of compositionend was changed (input) #10");

  is(input.value, "value of input",
     kDescription + "input value was changed #10");

  window.removeEventListener("compositionstart",
                             compositionEventHandlerForWindow, true);
  window.removeEventListener("compositionend",
                             compositionEventHandlerForWindow, true);
  window.removeEventListener("compositionupdate",
                             compositionEventHandlerForWindow, true);
  window.removeEventListener("input",
                             formEventHandlerForWindow, true);

  input.removeEventListener("compositionstart",
                            compositionEventHandlerForInput, true);
  input.removeEventListener("compositionend",
                            compositionEventHandlerForInput, true);
  input.removeEventListener("compositionupdate",
                            compositionEventHandlerForInput, true);
  input.removeEventListener("input",
                            formEventHandlerForInput, true);
}

function runQueryTextRectInContentEditableTest()
{
  contenteditable.focus();

  contenteditable.innerHTML = "<p>abc</p><p>def</p>";
                      // \n    0  123    4  567
                      // \r\n  01 234    56 789

  var description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "a"
  var a = synthesizeQueryTextRect(kLFLen, 1);
  if (!checkQueryContentResult(a, description + "rect for 'a'")) {
    return;
  }

  // "b"
  var b = synthesizeQueryTextRect(kLFLen + 1, 1);
  if (!checkQueryContentResult(b, description + "rect for 'b'")) {
    return;
  }

  is(b.top, a.top, description + "'a' and 'b' should be at same top");
  isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
  is(b.height, a.height, description + "'a' and 'b' should be same height");

  // "c"
  var c = synthesizeQueryTextRect(kLFLen + 2, 1);
  if (!checkQueryContentResult(c, description + "rect for 'c'")) {
    return;
  }

  is(c.top, b.top, description + "'b' and 'c' should be at same top");
  isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
  is(c.height, b.height, description + "'b' and 'c' should be same height");

  // "abc" as array
  var abcAsArray = synthesizeQueryTextRectArray(kLFLen, 3);
  if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
      !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
    return;
  }

  // 2nd <p> (can be computed with the rect of 'c')
  var p2 = synthesizeQueryTextRect(kLFLen + 3, 1);
  if (!checkQueryContentResult(p2, description + "rect for 2nd <p>")) {
    return;
  }

  is(p2.top, c.top, description + "'c' and a line breaker caused by 2nd <p> should be at same top");
  isSimilarTo(p2.left, c.left + c.width, 2, description + "left of a line breaker caused by 2nd <p> should be at similar to right of 'c'");
  is(p2.height, c.height, description + "'c' and a line breaker caused by 2nd <p> should be same height");

  // 2nd <p> as array
  var p2AsArray = synthesizeQueryTextRectArray(kLFLen + 3, 1);
  if (!checkQueryContentResult(p2AsArray, description + "2nd <p>'s line breaker as array") ||
      !checkRectArray(p2AsArray, [p2], description + "query text rect array result of 2nd <p> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var p2_2 = synthesizeQueryTextRect(kLFLen + 4, 1);
    if (!checkQueryContentResult(p2_2, description + "rect for \\n of \\r\\n caused by 2nd <p>")) {
      return;
    }

    is(p2_2.top, p2.top, description + "'\\r' and '\\n' should be at same top");
    is(p2_2.left, p2.left, description + "'\\r' and '\\n' should be at same top");
    is(p2_2.height, p2.height, description + "'\\r' and '\\n' should be same height");
    is(p2_2.width, p2.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var p2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 4, 1);
    if (!checkQueryContentResult(p2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <p>") ||
        !checkRectArray(p2_2AsArray, [p2_2], description + "query text rect array result of \\n of \\r\\n caused by 2nd <p> should match with each query text rect result")) {
      return;
    }
  }

  // "d"
  var d = synthesizeQueryTextRect(kLFLen * 2 + 3, 1);
  if (!checkQueryContentResult(d, description + "rect for 'd'")) {
    return;
  }

  isGreaterThan(d.top, a.top + a.height, description + "top of 'd' should be greater than bottom of 'a'");
  is(d.left, a.left, description + "'a' and 'd' should be same at same left");
  is(d.height, a.height, description + "'a' and 'd' should be same height");

  // "e"
  var e = synthesizeQueryTextRect(kLFLen * 2 + 4, 1);
  if (!checkQueryContentResult(e, description + "rect for 'e'")) {
    return;
  }

  is(e.top, d.top, description + "'d' and 'd' should be at same top");
  isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
  is(e.height, d.height, description + "'d' and 'e' should be same height");

  // "f"
  var f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
  is(f.height, e.height, description + "'e' and 'f' should be same height");

  // "def" as array
  var defAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 3, 3);
  if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
      !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
    return;
  }

  // next of "f" (can be computed with rect of 'f')
  var next_f = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
    return;
  }

  is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
  isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
  is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");

  // next of "f" as array
  var next_fAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
      !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  var tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
  is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
  is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
  is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");

  // too big offset for the editors as array
  var tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }

  contenteditable.innerHTML = "<p>abc</p><p>def</p><p><br></p>";
                      // \n    0  123    4  567    8  9
                      // \r\n  01 234    56 789    01 23

  description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "f"
  f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  is(f.height, e.height, description + "'e' and 'f' should be same height");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");

  // 3rd <p> (can be computed with rect of 'f')
  var p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
    return;
  }

  is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
  is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
  isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");

  // 3rd <p> as array
  var p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
      !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
      return;
    }

    is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
    is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
    is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
    is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
        !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
      return;
    }
  }

  // <br> in 3rd <p>
  var br = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(br, description + "rect for <br> in 3rd <p>")) {
    return;
  }

  isGreaterThan(br.top, d.top + d.height, description + "a line breaker caused by <br> in 3rd <p> should be greater than bottom of 'd'");
  isSimilarTo(br.height, d.height, 2, description + "'d' and a line breaker caused by <br> in 3rd <p> should be similar height");
  is(br.left, d.left, description + "left of a line breaker caused by <br> in 3rd <p> should be same left of 'd'");

  // <br> in 3rd <p> as array
  var brAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(brAsArray, description + "<br> in 3rd <p> as array") ||
      !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> in 3rd <p> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var br_2 = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
    if (!checkQueryContentResult(br_2, description + "rect for \\n of \\r\\n caused by <br> in 3rd <p>")) {
      return;
    }

    is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
    is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
    is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
    is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var br_2AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
    if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br> in 3rd <p>") ||
        !checkRectArray(br_2AsArray, [br_2], description + "query text rect array result of \\n of \\r\\n caused by <br> in 3rd <p> should match with each query text rect result")) {
      return;
    }
  }

  // next of <br> in 3rd <p>
  var next_br = synthesizeQueryTextRect(kLFLen * 4 + 6, 1);
  if (!checkQueryContentResult(next_br, description + "rect for next of <br> in 3rd <p>")) {
    return;
  }

  is(next_br.top, br.top, description + "next of <br> and <br> should be at same top");
  is(next_br.left, br.left, description + "next of <br> and <br> should be at same left");
  is(next_br.height, br.height, description + "next of <br> and <br> should be same height");
  is(next_br.width, br.width, description + "next of <br> and <br> should be same width");

  // next of <br> in 3rd <p> as array
  var next_brAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 6, 1);
  if (!checkQueryContentResult(next_brAsArray, description + "rect array for next of <br> in 3rd <p>") ||
      !checkRectArray(next_brAsArray, [next_br], description + "query text rect array result of next of <br> in 3rd <p> should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  tooBigOffset = synthesizeQueryTextRect(kLFLen * 4 + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_br.top, description + "too big offset and next of 3rd <p> should be at same top");
  is(tooBigOffset.left, next_br.left, description + "too big offset and next of 3rd <p> should be at same left");
  is(tooBigOffset.height, next_br.height, description + "too big offset and next of 3rd <p> should be same height");
  is(tooBigOffset.width, next_br.width, description + "too big offset and next of 3rd <p> should be same width");

  // too big offset for the editors as array
  tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 4 + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }

  contenteditable.innerHTML = "<p>abc</p><p>def</p><p></p>";
                      // \n    0  123    4  567    8
                      // \r\n  01 234    56 789    0

  description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "f"
  f = synthesizeQueryTextRect(kLFLen * 2 + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
  is(f.height, e.height, description + "'e' and 'f' should be same height");

  // 3rd <p> (can be computed with rect of 'f')
  p3 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(p3, description + "rect for 3rd <p>")) {
    return;
  }

  is(p3.top, f.top, description + "'f' and a line breaker caused by 3rd <p> should be at same top");
  is(p3.height, f.height, description + "'f' and a line breaker caused by 3rd <p> should be same height");
  isSimilarTo(p3.left, f.left + f.width, 2, description + "left of a line breaker caused by 3rd <p> should be similar to right of 'f'");

  // 3rd <p> as array
  p3AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(p3AsArray, description + "3rd <p>'s line breaker as array") ||
      !checkRectArray(p3AsArray, [p3], description + "query text rect array result of 3rd <p> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    p3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(p3_2, description + "rect for \\n of \\r\\n caused by 3rd <p>")) {
      return;
    }

    is(p3_2.top, p3.top, description + "'\\r' and '\\n' should be at same top");
    is(p3_2.left, p3.left, description + "'\\r' and '\\n' should be at same top");
    is(p3_2.height, p3.height, description + "'\\r' and '\\n' should be same height");
    is(p3_2.width, p3.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    p3_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(p3_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <p>") ||
        !checkRectArray(p3_2AsArray, [p3_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <p> should match with each query text rect result")) {
      return;
    }
  }

  // next of 3rd <p>
  var next_p3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(next_p3, description + "rect for next of 3rd <p>")) {
    return;
  }

  isGreaterThan(next_p3.top, d.top + d.height, description + "top of next of 3rd <p> should equal to or be bigger than bottom of 'd'");
  isSimilarTo(next_p3.left, d.left, 2, description + "left of next of 3rd <p> should be at similar to left of 'd'");
  isSimilarTo(next_p3.height, d.height, 2, description + "next of 3rd <p> and 'd' should be similar height");

  // next of 3rd <p> as array
  var next_p3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(next_p3AsArray, description + "next of 3rd <p> as array") ||
      !checkRectArray(next_p3AsArray, [next_p3], description + "query text rect array result of next of 3rd <p> should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_p3.top, description + "too big offset and next of 3rd <p> should be at same top");
  is(tooBigOffset.left, next_p3.left, description + "too big offset and next of 3rd <p> should be at same left");
  is(tooBigOffset.height, next_p3.height, description + "too big offset and next of 3rd <p> should be same height");
  is(tooBigOffset.width, next_p3.width, description + "too big offset and next of 3rd <p> should be same width");

  // too big offset for the editors as array
  tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }

  contenteditable.innerHTML = "abc<br>def";
                      // \n    0123   456
                      // \r\n  01234  567

  description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "a"
  a = synthesizeQueryTextRect(0, 1);
  if (!checkQueryContentResult(a, description + "rect for 'a'")) {
    return;
  }

  // "b"
  b = synthesizeQueryTextRect(1, 1);
  if (!checkQueryContentResult(b, description + "rect for 'b'")) {
    return;
  }

  is(b.top, a.top, description + "'a' and 'b' should be at same top");
  isSimilarTo(b.left, a.left + a.width, 2, description + "left of 'b' should be at similar to right of 'a'");
  is(b.height, a.height, description + "'a' and 'b' should be same height");

  // "c"
  c = synthesizeQueryTextRect(2, 1);
  if (!checkQueryContentResult(c, description + "rect for 'c'")) {
    return;
  }

  is(c.top, b.top, description + "'b' and 'c' should be at same top");
  isSimilarTo(c.left, b.left + b.width, 2, description + "left of 'c' should be at similar to right of 'b'");
  is(c.height, b.height, description + "'b' and 'c' should be same height");

  // "abc" as array
  abcAsArray = synthesizeQueryTextRectArray(0, 3);
  if (!checkQueryContentResult(abcAsArray, description + "rect array for 'abc'") ||
      !checkRectArray(abcAsArray, [a, b, c], description + "query text rect array result of 'abc' should match with each query text rect result")) {
    return;
  }

  // <br> (can be computed with the rect of 'c')
  br = synthesizeQueryTextRect(3, 1);
  if (!checkQueryContentResult(br, description + "rect for <br>")) {
    return;
  }

  is(br.top, c.top, description + "'c' and a line breaker caused by <br> should be at same top");
  isSimilarTo(br.left, c.left + c.width, 2, description + "left of a line breaker caused by <br> should be at similar to right of 'c'");
  is(br.height, c.height, description + "'c' and a line breaker caused by <br> should be same height");

  // <br> as array
  brAsArray = synthesizeQueryTextRectArray(3, 1);
  if (!checkQueryContentResult(brAsArray, description + "<br>'s line breaker as array") ||
      !checkRectArray(brAsArray, [br], description + "query text rect array result of <br> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var br_2 = synthesizeQueryTextRect(4, 1);
    if (!checkQueryContentResult(br_2, description + "rect for \n of \r\n caused by <br>")) {
      return;
    }

    is(br_2.top, br.top, description + "'\\r' and '\\n' should be at same top");
    is(br_2.left, br.left, description + "'\\r' and '\\n' should be at same top");
    is(br_2.height, br.height, description + "'\\r' and '\\n' should be same height");
    is(br_2.width, br.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var br_2AsArray = synthesizeQueryTextRectArray(4, 1);
    if (!checkQueryContentResult(br_2AsArray, description + "rect array for \\n of \\r\\n caused by <br>") ||
        !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")) {
      return;
    }
  }

  // "d"
  d = synthesizeQueryTextRect(kLFLen + 3, 1);
  if (!checkQueryContentResult(d, description + "rect for 'd'")) {
    return;
  }

  isSimilarTo(d.top, a.top + a.height, 2, description + "top of 'd' should be at similar to bottom of 'a'");
  is(d.left, a.left, description + "'a' and 'd' should be same at same left");
  is(d.height, a.height, description + "'a' and 'd' should be same height");

  // "e"
  e = synthesizeQueryTextRect(kLFLen + 4, 1);
  if (!checkQueryContentResult(e, description + "rect for 'e'")) {
    return;
  }

  is(e.top, d.top, description + "'d' and 'd' should be at same top");
  isSimilarTo(e.left, d.left + d.width, 2, description + "left of 'e' should be at similar to right of 'd'");
  is(e.height, d.height, description + "'d' and 'e' should be same height");

  // "f"
  f = synthesizeQueryTextRect(kLFLen + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
  is(f.height, e.height, description + "'e' and 'f' should be same height");

  // "def" as array
  defAsArray = synthesizeQueryTextRectArray(kLFLen + 3, 3);
  if (!checkQueryContentResult(defAsArray, description + "rect array for 'def'") ||
      !checkRectArray(defAsArray, [d, e, f], description + "query text rect array result of 'def' should match with each query text rect result")) {
    return;
  }

  // next of "f" (can be computed with rect of 'f')
  next_f = synthesizeQueryTextRect(kLFLen + 6, 1);
  if (!checkQueryContentResult(next_f, description + "rect for next of 'f'")) {
    return;
  }

  is(next_f.top, d.top, 2, description + "'f' and next of 'f' should be at same top");
  isSimilarTo(next_f.left, f.left + f.width, 2, description + "left of next of 'f' should be at similar to right of 'f'");
  is(next_f.height, d.height, description + "'f' and next of 'f' should be same height");

  // next of "f" as array
  next_fAsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
  if (!checkQueryContentResult(next_fAsArray, description + "rect array for next of 'f'") ||
      !checkRectArray(next_fAsArray, [next_f], description + "query text rect array result of next of 'f' should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  tooBigOffset = synthesizeQueryTextRect(kLFLen + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_f.top, description + "too big offset and next of 'f' should be at same top");
  is(tooBigOffset.left, next_f.left, description + "too big offset and next of 'f' should be at same left");
  is(tooBigOffset.height, next_f.height, description + "too big offset and next of 'f' should be same height");
  is(tooBigOffset.width, next_f.width, description + "too big offset and next of 'f' should be same width");

  // too big offset for the editors as array
  tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }

  // Note that this case does not have an empty line at the end.
  contenteditable.innerHTML = "abc<br>def<br>";
                      // \n    0123   4567
                      // \r\n  01234  56789

  description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "f"
  f = synthesizeQueryTextRect(kLFLen + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  is(f.height, e.height, description + "'e' and 'f' should be same height");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");

  // 2nd <br> (can be computed with rect of 'f')
  var br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
  if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
    return;
  }

  is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
  is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
  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'");

  // 2nd <br> as array
  var br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
      !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
    if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
      return;
    }

    is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
    is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
    is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
    is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
    if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
        !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")) {
      return;
    }
  }

  // next of 2nd <br>
  var next_br2 = synthesizeQueryTextRect(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(next_br2, description + "rect for next of 2nd <br>")) {
    return;
  }

  is(next_br2.top, br2.top, description + "2nd <br> and next of 2nd <br> should be at same top");
  is(next_br2.left, br2.left, description + "2nd <br> and next of 2nd <br> should be at same top");
  is(next_br2.height, br2.height, description + "2nd <br> and next of 2nd <br> should be same height");
  is(next_br2.width, br2.width, description + "2nd <br> and next of 2nd <br> should be same width");

  // next of 2nd <br> as array
  var next_br2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 6, 1);
  if (!checkQueryContentResult(next_br2AsArray, description + "rect array for next of 2nd <br>") ||
      !checkRectArray(next_br2AsArray, [next_br2], description + "query text rect array result of next of 2nd <br> should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  tooBigOffset = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_br2.top, description + "too big offset and next of 2nd <br> should be at same top");
  is(tooBigOffset.left, next_br2.left, description + "too big offset and next of 2nd <br> should be at same left");
  is(tooBigOffset.height, next_br2.height, description + "too big offset and next of 2nd <br> should be same height");
  is(tooBigOffset.width, next_br2.width, description + "too big offset and next of 2nd <br> should be same width");

  // too big offset for the editors as array
  tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }

  contenteditable.innerHTML = "abc<br>def<br><br>";
                      // \n    0123   4567   8
                      // \r\n  01234  56789  01

  description = "runTextRectInContentEditableTest: \"" + contenteditable.innerHTML + "\", ";

  // "f"
  f = synthesizeQueryTextRect(kLFLen + 5, 1);
  if (!checkQueryContentResult(f, description + "rect for 'f'")) {
    return;
  }

  is(f.top, e.top, description + "'e' and 'f' should be at same top");
  isSimilarTo(f.left, e.left + e.width, 2, description + "left of 'f' should be at similar to right of 'e'");
  is(f.height, e.height, description + "'e' and 'f' should be same height");

  // 2nd <br>
  br2 = synthesizeQueryTextRect(kLFLen + 6, 1);
  if (!checkQueryContentResult(br2, description + "rect for 2nd <br>")) {
    return;
  }

  is(br2.top, f.top, description + "'f' and a line breaker caused by 2nd <br> should be at same top");
  is(br2.height, f.height, description + "'f' and a line breaker caused by 2nd <br> should be same height");
  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);

  // 2nd <br> as array
  br2AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
  if (!checkQueryContentResult(br2AsArray, description + "2nd <br>'s line breaker as array") ||
      !checkRectArray(br2AsArray, [br2], description + "query text rect array result of 2nd <br> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    br2_2 = synthesizeQueryTextRect(kLFLen + 7, 1);
    if (!checkQueryContentResult(br2_2, description + "rect for \\n of \\r\\n caused by 2nd <br>")) {
      return;
    }

    is(br2_2.top, br2.top, description + "'\\r' and '\\n' should be at same top");
    is(br2_2.left, br2.left, description + "'\\r' and '\\n' should be at same top");
    is(br2_2.height, br2.height, description + "'\\r' and '\\n' should be same height");
    is(br2_2.width, br2.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen + 7, 1);
    if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 2nd <br>") ||
        !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")) {
      return;
    }
  }

  // 3rd <br>
  var br3 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(br3, description + "rect for next of 3rd <br>")) {
    return;
  }

  isSimilarTo(br3.top, d.top + d.height, 3, description + "top of next of 3rd <br> should at similar to bottom of 'd'");
  isSimilarTo(br3.left, d.left, 2, description + "left of next of 3rd <br> should be at similar to left of 'd'");
  isSimilarTo(br3.height, d.height, 2, description + "next of 3rd <br> and 'd' should be similar height");

  // 3rd <br> as array
  var br3AsArray = synthesizeQueryTextRectArray(kLFLen + 6, 1);
  if (!checkQueryContentResult(br3AsArray, description + "3rd <br>'s line breaker as array") ||
      !checkRectArray(br3AsArray, [br3], description + "query text rect array result of 3rd <br> should match with each query text rect result")) {
    return;
  }

  if (kLFLen > 1) {
    // \n of \r\n
    var br3_2 = synthesizeQueryTextRect(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(br3_2, description + "rect for \\n of \\r\\n caused by 3rd <br>")) {
      return;
    }

    is(br3_2.top, br3.top, description + "'\\r' and '\\n' should be at same top");
    is(br3_2.left, br3.left, description + "'\\r' and '\\n' should be at same left");
    is(br3_2.height, br3.height, description + "'\\r' and '\\n' should be same height");
    is(br3_2.width, br3.width, description + "'\\r' and '\\n' should be same width");

    // \n of \r\n as array
    var br2_2AsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
    if (!checkQueryContentResult(br2_2AsArray, description + "rect array for \\n of \\r\\n caused by 3rd <br>") ||
        !checkRectArray(br2_2AsArray, [br2_2], description + "query text rect array result of \\n of \\r\\n caused by 3rd <br> should match with each query text rect result")) {
      return;
    }
  }

  // next of 3rd <br>
  var next_br3 = synthesizeQueryTextRect(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(next_br3, description + "rect for next of 3rd <br>")) {
    return;
  }

  is(next_br3.top, br3.top, description + "3rd <br> and next of 3rd <br> should be at same top");
  is(next_br3.left, br3.left, description + "3rd <br> and next of 3rd <br> should be at same left");
  is(next_br3.height, br3.height, description + "3rd <br> and next of 3rd <br> should be same height");
  is(next_br3.width, br3.width, description + "3rd <br> and next of 3rd <br> should be same width");

  // next of 3rd <br> as array
  var next_br3AsArray = synthesizeQueryTextRectArray(kLFLen * 3 + 6, 1);
  if (!checkQueryContentResult(next_br3AsArray, description + "rect array for next of 3rd <br>") ||
      !checkRectArray(next_br3AsArray, [next_br3], description + "query text rect array result of next of 3rd <br> should match with each query text rect result")) {
    return;
  }

  // too big offset for the editor
  tooBigOffset = synthesizeQueryTextRect(kLFLen * 3 + 7, 1);
  if (!checkQueryContentResult(tooBigOffset, description + "rect for too big offset")) {
    return;
  }

  is(tooBigOffset.top, next_br3.top, description + "too big offset and next of 3rd <br> should be at same top");
  is(tooBigOffset.left, next_br3.left, description + "too big offset and next of 3rd <br> should be at same left");
  is(tooBigOffset.height, next_br3.height, description + "too big offset and next of 3rd <br> should be same height");
  is(tooBigOffset.width, next_br3.width, description + "too big offset and next of 3rd <br> should be same width");

  // too big offset for the editors as array
  tooBigOffsetAsArray = synthesizeQueryTextRectArray(kLFLen * 2 + 7, 1);
  if (!checkQueryContentResult(tooBigOffsetAsArray, description + "rect array for too big offset") ||
      !checkRectArray(tooBigOffsetAsArray, [tooBigOffset], description + "query text rect array result with too big offset should match with each query text rect result")) {
    return;
  }
}

function runCharAtPointTest(aFocusedEditor, aTargetName)
{
  aFocusedEditor.value = "This is a test of the\nContent Events";
                       // 012345678901234567890  12345678901234
                       // 0         1         2           3

  aFocusedEditor.focus();

  const kNone = -1;
  const kTestingOffset             = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
  const kLeftSideOffset            = [ kNone,  9,    19,       kNone, 33 + kLFLen];
  const kRightSideOffset           = [     1, 11, kNone, 22 + kLFLen,       kNone];
  const kLeftTentativeCaretOffset  = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
  const kRightTentativeCaretOffset = [     1, 11,    21, 22 + kLFLen, 35 + kLFLen];

  var editorRect = synthesizeQueryEditorRect();
  if (!checkQueryContentResult(editorRect,
        "runCharAtPointTest (" + aTargetName + "): editorRect")) {
    return;
  }

  for (var i = 0; i < kTestingOffset.length; i++) {
    var textRect = synthesizeQueryTextRect(kTestingOffset[i], 1);
    if (!checkQueryContentResult(textRect,
          "runCharAtPointTest (" + aTargetName + "): textRect: i=" + i)) {
      continue;
    }

    checkRectContainsRect(textRect, editorRect,
      "runCharAtPointTest (" + aTargetName +
      "): the text rect isn't in the editor");

    // Test #1, getting same character rect by the point near the top-left.
    var charAtPt1 = synthesizeCharAtPoint(textRect.left + 1,
                                          textRect.top + 1);
    if (checkQueryContentResult(charAtPt1,
          "runCharAtPointTest (" + aTargetName + "): charAtPt1: i=" + i)) {
      ok(!charAtPt1.notFound,
         "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
      if (!charAtPt1.notFound) {
        is(charAtPt1.offset, kTestingOffset[i],
           "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
        checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
                  "): charAtPt1 left is wrong: i=" + i);
      }
      ok(!charAtPt1.tentativeCaretOffsetNotFound,
         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
      if (!charAtPt1.tentativeCaretOffsetNotFound) {
        is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
      }
    }

    // Test #2, getting same character rect by the point near the bottom-right.
    var charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
                                          textRect.top + textRect.height - 2);
    if (checkQueryContentResult(charAtPt2,
          "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
      ok(!charAtPt2.notFound,
         "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
      if (!charAtPt2.notFound) {
        is(charAtPt2.offset, kTestingOffset[i],
           "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
        checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
                  "): charAtPt1 left is wrong: i=" + i);
      }
      ok(!charAtPt2.tentativeCaretOffsetNotFound,
         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
      if (!charAtPt2.tentativeCaretOffsetNotFound) {
        is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
      }
    }

    // Test #3, getting left character offset.
    var charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
                                          textRect.top + 1);
    if (checkQueryContentResult(charAtPt3,
          "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
      is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
         kLeftSideOffset[i] == kNone ?
           "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
           "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
      if (!charAtPt3.notFound) {
        is(charAtPt3.offset, kLeftSideOffset[i],
           "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
      }
      if (kLeftSideOffset[i] == kNone) {
        // There may be no enough padding-left (depends on platform)
        todo(false,
             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
      } else {
        ok(!charAtPt3.tentativeCaretOffsetNotFound,
           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
        if (!charAtPt3.tentativeCaretOffsetNotFound) {
          is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
        }
      }
    }

    // Test #4, getting right character offset.
    var charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
                                          textRect.top + textRect.height - 2);
    if (checkQueryContentResult(charAtPt4,
          "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
      is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
         kRightSideOffset[i] == kNone ?
           "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
           "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
      if (!charAtPt4.notFound) {
        is(charAtPt4.offset, kRightSideOffset[i],
           "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
      }
      ok(!charAtPt4.tentativeCaretOffsetNotFound,
         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
      if (!charAtPt4.tentativeCaretOffsetNotFound) {
        is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
      }
    }
  }
}

function runCharAtPointAtOutsideTest()
{
  textarea.focus();
  textarea.value = "some text";
  var editorRect = synthesizeQueryEditorRect();
  if (!checkQueryContentResult(editorRect,
        "runCharAtPointAtOutsideTest: editorRect")) {
    return;
  }
  // Check on a text node which is at the outside of editor.
  var charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
                                       editorRect.top - 10);
  if (checkQueryContentResult(charAtPt,
        "runCharAtPointAtOutsideTest: charAtPt")) {
    ok(charAtPt.notFound,
       "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
    ok(charAtPt.tentativeCaretOffsetNotFound,
       "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
  }
}

function runSetSelectionEventTest()
{
  contenteditable.focus();

  var selection = windowOfContenteditable.getSelection();

  // #1
  contenteditable.innerHTML = "abc<br>def";

  synthesizeSelectionSet(0, 100);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
  checkSelection(0, "abc" + kLF + "def", "runSetSelectionEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(2, 2 + kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 2,
     "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(2, "c" + kLF + "d", "runSetSelectionEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(1, 2);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
  is(selection.focusOffset, contenteditable.firstChild.wholeText.length,
     "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node");
  checkSelection(1, "bc", "runSetSelectionEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, contenteditable.firstChild.wholeText.length,
     "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 2,
     "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the last text node");
  checkSelection(3, kLF, "runSetSelectionEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(6+kLFLen, 0);
  is(selection.anchorNode, contenteditable.lastChild,
     "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
     "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.anchorOffset, contenteditable.lastChild.wholeText.length,
     "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (6+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(100, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node of the editor");
  is(selection.anchorOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of children");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node of the editor");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of children");
  checkSelection(6 + kLFLen, "", "runSetSelectionEventTest #1 (100, 0), \"" + contenteditable.innerHTML + "\"");

  // #2
  contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";

  synthesizeSelectionSet(kLFLen, 4+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
  is(selection.focusNode, contenteditable.lastChild.firstChild,
     "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(kLFLen, "abc" + kLF + "d", "runSetSelectionEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 2);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
  is(selection.focusNode, contenteditable.firstChild.childNodes.item(1).firstChild,
     "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <b> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
     "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <b> node");
  checkSelection(kLFLen, "ab", "runSetSelectionEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(1+kLFLen, 2);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node in the first <p> node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node in the first <p> node");
  checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(2+kLFLen, 2+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
     "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <b> node");
  is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
     "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <b> node");
  is(selection.focusNode, contenteditable.lastChild.firstChild,
     "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the last <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(2+kLFLen, "c" + kLF + "d", "runSetSelectionEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen*2, 1);
  is(selection.anchorNode, contenteditable.lastChild,
     "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the second <p> node");
  is(selection.focusNode, contenteditable.lastChild.firstChild,
     "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the second <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(3+kLFLen*2, "d", "runSetSelectionEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #2 (0, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first <p> node");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, kLF, "runSetSelectionEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.childNodes.item(1).firstChild,
     "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the <b> node");
  is(selection.anchorOffset, contenteditable.firstChild.childNodes.item(1).firstChild.wholeText.length,
     "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");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node of the first <p> node");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node of the first <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "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");
  is(selection.focusNode, contenteditable.lastChild.firstChild,
     "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the second <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #3
  contenteditable.innerHTML = "<div>abc<p>def</p></div>";

  synthesizeSelectionSet(1+kLFLen, 2);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
  is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
  checkSelection(1+kLFLen, "bc", "runSetSelectionEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(1+kLFLen, 3+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(1+kLFLen, "bc" + kLF + "d", "runSetSelectionEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen, 0);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the first text node");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node");
  is(selection.focusOffset, contenteditable.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the first text node");
  checkSelection(3+kLFLen, "", "runSetSelectionEventTest #3 (3+kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 6+kLFLen*2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 100);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(0, kLF + "abc" + kLF + "def", "runSetSelectionEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(4+kLFLen*2, 2);
  is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(4+kLFLen*2, 100);
  is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(6+kLFLen*2, 0);
  is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
  is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(6+kLFLen*2, 1);
  is(selection.anchorNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the first text node");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, kLF, "runSetSelectionEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 1+kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the first text node of the <div> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(0, kLF + "a", "runSetSelectionEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(2+kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
  is(selection.anchorOffset, 2,
     "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 2");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(2+kLFLen, "c" + kLF, "runSetSelectionEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
  is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(3+kLFLen, kLF, "runSetSelectionEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node of the <div> node");
  is(selection.anchorOffset, contenteditable.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node of the <div> node");
  is(selection.focusNode, contenteditable.firstChild.lastChild.firstChild,
     "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node of the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(3+kLFLen, kLF + "d", "runSetSelectionEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #4
  contenteditable.innerHTML = "<div><p>abc</p>def</div>";

  synthesizeSelectionSet(1+kLFLen*2, 2);
  is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
  checkSelection(1+kLFLen*2, "bc", "runSetSelectionEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(1+kLFLen*2, 3);
  is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(1+kLFLen*2, "bcd", "runSetSelectionEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(3+kLFLen*2, 0);
  is(selection.anchorNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node in the <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the text node in the <p> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.firstChild.firstChild.wholeText.length,
     "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the text node in the <p> node");
  checkSelection(3+kLFLen*2, "", "runSetSelectionEventTest #4 (3+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 6+kLFLen*2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 100);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(0, kLF + kLF + "abcdef", "runSetSelectionEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(4+kLFLen*2, 2);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(4+kLFLen*2, 100);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(4+kLFLen*2, "ef", "runSetSelectionEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(6+kLFLen*2, 0);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
  is(selection.focusNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(6+kLFLen*2, 1);
  is(selection.anchorNode, contenteditable.firstChild.lastChild,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the last text node");
  is(selection.anchorOffset, contenteditable.firstChild.lastChild.wholeText.length,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the length of the last text node");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(6+kLFLen*2, "", "runSetSelectionEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, kLF, "runSetSelectionEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen*2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, kLF + kLF, "runSetSelectionEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 1+kLFLen*2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <div> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(0, kLF + kLF + "a", "runSetSelectionEventTest #4 (0, 1+kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 0);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <div> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #4 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(kLFLen, kLF, "runSetSelectionEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <div> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node");
  is(selection.focusNode, contenteditable.firstChild.firstChild.firstChild,
     "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node in the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(kLFLen, kLF +"a", "runSetSelectionEventTest #4 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #5
  contenteditable.innerHTML = "<br>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #5 (0, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(0, kLF, "runSetSelectionEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 1);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");

  // #6
  contenteditable.innerHTML = "<p><br></p>";

  synthesizeSelectionSet(kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(kLFLen, kLF, "runSetSelectionEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen*2, 0);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen*2, 1);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen*2, "", "runSetSelectionEventTest #6 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, kLF, "runSetSelectionEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen*2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(0, kLF + kLF, "runSetSelectionEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 0);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #6 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  // #7
  contenteditable.innerHTML = "<br><br>";

  synthesizeSelectionSet(0, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(0, kLF, "runSetSelectionEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, kLFLen * 2);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(0, kLF + kLF, "runSetSelectionEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #7 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen, kLFLen) selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen * 2, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the root's children");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen * 2, "", "runSetSelectionEventTest #7 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  // #8
  contenteditable.innerHTML = "<p><br><br></p>";

  synthesizeSelectionSet(kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(kLFLen, kLF, "runSetSelectionEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, kLFLen * 2);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(kLFLen, kLF + kLF, "runSetSelectionEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen*2, 0);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 1");
  checkSelection(kLFLen*2, "", "runSetSelectionEventTest #8 (kLFLen*2, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen*2, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*2, kLFLen) selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(kLFLen*2, kLF, "runSetSelectionEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen*3, 0);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the <p> node");
  is(selection.anchorOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the count of the <p>'s children");
  is(selection.focusNode, contenteditable.firstChild,
     "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, contenteditable.firstChild.childNodes.length,
     "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the <p>'s children");
  checkSelection(kLFLen*3, "", "runSetSelectionEventTest #8 (kLFLen*3, 0), \"" + contenteditable.innerHTML + "\"");

  // #9 (ContentEventHandler cannot distinguish if <p> can have children, so, the result is same as case #5, "<br>")
  contenteditable.innerHTML = "<p></p>";

  synthesizeSelectionSet(kLFLen, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the <p> node");
  is(selection.focusOffset, 1,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the index of the <p> node + 1");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(kLFLen, 1);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be the index of the <p> node + 1");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #9 (kLFLen, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(kLFLen, "", "runSetSelectionEventTest #9 (kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  // #10
  contenteditable.innerHTML = "";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #10 (0, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 1);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #10 (0, 1), \"" + contenteditable.innerHTML + "\"");

  // #11
  contenteditable.innerHTML = "<span></span><i><u></u></i>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #11 (0, 0), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(0, 1);
  is(selection.anchorNode, contenteditable,
     "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor node should be the root node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable,
     "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus node should be the root node");
  is(selection.focusOffset, contenteditable.childNodes.length,
     "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\": selection focus offset should be the count of the root's children");
  checkSelection(0, "", "runSetSelectionEventTest #11 (0, 1), \"" + contenteditable.innerHTML + "\"");

  // #12
  contenteditable.innerHTML = "<span>abc</span><i><u></u></i>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.firstChild.firstChild,
     "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #12 (0, 0), \"" + contenteditable.innerHTML + "\"");

  // #13
  contenteditable.innerHTML = "<span></span><i>abc<u></u></i>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild,
     "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.childNodes.item(1).firstChild,
     "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #13 (0, 0), \"" + contenteditable.innerHTML + "\"");

  // #14
  contenteditable.innerHTML = "<span></span><i><u>abc</u></i>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable.childNodes.item(1).firstChild.firstChild,
     "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.childNodes.item(1).firstChild.firstChild,
     "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #14 (0, 0), \"" + contenteditable.innerHTML + "\"");

  // #15
  contenteditable.innerHTML = "<span></span><i><u></u>abc</i>";

  synthesizeSelectionSet(0, 0);
  is(selection.anchorNode, contenteditable.childNodes.item(1).lastChild,
     "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.childNodes.item(1).lastChild,
     "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the text node");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(0, "", "runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\"");

  // #16
  contenteditable.innerHTML = "a<blink>b</blink>c";
  synthesizeSelectionSet(0, 3);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
  is(selection.focusOffset, contenteditable.lastChild.wholeText.length,
     "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
  checkSelection(0, "abc", "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");

  // #17 (bug 1319660 - incorrect adjustment of content iterator last node)
  contenteditable.innerHTML = "<div>a</div><div><br></div>";

  synthesizeSelectionSet(kLFLen, 1+kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(kLFLen, "a" + kLF, "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  synthesizeSelectionSet(1+2*kLFLen, 0);
  is(selection.anchorNode, contenteditable.lastChild,
     "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <div> element");
  is(selection.anchorOffset, 0,
     "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(1+2*kLFLen, "", "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  // #18 (bug 1319660 - content iterator start node regression)
  contenteditable.innerHTML = "<div><br></div><div><br></div>";

  synthesizeSelectionSet(2*kLFLen, kLFLen);
  is(selection.anchorNode, contenteditable.firstChild,
     "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
  is(selection.anchorOffset, 1,
     "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 1");
  is(selection.focusNode, contenteditable.lastChild,
     "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
  is(selection.focusOffset, 0,
     "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be 0");
  checkSelection(2*kLFLen, kLF, "runSetSelectionEventTest #18 (2*kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");
}

function runQueryTextContentEventTest()
{
  contenteditable.focus();

  var result;

  // #1
  contenteditable.innerHTML = "abc<br>def";

  result = synthesizeQueryTextContent(0, 6 + kLFLen);
  is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 6+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 100);
  is(result.text, "abc" + kLF + "def", "runQueryTextContentEventTest #1 (0, 100), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(2, 2 + kLFLen);
  is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #1 (2, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(1, 2);
  is(result.text, "bc", "runQueryTextContentEventTest #1 (1, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #1 (3, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(6 + kLFLen, 1);
  is(result.text, "", "runQueryTextContentEventTest #1 (6 + kLFLen, 0), \"" + contenteditable.innerHTML + "\"");

  // #2
  contenteditable.innerHTML = "<p>a<b>b</b>c</p><p>def</p>";

  result = synthesizeQueryTextContent(kLFLen, 4+kLFLen);
  is(result.text, "abc" + kLF + "d", "runQueryTextContentEventTest #2 (kLFLen, 4+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, 2);
  is(result.text, "ab", "runQueryTextContentEventTest #2 (kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(1+kLFLen, 2);
  is(result.text, "bc", "runQueryTextContentEventTest #2 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(2+kLFLen, 2+kLFLen);
  is(result.text, "c" + kLF + "d", "runQueryTextContentEventTest #2 (2+kLFLen, 2+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen*2, 1);
  is(result.text, "d", "runQueryTextContentEventTest #2 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #2 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
  is(result.text, "c" + kLF, "runQueryTextContentEventTest #2 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #2 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
  is(result.text, kLF + "d", "runQueryTextContentEventTest #2 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #3
  contenteditable.innerHTML = "<div>abc<p>def</p></div>";

  result = synthesizeQueryTextContent(1+kLFLen, 2);
  is(result.text, "bc", "runQueryTextContentEventTest #3 (1+kLFLen, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(1+kLFLen, 3+kLFLen);
  is(result.text, "bc" + kLF + "d", "runQueryTextContentEventTest #3 (1+kLFLen, 3+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen*2, 1);
  is(result.text, "d", "runQueryTextContentEventTest #3 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 6+kLFLen*2);
  is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 100);
  is(result.text, kLF + "abc" + kLF + "def", "runQueryTextContentEventTest #3 (0, 100), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(4+kLFLen*2, 2);
  is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(4+kLFLen*2, 100);
  is(result.text, "ef", "runQueryTextContentEventTest #3 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(6+kLFLen*2, 1);
  is(result.text, "", "runQueryTextContentEventTest #3 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #3 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 1+kLFLen);
  is(result.text, kLF + "a", "runQueryTextContentEventTest #3 (0, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(2+kLFLen, 1+kLFLen);
  is(result.text, "c" + kLF, "runQueryTextContentEventTest #3 (2+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #3 (3+kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen, 1+kLFLen);
  is(result.text, kLF + "d", "runQueryTextContentEventTest #3 (3+kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #4
  contenteditable.innerHTML = "<div><p>abc</p>def</div>";

  result = synthesizeQueryTextContent(1+kLFLen*2, 2);
  is(result.text, "bc", "runQueryTextContentEventTest #4 (1+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(1+kLFLen*2, 3);
  is(result.text, "bcd", "runQueryTextContentEventTest #4 (1+kLFLen*2, 3), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(3+kLFLen*2, 1);
  is(result.text, "d", "runQueryTextContentEventTest #4 (3+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 6+kLFLen*2);
  is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 6+kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 100);
  is(result.text, kLF + kLF + "abcdef", "runQueryTextContentEventTest #4 (0, 100), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(4+kLFLen*2, 2);
  is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(4+kLFLen*2, 100);
  is(result.text, "ef", "runQueryTextContentEventTest #4 (4+kLFLen*2, 100), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(6+kLFLen*2, 1);
  is(result.text, "", "runQueryTextContentEventTest #4 (6+kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #4 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen*2);
  is(result.text, kLF + kLF, "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, 1+kLFLen*2);
  is(result.text, kLF + kLF + "a", "runQueryTextContentEventTest #4 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, 1+kLFLen);
  is(result.text, kLF + "a", "runQueryTextContentEventTest #4 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  // #5
  contenteditable.innerHTML = "<br>";

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #5 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, 1);
  is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen, 1), \"" + contenteditable.innerHTML + "\"");

  // #6
  contenteditable.innerHTML = "<p><br></p>";

  result = synthesizeQueryTextContent(kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #6 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen*2, 1);
  is(result.text, "", "runQueryTextContentEventTest #5 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #6 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen*2);
  is(result.text, kLF + kLF, "runQueryTextContentEventTest #6 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  // #7
  contenteditable.innerHTML = "<br><br>";

  result = synthesizeQueryTextContent(0, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #7 (0, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(0, kLFLen * 2);
  is(result.text, kLF + kLF, "runQueryTextContentEventTest #7 (0, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #7 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen * 2, 1);
  is(result.text, "", "runQueryTextContentEventTest #7 (kLFLen*2, 1), \"" + contenteditable.innerHTML + "\"");

  // #8
  contenteditable.innerHTML = "<p><br><br></p>";

  result = synthesizeQueryTextContent(kLFLen, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen, kLFLen * 2);
  is(result.text, kLF + kLF, "runQueryTextContentEventTest #8 (kLFLen, kLFLen*2), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen*2, kLFLen);
  is(result.text, kLF, "runQueryTextContentEventTest #8 (kLFLen*2, kLFLen), \"" + contenteditable.innerHTML + "\"");

  result = synthesizeQueryTextContent(kLFLen*3, 1);
  is(result.text, "", "runQueryTextContentEventTest #8 (kLFLen*3, 1), \"" + contenteditable.innerHTML + "\"");

  // #16
  contenteditable.innerHTML = "a<blink>b</blink>c";

  result = synthesizeQueryTextContent(0, 3);
  is(result.text, "abc", "runQueryTextContentEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
}

function runQuerySelectionEventTest()
{
  contenteditable.focus();

  var selection = windowOfContenteditable.getSelection();

  // #1
  contenteditable.innerHTML = "<br/>a";
  selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild, 1);
  checkSelection(0, kLF + "a", "runQuerySelectionEventTest #1, \"" + contenteditable.innerHTML + "\"");

  // #2
  contenteditable.innerHTML = "<p></p><p>abc</p>";
  selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild.firstChild, 1);
  checkSelection(kLFLen, kLF + "a", "runQuerySelectionEventTest #2, \"" + contenteditable.innerHTML + "\"");

  // #3
  contenteditable.innerHTML = "<p>abc</p><p>def</p>";
  selection.setBaseAndExtent(contenteditable.firstChild, 0, contenteditable.lastChild.firstChild, 1);
  checkSelection(kLFLen, "abc" + kLF + "d", "runQuerySelectionEventTest #3, \"" + contenteditable.innerHTML + "\"");
}

function runQueryIMESelectionTest()
{
  textarea.focus();
  textarea.value = "before  after";
  var startoffset = textarea.selectionStart = textarea.selectionEnd = "before ".length;

  if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
      !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition") ||
      !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: before starting composition")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "a",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: inputting raw text") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
      !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text") ||
      !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: inputting raw text")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "abcdefgh",
        "clauses":
        [
          { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 8, "length": 0 }
    });

  if (!checkIMESelection("RawClause", true, startoffset, "abcdefgh", "runQueryIMESelectionTest: updating raw text") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
      !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text") ||
      !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: updating raw text")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDEFGH",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: starting to convert") ||
      !checkIMESelection("ConvertedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: starting to convert") ||
      !checkIMESelection("SelectedClause", true, startoffset, "AB", "runQueryIMESelectionTest: starting to convert")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDEFGH",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 3, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
        ]
      },
      "caret": { "start": 5, "length": 0 }
    });

  if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: changing selected clause") ||
      !checkIMESelection("ConvertedClause", true, startoffset, "AB", "runQueryIMESelectionTest: changing selected clause") ||
      !checkIMESelection("SelectedClause", true, startoffset + 2, "CDE", "runQueryIMESelectionTest: changing selected clause")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis" });

  if (!checkIMESelection("RawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
      !checkIMESelection("SelectedRawClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
      !checkIMESelection("ConvertedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition") ||
      !checkIMESelection("SelectedClause", false, 0, "", "runQueryIMESelectionTest: after committing composition")) {
    return;
  }

  startoffset = textarea.selectionStart;

  synthesizeCompositionChange(
    { "composition":
      { "string": "abcdefgh",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
        ]
      },
      "caret": { "start": 8, "length": 0 }
    });

  if (!checkIMESelection("RawClause", true, startoffset, "a", "runQueryIMESelectionTest: unrealistic testcase") ||
      !checkIMESelection("SelectedRawClause", true, startoffset + 1, "b", "runQueryIMESelectionTest: unrealistic testcase") ||
      !checkIMESelection("ConvertedClause", true, startoffset + 2, "c", "runQueryIMESelectionTest: unrealistic testcase") ||
      !checkIMESelection("SelectedClause", true, startoffset + 3, "d", "runQueryIMESelectionTest: unrealistic testcase")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis" });
}

function runQueryContentEventRelativeToInsertionPoint()
{
  textarea.focus();
  textarea.value = "0123456789";

  var startoffset = textarea.selectionStart = textarea.selectionEnd = 0;

  if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#1") ||
      !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#2") ||
      !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#3") ||
      !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#4") ||
      !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-0]", "#5")) {
    return;
  }

  textarea.selectionEnd = 5;

  if (!checkContentRelativeToSelection(0, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#1") ||
      !checkContentRelativeToSelection(-1, 1, 0, "0", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#2") ||
      !checkContentRelativeToSelection(1, 1, 1, "1", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#3") ||
      !checkContentRelativeToSelection(5, 10, 5, "56789", "runQueryContentEventRelativeToInsertionPoint[0-5]", "#4") ||
      !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[0-5]"), "#5") {
    return;
  }

  startoffset = textarea.selectionStart = textarea.selectionEnd = 4;

  if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "4", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#1") ||
      !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#2") ||
      !checkContentRelativeToSelection(1, 1, startOffset + 1, "5", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#3") ||
      !checkContentRelativeToSelection(5, 10, startOffset + 5, "9", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#4") ||
      !checkContentRelativeToSelection(10, 1, 10, "", "runQueryContentEventRelativeToInsertionPoint[4-4]", "#5")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "a",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#1") ||
      !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#2") ||
      !checkContentRelativeToSelection(1, 1, startOffset + 1, "4", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#3") ||
      !checkContentRelativeToSelection(5, 10, startOffset + 5, "89", "runQueryContentEventRelativeToInsertionPoint[composition at 4]", "#4") ||
      !checkContentRelativeToSelection(11, 1, 11, "", "runQueryContentEventRelativeToInsertionPoint[composition at 4]")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis" });

  // Move start of composition at first compositionupdate event.
  function onCompositionUpdate(aEvent)
  {
    startoffset = textarea.selectionStart = textarea.selectionEnd = textarea.selectionStart - 1;
    textarea.removeEventListener("compositionupdate", onCompositionUpdate);
  }
  textarea.addEventListener("compositionupdate", onCompositionUpdate);

  synthesizeCompositionChange(
    { "composition":
      { "string": "a",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkContentRelativeToSelection(0, 1, startOffset + 0, "b", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#1") ||
      !checkContentRelativeToSelection(-1, 1, startOffset - 1, "3", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#2") ||
      !checkContentRelativeToSelection(1, 1, startOffset + 1, "a", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#3") ||
      !checkContentRelativeToSelection(5, 10, startOffset + 5, "789", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#4") ||
      !checkContentRelativeToSelection(12, 1, 12, "", "runQueryContentEventRelativeToInsertionPoint[composition at 3]", "#5")) {
    synthesizeComposition({ type: "compositioncommitasis" });
    return;
  }

  synthesizeComposition({ type: "compositioncommitasis" });
}

function runBug1375825Test()
{
  contenteditable.focus();

  // #1
  contenteditable.innerHTML = "abc<span contenteditable=\"false\">defgh</span>";

  var ret = synthesizeQueryTextRect(2, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "c", "runBug1375825Test #1 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");

  ret = synthesizeQueryTextRect(3, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "d", "runBug1375825Test #1 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");

  ret = synthesizeQueryTextRect(4, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "e", "runBug1375825Test #1 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");

  ret = synthesizeQueryTextRect(5, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "f", "runBug1375825Test #1 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");

  ret = synthesizeQueryTextRect(6, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "g", "runBug1375825Test #1 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");

  ret = synthesizeQueryTextRect(7, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "h", "runBug1375825Test #1 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");

  // #2
  contenteditable.innerHTML = "abc<span style=\"-moz-user-select: all;\">defgh</span>";

  ret = synthesizeQueryTextRect(2, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "c", "runBug1375825Test #2 (2, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'c'");

  ret = synthesizeQueryTextRect(3, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "d", "runBug1375825Test #2 (3, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'd'");

  ret = synthesizeQueryTextRect(4, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "e", "runBug1375825Test #2 (4, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'e'");

  ret = synthesizeQueryTextRect(5, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "f", "runBug1375825Test #2 (5, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'f'");

  ret = synthesizeQueryTextRect(6, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "g", "runBug1375825Test #2 (6, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'g'");

  ret = synthesizeQueryTextRect(7, 1);
  if (!checkQueryContentResult(ret, "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\"")) {
    return;
  }
  is(ret.text, "h", "runBug1375825Test #2 (7, 1), \"" + contenteditable.innerHTML + "\": should have queried a rect for 'h'");
}

function runCSSTransformTest()
{
  textarea.focus();
  textarea.value = "some text";
  textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
  var editorRect = synthesizeQueryEditorRect();
  if (!checkQueryContentResult(editorRect,
        "runCSSTransformTest: editorRect")) {
    return;
  }
  var firstCharRect = synthesizeQueryTextRect(0, 1);
  if (!checkQueryContentResult(firstCharRect,
        "runCSSTransformTest: firstCharRect")) {
    return;
  }
  var lastCharRect = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
  if (!checkQueryContentResult(lastCharRect,
        "runCSSTransformTest: lastCharRect")) {
    return;
  }
  var caretRect = synthesizeQueryCaretRect(textarea.selectionStart);
  if (!checkQueryContentResult(caretRect,
        "runCSSTransformTest: caretRect")) {
    return;
  }
  var caretRectBeforeFirstChar = synthesizeQueryCaretRect(0);
  if (!checkQueryContentResult(caretRectBeforeFirstChar,
        "runCSSTransformTest: caretRectBeforeFirstChar")) {
    return;
  }

  try {
    textarea.style.transform = "translate(10px, 15px)";
    function movedRect(aRect, aX, aY)
    {
      return { left: aRect.left + aX, top: aRect.top + aY, width: aRect.width, height: aRect.height }
    }

    var editorRectTranslated = synthesizeQueryEditorRect();
    if (!checkQueryContentResult(editorRectTranslated,
          "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform) ||
        !checkRect(editorRectTranslated, movedRect(editorRect, 10, 15),
          "runCSSTransformTest: editorRectTranslated, " + textarea.style.transform)) {
      return;
    }
    var firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslated,
          "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform) ||
        !checkRect(firstCharRectTranslated, movedRect(firstCharRect, 10, 15),
          "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    var lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslated,
          "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform) ||
        !checkRect(lastCharRectTranslated, movedRect(lastCharRect, 10, 15),
          "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    var caretRectTranslated = synthesizeQueryCaretRect(textarea.selectionStart);
    if (!checkQueryContentResult(caretRectTranslated,
          "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform) ||
        !checkRect(caretRectTranslated, movedRect(caretRect, 10, 15),
          "runCSSTransformTest: caretRectTranslated, " + textarea.style.transform)) {
      return;
    }
    var caretRectBeforeFirstCharTranslated = synthesizeQueryCaretRect(0);
    if (!checkQueryContentResult(caretRectBeforeFirstCharTranslated,
          "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform) ||
        !checkRect(caretRectBeforeFirstCharTranslated, movedRect(caretRectBeforeFirstChar, 10, 15),
          "runCSSTransformTest: caretRectBeforeFirstCharTranslated, " + textarea.style.transform)) {
      return;
    }
    var firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }
    var lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }

    // XXX It's too difficult to check the result with scale and rotate...
    //     For now, let's check if query text rect and query text rect array returns same rect.
    textarea.style.transform = "scale(1.5)";
    firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslated,
          "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslated,
          "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }
    lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }

    textarea.style.transform = "rotate(30deg)";
    firstCharRectTranslated = synthesizeQueryTextRect(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslated,
          "runCSSTransformTest: firstCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    lastCharRectTranslated = synthesizeQueryTextRect(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslated,
          "runCSSTransformTest: lastCharRectTranslated, " + textarea.style.transform)) {
      return;
    }
    firstCharRectTranslatedAsArray = synthesizeQueryTextRectArray(0, 1);
    if (!checkQueryContentResult(firstCharRectTranslatedAsArray, "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(firstCharRectTranslatedAsArray, [firstCharRectTranslated], "runCSSTransformTest: firstCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }
    lastCharRectTranslatedAsArray = synthesizeQueryTextRectArray(textarea.value.length - 1, textarea.value.length);
    if (!checkQueryContentResult(lastCharRectTranslatedAsArray, "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform) ||
        !checkRectArray(lastCharRectTranslatedAsArray, [lastCharRectTranslated], "runCSSTransformTest: lastCharRectTranslatedAsArray, " + textarea.style.transform)) {
      return;
    }
  } finally {
    textarea.style.transform = "";
  }
}

function runBug722639Test()
{
  textarea.focus();
  textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
  textarea.value += textarea.value;
  textarea.value += textarea.value; // 80 characters

  var firstLine = synthesizeQueryTextRect(0, 1);
  if (!checkQueryContentResult(firstLine,
        "runBug722639Test: firstLine")) {
    return;
  }
  ok(true, "runBug722639Test: 1st line, top=" + firstLine.top + ", left=" + firstLine.left);
  var firstLineAsArray = synthesizeQueryTextRectArray(0, 1);
  if (!checkQueryContentResult(firstLineAsArray, "runBug722639Test: 1st line as array") ||
      !checkRectArray(firstLineAsArray, [firstLine], "runBug722639Test: 1st line as array should match with text rect result")) {
    return;
  }
  if (kLFLen > 1) {
    var firstLineLF = synthesizeQueryTextRect(1, 1);
    if (!checkQueryContentResult(firstLineLF,
          "runBug722639Test: firstLineLF")) {
      return;
    }
    is(firstLineLF.top, firstLine.top, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
    is(firstLineLF.left, firstLine.left, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
    is(firstLineLF.height, firstLine.height, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
    is(firstLineLF.width, firstLine.width, "runBug722639Test: 1st line's \\n rect should be same as 1st line's \\r rect");
    var firstLineLFAsArray = synthesizeQueryTextRectArray(1, 1);
    if (!checkQueryContentResult(firstLineLFAsArray, "runBug722639Test: 1st line's \\n rect as array") ||
        !checkRectArray(firstLineLFAsArray, [firstLineLF], "runBug722639Test: 1st line's rect as array should match with text rect result")) {
      return;
    }
  }
  var secondLine = synthesizeQueryTextRect(kLFLen, 1);
  if (!checkQueryContentResult(secondLine,
        "runBug722639Test: secondLine")) {
    return;
  }
  ok(true, "runBug722639Test: 2nd line, top=" + secondLine.top + ", left=" + secondLine.left);
  var secondLineAsArray = synthesizeQueryTextRectArray(kLFLen, 1);
  if (!checkQueryContentResult(secondLineAsArray, "runBug722639Test: 2nd line as array") ||
      !checkRectArray(secondLineAsArray, [secondLine], "runBug722639Test: 2nd line as array should match with text rect result")) {
    return;
  }
  if (kLFLen > 1) {
    var secondLineLF = synthesizeQueryTextRect(kLFLen + 1, 1);
    if (!checkQueryContentResult(secondLineLF,
          "runBug722639Test: secondLineLF")) {
      return;
    }
    is(secondLineLF.top, secondLine.top, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
    is(secondLineLF.left, secondLine.left, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
    is(secondLineLF.height, secondLine.height, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
    is(secondLineLF.width, secondLine.width, "runBug722639Test: 2nd line's \\n rect should be same as 2nd line's \\r rect");
    var secondLineLFAsArray = synthesizeQueryTextRectArray(kLFLen + 1, 1);
    if (!checkQueryContentResult(secondLineLFAsArray, "runBug722639Test: 2nd line's \\n rect as array") ||
        !checkRectArray(secondLineLFAsArray, [secondLineLF], "runBug722639Test: 2nd line's rect as array should match with text rect result")) {
      return;
    }
  }
  var lineHeight = secondLine.top -  firstLine.top;
  ok(lineHeight > 0,
     "runBug722639Test: lineHeight must be positive");
  is(secondLine.left, firstLine.left,
     "runBug722639Test: the left value must be always same value");
  is(secondLine.height, firstLine.height,
     "runBug722639Test: the height must be always same value");
  var previousTop = secondLine.top;
  for (var i = 3; i <= textarea.value.length + 1; i++) {
    var currentLine = synthesizeQueryTextRect(kLFLen * (i - 1), 1);
    if (!checkQueryContentResult(currentLine,
           "runBug722639Test: " + i + "th currentLine")) {
      return;
    }
    ok(true, "runBug722639Test: " + i + "th line, top=" + currentLine.top + ", left=" + currentLine.left);
    var currentLineAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1), 1);
    if (!checkQueryContentResult(currentLineAsArray, "runBug722639Test: " + i + "th line as array") ||
        !checkRectArray(currentLineAsArray, [currentLine], "runBug722639Test: " + i + "th line as array should match with text rect result")) {
      return;
    }
    // NOTE: the top position may be 1px larger or smaller than other lines
    //       due to sub pixel positioning.
    if (Math.abs(currentLine.top - (previousTop + lineHeight)) <= 1) {
      ok(true, "runBug722639Test: " + i + "th line's top is expected");
    } else {
      is(currentLine.top, previousTop + lineHeight,
         "runBug722639Test: " + i + "th line's top is unexpected");
    }
    is(currentLine.left, firstLine.left,
       "runBug722639Test: " + i + "th line's left is unexpected");
    is(currentLine.height, firstLine.height,
       "runBug722639Test: " + i + "th line's height is unexpected");
    if (kLFLen > 1) {
      var currentLineLF = synthesizeQueryTextRect(kLFLen * (i - 1) + 1, 1);
      if (!checkQueryContentResult(currentLineLF,
            "runBug722639Test: " + i + "th currentLineLF")) {
        return;
      }
      is(currentLineLF.top, currentLine.top, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
      is(currentLineLF.left, currentLine.left, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
      is(currentLineLF.height, currentLine.height, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
      is(currentLineLF.width, currentLine.width, "runBug722639Test: " + i + "th line's \\n rect should be same as same line's \\r rect");
      var currentLineLFAsArray = synthesizeQueryTextRectArray(kLFLen * (i - 1) + 1, 1);
      if (!checkQueryContentResult(currentLineLFAsArray, "runBug722639Test: " + i + "th line's \\n rect as array") ||
          !checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
        return;
      }
    }
    previousTop = currentLine.top;
  }
}

function runForceCommitTest()
{
  var events;
  function eventHandler(aEvent)
  {
    events.push(aEvent);
  }
  window.addEventListener("compositionstart", eventHandler, true);
  window.addEventListener("compositionupdate", eventHandler, true);
  window.addEventListener("compositionend", eventHandler, true);
  window.addEventListener("input", eventHandler, true);
  window.addEventListener("text", eventHandler, true);

  // Make the composition in textarea commit by click in the textarea
  textarea.focus();
  textarea.value = "";

  events = [];
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  is(events.length, 4,
     "runForceCommitTest: wrong event count #1");
  is(events[0].type, "compositionstart",
     "runForceCommitTest: the 1st event must be compositionstart #1");
  is(events[1].type, "compositionupdate",
     "runForceCommitTest: the 2nd event must be compositionupdate #1");
  is(events[2].type, "text",
     "runForceCommitTest: the 3rd event must be text #1");
  is(events[3].type, "input",
     "runForceCommitTest: the 4th event must be input #1");
  checkInputEvent(events[3], true, "insertCompositionText", "\u306E",
                  "runForceCommitTest #1");

  events = [];
  synthesizeMouseAtCenter(textarea, {});

  is(events.length, 3,
     "runForceCommitTest: wrong event count #2");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #2");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #2");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #2");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #2");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #2");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #2");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #2");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #2");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #2");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #2");

  // Make the composition in textarea commit by click in another editor (input)
  textarea.focus();
  textarea.value = "";
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  synthesizeMouseAtCenter(input, {});

  is(events.length, 3,
     "runForceCommitTest: wrong event count #3");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #3");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #3");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #3");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #3");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #3");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #3");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #3");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #3");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #3");
  ok(!getEditor(input).isComposing,
     "runForceCommitTest: the input has composition #3");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #3");
  is(input.value, "",
     "runForceCommitTest: the input has the committed text? #3");

  // Make the composition in textarea commit by blur()
  textarea.focus();
  textarea.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  textarea.blur();

  is(events.length, 3,
     "runForceCommitTest: wrong event count #4");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #4");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #4");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #4");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #4");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #4");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #4");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #4");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #4");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #4");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #4");

  // Make the composition in textarea commit by input.focus()
  textarea.focus();
  textarea.value = "";
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  input.focus();

  is(events.length, 3,
     "runForceCommitTest: wrong event count #5");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #5");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #5");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #5");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #5");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #5");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #5");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #5");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #5");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #5");
  ok(!getEditor(input).isComposing,
     "runForceCommitTest: the input has composition #5");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #5");
  is(input.value, "",
     "runForceCommitTest: the input has the committed text? #5");

  // Make the composition in textarea commit by click in another document's editor
  textarea.focus();
  textarea.value = "";
  textareaInFrame.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  synthesizeMouseAtCenter(textareaInFrame, {}, iframe.contentWindow);

  is(events.length, 3,
     "runForceCommitTest: wrong event count #6");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #6");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #6");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #6");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #6");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #6");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #6");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #6");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #6");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #6");
  ok(!getEditor(textareaInFrame).isComposing,
     "runForceCommitTest: the textarea in frame has composition #6");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #6");
  is(textareaInFrame.value, "",
     "runForceCommitTest: the textarea in frame has the committed text? #6");

  // Make the composition in textarea commit by another document's editor's focus()
  textarea.focus();
  textarea.value = "";
  textareaInFrame.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  textareaInFrame.focus();

  is(events.length, 3,
     "runForceCommitTest: wrong event count #7");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #7");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #7");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #7");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #7");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #7");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #7");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #7");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #7");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #7");
  ok(!getEditor(textareaInFrame).isComposing,
     "runForceCommitTest: the textarea in frame has composition #7");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #7");
  is(textareaInFrame.value, "",
     "runForceCommitTest: the textarea in frame has the committed text? #7");

  // Make the composition in a textarea commit by click in another editable document
  textarea.focus();
  textarea.value = "";
  iframe2.contentDocument.body.innerHTML = "Text in the Body";
  var iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);

  is(events.length, 3,
     "runForceCommitTest: wrong event count #8");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #8");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #8");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #8");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #8");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #8");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #8");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #8");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #8");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #8");
  ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
     "runForceCommitTest: the editable document has composition #8");
  is(textarea.value, "\u306E",
     "runForceCommitTest: the textarea doesn't have the committed text #8");
  is(iframe2.contentDocument.body.innerHTML, iframe2BodyInnerHTML,
     "runForceCommitTest: the editable document has the committed text? #8");

  // Make the composition in an editable document commit by click in it
  iframe2.contentWindow.focus();
  iframe2.contentDocument.body.innerHTML = "Text in the Body";
  iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    }, iframe2.contentWindow);

  events = [];
  synthesizeMouseAtCenter(iframe2.contentDocument.body, {}, iframe2.contentWindow);

  is(events.length, 3,
     "runForceCommitTest: wrong event count #9");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #9");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #9");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #9");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #9");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #9");
  is(events[0].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 1st event was fired on wrong event target #9");
  is(events[1].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 2nd event was fired on wrong event target #9");
  is(events[2].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 3rd event was fired on wrong event target #9");
  ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
     "runForceCommitTest: the editable document still has composition #9");
  ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
     iframe2.contentDocument.body.innerHTML.includes("\u306E"),
     "runForceCommitTest: the editable document doesn't have the committed text #9");

  // Make the composition in an editable document commit by click in another document's editor
  textarea.value = "";
  iframe2.contentWindow.focus();
  iframe2.contentDocument.body.innerHTML = "Text in the Body";
  iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    }, iframe2.contentWindow);

  events = [];
  synthesizeMouseAtCenter(textarea, {});

  is(events.length, 3,
     "runForceCommitTest: wrong event count #10");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #10");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #10");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #10");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #10");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #10");
  is(events[0].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 1st event was fired on wrong event target #10");
  is(events[1].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 2nd event was fired on wrong event target #10");
  is(events[2].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 3rd event was fired on wrong event target #10");
  ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
     "runForceCommitTest: the editable document still has composition #10");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea has composition #10");
  ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
     iframe2.contentDocument.body.innerHTML.includes("\u306E"),
     "runForceCommitTest: the editable document doesn't have the committed text #10");
  is(textarea.value, "",
     "runForceCommitTest: the textarea has the committed text? #10");

  // Make the composition in an editable document commit by click in the another editable document
  iframe2.contentWindow.focus();
  iframe2.contentDocument.body.innerHTML = "Text in the Body";
  iframe2BodyInnerHTML = iframe2.contentDocument.body.innerHTML;
  iframe3.contentDocument.body.innerHTML = "Text in the Body";
  iframe3BodyInnerHTML = iframe2.contentDocument.body.innerHTML;

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    }, iframe2.contentWindow);

  events = [];
  synthesizeMouseAtCenter(iframe3.contentDocument.body, {}, iframe3.contentWindow);

  is(events.length, 3,
     "runForceCommitTest: wrong event count #11");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #11");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #11");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #11");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #11");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #11");
  is(events[0].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 1st event was fired on wrong event target #11");
  is(events[1].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 2nd event was fired on wrong event target #11");
  is(events[2].target, iframe2.contentDocument.body,
     "runForceCommitTest: The 3rd event was fired on wrong event target #11");
  ok(!getHTMLEditorIMESupport(iframe2.contentWindow).isComposing,
     "runForceCommitTest: the editable document still has composition #11");
  ok(!getHTMLEditorIMESupport(iframe3.contentWindow).isComposing,
     "runForceCommitTest: the other editable document has composition #11");
  ok(iframe2.contentDocument.body.innerHTML != iframe2BodyInnerHTML &&
     iframe2.contentDocument.body.innerHTML.includes("\u306E"),
     "runForceCommitTest: the editable document doesn't have the committed text #11");
  is(iframe3.contentDocument.body.innerHTML, iframe3BodyInnerHTML,
     "runForceCommitTest: the other editable document has the committed text? #11");

  input.focus();
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  input.value = "set value";

  is(events.length, 3,
     "runForceCommitTest: wrong event count #12");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #12");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #12");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #12");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #12");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #12");
  is(events[0].target, input,
     "runForceCommitTest: The 1st event was fired on wrong event target #12");
  is(events[1].target, input,
     "runForceCommitTest: The 2nd event was fired on wrong event target #12");
  is(events[2].target, input,
     "runForceCommitTest: The 3rd event was fired on wrong event target #12");
  ok(!getEditor(input).isComposing,
     "runForceCommitTest: the input still has composition #12");
  is(input.value, "set value",
     "runForceCommitTest: the input doesn't have the set text #12");

  textarea.focus();
  textarea.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  textarea.value = "set value";

  is(events.length, 3,
     "runForceCommitTest: wrong event count #13");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #13");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #13");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #13");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #13");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #13");
  is(events[0].target, textarea,
     "runForceCommitTest: The 1st event was fired on wrong event target #13");
  is(events[1].target, textarea,
     "runForceCommitTest: The 2nd event was fired on wrong event target #13");
  is(events[2].target, textarea,
     "runForceCommitTest: The 3rd event was fired on wrong event target #13");
  ok(!getEditor(textarea).isComposing,
     "runForceCommitTest: the textarea still has composition #13");
  is(textarea.value, "set value",
     "runForceCommitTest: the textarea doesn't have the set text #13");

  input.focus();
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  input.value += " appended value";

  is(events.length, 3,
     "runForceCommitTest: wrong event count #14");
  is(events[0].type, "text",
     "runForceCommitTest: the 1st event must be text #14");
  is(events[1].type, "compositionend",
     "runForceCommitTest: the 2nd event must be compositionend #14");
  is(events[2].type, "input",
     "runForceCommitTest: the 3rd event must be input #14");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runForceCommitTest #14");
  is(events[1].data, "\u306E",
     "runForceCommitTest: compositionend has wrong data #14");
  is(events[0].target, input,
     "runForceCommitTest: The 1st event was fired on wrong event target #14");
  is(events[1].target, input,
     "runForceCommitTest: The 2nd event was fired on wrong event target #14");
  is(events[2].target, input,
     "runForceCommitTest: The 3rd event was fired on wrong event target #14");
  ok(!getEditor(input).isComposing,
     "runForceCommitTest: the input still has composition #14");
  is(input.value, "\u306E appended value",
     "runForceCommitTest: the input should have both composed text and appended text #14");

  input.focus();
  input.value = "abcd";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  input.value = "abcd\u306E";

  is(events.length, 0,
     "runForceCommitTest: setting same value to input with composition shouldn't cause any events #15");
  is(input.value, "abcd\u306E",
     "runForceCommitTest: the input has unexpected value #15");

  input.blur(); // commit composition

  textarea.focus();
  textarea.value = "abcd";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  textarea.value = "abcd\u306E";

  is(events.length, 0,
     "runForceCommitTest: setting same value to textarea with composition shouldn't cause any events #16");
  is(textarea.value, "abcd\u306E",
     "runForceCommitTest: the input has unexpected value #16");

  textarea.blur(); // commit composition

  window.removeEventListener("compositionstart", eventHandler, true);
  window.removeEventListener("compositionupdate", eventHandler, true);
  window.removeEventListener("compositionend", eventHandler, true);
  window.removeEventListener("input", eventHandler, true);
  window.removeEventListener("text", eventHandler, true);
}

function runNestedSettingValue()
{
  var isTesting = false;
  var events = [];
  function eventHandler(aEvent)
  {
    events.push(aEvent);
    if (isTesting) {
      aEvent.target.value += aEvent.type + ", ";
    }
  }
  window.addEventListener("compositionstart", eventHandler, true);
  window.addEventListener("compositionupdate", eventHandler, true);
  window.addEventListener("compositionend", eventHandler, true);
  window.addEventListener("input", eventHandler, true);
  window.addEventListener("text", eventHandler, true);

  textarea.focus();
  textarea.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  isTesting = true;
  textarea.value = "first setting value, ";
  isTesting = false;

  is(events.length, 3,
     "runNestedSettingValue: wrong event count #1");
  is(events[0].type, "text",
     "runNestedSettingValue: the 1st event must be text #1");
  is(events[1].type, "compositionend",
     "runNestedSettingValue: the 2nd event must be compositionend #1");
  is(events[2].type, "input",
     "runNestedSettingValue: the 3rd event must be input #1");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runNestedSettingValue #1");
  is(events[1].data, "\u306E",
     "runNestedSettingValue: compositionend has wrong data #1");
  is(events[0].target, textarea,
     "runNestedSettingValue: The 1st event was fired on wrong event target #1");
  is(events[1].target, textarea,
     "runNestedSettingValue: The 2nd event was fired on wrong event target #1");
  is(events[2].target, textarea,
     "runNestedSettingValue: The 3rd event was fired on wrong event target #1");
  ok(!getEditor(textarea).isComposing,
     "runNestedSettingValue: the textarea still has composition #1");
  is(textarea.value, "first setting value, text, compositionend, input, ",
     "runNestedSettingValue: the textarea should have all string set to value attribute");

  input.focus();
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  isTesting = true;
  input.value = "first setting value, ";
  isTesting = false;

  is(events.length, 3,
     "runNestedSettingValue: wrong event count #2");
  is(events[0].type, "text",
     "runNestedSettingValue: the 1st event must be text #2");
  is(events[1].type, "compositionend",
     "runNestedSettingValue: the 2nd event must be compositionend #2");
  is(events[2].type, "input",
     "runNestedSettingValue: the 3rd event must be input #2");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runNestedSettingValue #2");
  is(events[1].data, "\u306E",
     "runNestedSettingValue: compositionend has wrong data #2");
  is(events[0].target, input,
     "runNestedSettingValue: The 1st event was fired on wrong event target #2");
  is(events[1].target, input,
     "runNestedSettingValue: The 2nd event was fired on wrong event target #2");
  is(events[2].target, input,
     "runNestedSettingValue: The 3rd event was fired on wrong event target #2");
  ok(!getEditor(input).isComposing,
     "runNestedSettingValue: the input still has composition #2");
  is(textarea.value, "first setting value, text, compositionend, input, ",
     "runNestedSettingValue: the input should have all string set to value attribute #2");

  textarea.focus();
  textarea.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  isTesting = true;
  textarea.setRangeText("first setting value, ");
  isTesting = false;

  is(events.length, 3,
     "runNestedSettingValue: wrong event count #3");
  is(events[0].type, "text",
     "runNestedSettingValue: the 1st event must be text #3");
  is(events[1].type, "compositionend",
     "runNestedSettingValue: the 2nd event must be compositionend #3");
  is(events[2].type, "input",
     "runNestedSettingValue: the 3rd event must be input #3");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runNestedSettingValue #3");
  is(events[1].data, "\u306E",
     "runNestedSettingValue: compositionend has wrong data #3");
  is(events[0].target, textarea,
     "runNestedSettingValue: The 1st event was fired on wrong event target #3");
  is(events[1].target, textarea,
     "runNestedSettingValue: The 2nd event was fired on wrong event target #3");
  is(events[2].target, textarea,
     "runNestedSettingValue: The 3rd event was fired on wrong event target #3");
  ok(!getEditor(textarea).isComposing,
     "runNestedSettingValue: the textarea still has composition #3");
  is(textarea.value, "\u306Efirst setting value, text, compositionend, input, ",
     "runNestedSettingValue: the textarea should have appended by setRangeText() and all string set to value attribute #3");

  input.focus();
  input.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  events = [];
  isTesting = true;
  input.setRangeText("first setting value, ");
  isTesting = false;

  is(events.length, 3,
     "runNestedSettingValue: wrong event count #4");
  is(events[0].type, "text",
     "runNestedSettingValue: the 1st event must be text #4");
  is(events[1].type, "compositionend",
     "runNestedSettingValue: the 2nd event must be compositionend #4");
  is(events[2].type, "input",
     "runNestedSettingValue: the 3rd event must be input #4");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runNestedSettingValue #4");
  is(events[1].data, "\u306E",
     "runNestedSettingValue: compositionend has wrong data #4");
  is(events[0].target, input,
     "runNestedSettingValue: The 1st event was fired on wrong event target #4");
  is(events[1].target, input,
     "runNestedSettingValue: The 2nd event was fired on wrong event target #4");
  is(events[2].target, input,
     "runNestedSettingValue: The 3rd event was fired on wrong event target #4");
  ok(!getEditor(input).isComposing,
     "runNestedSettingValue: the input still has composition #4");
  is(textarea.value, "\u306Efirst setting value, text, compositionend, input, ",
     "runNestedSettingValue: the input should have all string appended by setRangeText() and set to value attribute #4");

  window.removeEventListener("compositionstart", eventHandler, true);
  window.removeEventListener("compositionupdate", eventHandler, true);
  window.removeEventListener("compositionend", eventHandler, true);
  window.removeEventListener("input", eventHandler, true);
  window.removeEventListener("text", eventHandler, true);

}

function runAsyncForceCommitTest()
{
  var events;
  function eventHandler(aEvent)
  {
    events.push(aEvent);
  };

  // If IME commits composition for a request, TextComposition commits
  // composition automatically because most web apps must expect that active
  // composition should be committed synchronously.  Therefore, in this case,
  // a click during composition should cause committing composition
  // synchronously and delayed commit shouldn't cause composition events.
  var commitRequested = false;
  function callback(aTIP, aNotification)
  {
    ok(true, aNotification.type);
    if (aNotification.type != "request-to-commit") {
      return true;
    }
    commitRequested = true;
    setTimeout(function () {
      events = [];
      aTIP.commitComposition();

      is(events.length, 0,
         "runAsyncForceCommitTest: composition events shouldn't been fired by asynchronous call of nsITextInputProcessor.commitComposition()");

      window.removeEventListener("compositionstart", eventHandler, true);
      window.removeEventListener("compositionupdate", eventHandler, true);
      window.removeEventListener("compositionend", eventHandler, true);
      window.removeEventListener("input", eventHandler, true);
      window.removeEventListener("text", eventHandler, true);

      SimpleTest.executeSoon(continueTest);
    }, 1);
    return true;
  };

  window.addEventListener("compositionstart", eventHandler, true);
  window.addEventListener("compositionupdate", eventHandler, true);
  window.addEventListener("compositionend", eventHandler, true);
  window.addEventListener("input", eventHandler, true);
  window.addEventListener("text", eventHandler, true);

  // Make the composition in textarea commit by click in the textarea
  textarea.focus();
  textarea.value = "";

  events = [];
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    }, window, callback);

  is(events.length, 4,
     "runAsyncForceCommitTest: wrong event count #1");
  is(events[0].type, "compositionstart",
     "runAsyncForceCommitTest: the 1st event must be compositionstart #1");
  is(events[1].type, "compositionupdate",
     "runAsyncForceCommitTest: the 2nd event must be compositionupdate #1");
  is(events[2].type, "text",
     "runAsyncForceCommitTest: the 3rd event must be text #1");
  is(events[3].type, "input",
     "runAsyncForceCommitTest: the 4th event must be input #1");
  checkInputEvent(events[3], true, "insertCompositionText", "\u306E",
                  "runAsyncForceCommitTest #1");

  events = [];
  commitRequested = false;
  synthesizeMouseAtCenter(textarea, {});

  ok(commitRequested,
     "runAsyncForceCommitTest: \"request-to-commit\" should've been notified");
  is(events.length, 3,
     "runAsyncForceCommitTest: wrong event count #2");
  is(events[0].type, "text",
     "runAsyncForceCommitTest: the 1st event must be text #2");
  is(events[1].type, "compositionend",
     "runAsyncForceCommitTest: the 2nd event must be compositionend #2");
  is(events[2].type, "input",
     "runAsyncForceCommitTest: the 3rd event must be input #2");
  checkInputEvent(events[2], false, "insertCompositionText", "\u306E",
                  "runAsyncForceCommitTest #2");
  is(events[1].data, "\u306E",
     "runAsyncForceCommitTest: compositionend has wrong data #2");
  is(events[0].target, textarea,
     "runAsyncForceCommitTest: The 1st event was fired on wrong event target #2");
  is(events[1].target, textarea,
     "runAsyncForceCommitTest: The 2nd event was fired on wrong event target #2");
  is(events[2].target, textarea,
     "runAsyncForceCommitTest: The 3rd event was fired on wrong event target #2");
  ok(!getEditor(textarea).isComposing,
     "runAsyncForceCommitTest: the textarea still has composition #2");
  is(textarea.value, "\u306E",
     "runAsyncForceCommitTest: the textarea doesn't have the committed text #2");
}

function runBug811755Test()
{
  iframe2.contentDocument.body.innerHTML = "<div>content<br/></div>";
  iframe2.contentWindow.focus();
  // Query everything
  var textContent = synthesizeQueryTextContent(0, 10);
  if (!checkQueryContentResult(textContent, "runBug811755Test: synthesizeQueryTextContent #1")) {
    return false;
  }
  // Query everything but specify exact end offset, which should be immediately after the <br> node
  // If PreContentIterator is used, the next node after <br> is the node after </div>.
  // If ContentIterator is used, the next node is the <div> node itself. In this case, the end
  // node ends up being before the start node, and an empty string is returned.
  var queryContent = synthesizeQueryTextContent(0, textContent.text.length);
  if (!checkQueryContentResult(queryContent, "runBug811755Test: synthesizeQueryTextContent #2")) {
    return false;
  }
  is(queryContent.text, textContent.text, "runBug811755Test: two queried texts don't match");
  return queryContent.text == textContent.text;
}

function runIsComposingTest()
{
  var expectedIsComposing = false;
  var descriptionBase = "runIsComposingTest: ";
  var description = "";

  function eventHandler(aEvent)
  {
    if (aEvent.type == "keydown" || aEvent.type == "keyup") {
      is(aEvent.isComposing, expectedIsComposing,
         "runIsComposingTest: " + description + " (type=" + aEvent.type + ", key=" + aEvent.key + ")");
    } else if (aEvent.type == "keypress") {
      // keypress event shouldn't be fired during composition so that isComposing should be always false.
      is(aEvent.isComposing, false,
         "runIsComposingTest: " + description + " (type=" + aEvent.type + ")");
    } else {
      checkInputEvent(aEvent, expectedIsComposing, "insertCompositionText", "\u3042",
                      `runIsComposingTest: ${description}`);
    }
  }

  function onComposition(aEvent)
  {
    if (aEvent.type == "compositionstart") {
      expectedIsComposing = true;
    } else if (aEvent.type == "compositionend") {
      expectedIsComposing = false;
    }
  }

  textarea.addEventListener("keydown", eventHandler, true);
  textarea.addEventListener("keypress", eventHandler, true);
  textarea.addEventListener("keyup", eventHandler, true);
  textarea.addEventListener("input", eventHandler, true);
  textarea.addEventListener("compositionstart", onComposition, true);
  textarea.addEventListener("compositionend", onComposition, true);

  textarea.focus();
  textarea.value = "";

  // XXX These cases shouldn't occur in actual native key events because we
  //     don't dispatch key events while composition (bug 354358).
  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
  description = "events before dispatching compositionstart";
  synthesizeKey("KEY_ArrowLeft");

  description = "events after dispatching compositionchange";
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 },
      "key": { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
    });

  // Although, firing keypress event during composition is a bug.
  synthesizeKey("KEY_Insert");

  description = "events for committing composition string";

  synthesizeComposition({ type: "compositioncommitasis",
                          key: { key: "KEY_Enter", code: "Enter", type: "keydown" } });

  // input event will be fired by synthesizing compositionend event.
  // Then, its isComposing should be false.
  description = "events after dispatching compositioncommitasis";
  synthesizeKey("KEY_Enter", {type: "keyup"});

  SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");

  textarea.removeEventListener("keydown", eventHandler, true);
  textarea.removeEventListener("keypress", eventHandler, true);
  textarea.removeEventListener("keyup", eventHandler, true);
  textarea.removeEventListener("input", eventHandler, true);
  textarea.removeEventListener("compositionstart", onComposition, true);
  textarea.removeEventListener("compositionend", onComposition, true);

  textarea.value = "";
}

function runRedundantChangeTest()
{
  textarea.focus();

  var result = [];
  function clearResult()
  {
    result = [];
  }

  function handler(aEvent)
  {
    result.push(aEvent);
  }

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);
  textarea.addEventListener("input", handler, true);
  textarea.addEventListener("text", handler, true);

  textarea.value = "";

  // synthesize change event
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  is(result.length, 3,
     "runRedundantChangeTest: 3 events should be fired after synthesizing composition change #1");
  is(result[0].type, "compositionupdate",
     "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
  is(result[1].type, "text",
     "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
  is(result[2].type, "input",
     "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
  checkInputEvent(result[2], true, "insertCompositionText", "\u3042",
                  "runRedundantChangeTest: after synthesizing composition change #1");
  is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");

  // synthesize another change event
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042\u3044",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  is(result.length, 3,
     "runRedundantChangeTest: 3 events should be fired after synthesizing composition change #2");
  is(result[0].type, "compositionupdate",
     "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
  is(result[1].type, "text",
     "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
  is(result[2].type, "input",
     "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
  checkInputEvent(result[2], true, "insertCompositionText", "\u3042\u3044",
                  "runRedundantChangeTest: after synthesizing composition change #2");
  is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");

  // synthesize same change event again
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3042\u3044",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  is(result.length, 0, "runRedundantChangeTest: no events should be fired after synthesizing composition change again");
  is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");

  // synthesize commit-as-is
  clearResult();
  synthesizeComposition({ type: "compositioncommitasis" });
  is(result.length, 3,
     "runRedundantChangeTest: 3 events be fired after synthesizing composition commit-as-is");
  is(result[0].type, "text",
     "runRedundantChangeTest: text shouldn't be fired after synthesizing composition commit-as-is for removing the ranges");
  is(result[1].type, "compositionend",
     "runRedundantChangeTest: compositionend should be fired after synthesizing composition commit-as-is");
  is(result[2].type, "input",
     "runRedundantChangeTest: input shouldn't be fired before compositionend at synthesizing commit-as-is");
  checkInputEvent(result[2], false, "insertCompositionText", "\u3042\u3044",
                  "runRedundantChangeTest: at synthesizing commit-as-is");
  is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has the commit string");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);
  textarea.removeEventListener("input", handler, true);
  textarea.removeEventListener("text", handler, true);
}

function runNotRedundantChangeTest()
{
  textarea.focus();

  var result = [];
  function clearResult()
  {
    result = [];
  }

  function handler(aEvent)
  {
    result.push(aEvent);
  }

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);
  textarea.addEventListener("input", handler, true);
  textarea.addEventListener("text", handler, true);

  textarea.value = "abcde";

  // synthesize change event with non-null ranges
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDE",
        "clauses":
        [
          { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 }
    });

  is(result.length, 3,
     "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with non-null ranges");
  is(result[0].type, "compositionupdate",
     "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges");
  is(result[1].type, "text",
     "runNotRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string with non-null ranges");
  is(result[2].type, "input",
     "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges");
  checkInputEvent(result[2], true, "insertCompositionText", "ABCDE",
                  "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges");
  is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #1");

  // synthesize change event with null ranges
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDE",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
    });
  is(result.length, 2,
     "runNotRedundantChangeTest: 2 events should be fired after synthesizing composition change with null ranges after non-null ranges");
  is(result[0].type, "text",
     "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");
  is(result[1].type, "input",
     "runNotRedundantChangeTest: input should be fired after synthesizing composition change with null ranges after non-null ranges");
  checkInputEvent(result[1], true, "insertCompositionText", "ABCDE",
                  "runNotRedundantChangeTest: after synthesizing composition change with null ranges after non-null ranges");
  is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #2");

  // synthesize change event with non-null ranges
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDE",
        "clauses":
        [
          { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 }
    });

  is(result.length, 2,
     "runNotRedundantChangeTest: 2 events should be fired after synthesizing composition change with null ranges after non-null ranges");
  is(result[0].type, "text",
     "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");
  is(result[1].type, "input",
     "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after null ranges");
  checkInputEvent(result[1], true, "insertCompositionText", "ABCDE",
                  "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after null ranges");
  is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #3");

  // synthesize change event with empty data and null ranges
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "",
        "clauses":
        [
          { "length": 0, "attr": 0 }
        ]
      },
    });
  is(result.length, 3,
     "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
  is(result[0].type, "compositionupdate",
     "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
  is(result[1].type, "text",
     "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");
  is(result[2].type, "input",
     "runNotRedundantChangeTest: input should be fired after synthesizing composition change with empty data and null ranges after non-null ranges");
  checkInputEvent(result[2], true, "insertCompositionText", "",
                  "runNotRedundantChangeTest: after synthesizing composition change with empty data and null ranges after non-null ranges");
  is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #1");

  // synthesize change event with non-null ranges
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": "ABCDE",
        "clauses":
        [
          { "length": 5, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 }
    });

  is(result.length, 3,
     "runNotRedundantChangeTest: 3 events should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
  is(result[0].type, "compositionupdate",
     "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
  is(result[1].type, "text",
     "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");
  is(result[2].type, "input",
     "runNotRedundantChangeTest: input should be fired after synthesizing composition change with non-null ranges after empty data and null ranges");
  checkInputEvent(result[2], true, "insertCompositionText", "ABCDE",
                  "runNotRedundantChangeTest: after synthesizing composition change with non-null ranges after empty data and null ranges");
  is(textarea.value, "abcdeABCDE", "runNotRedundantChangeTest: textarea has uncommitted string #4");

  clearResult();
  synthesizeComposition({ type: "compositioncommit", data: "" });

  is(result.length, 4,
     "runNotRedundantChangeTest: 4 events should be fired after synthesizing composition commit with empty data after non-empty data");
  is(result[0].type, "compositionupdate",
     "runNotRedundantChangeTest: compositionupdate should be fired after synthesizing composition commit with empty data after non-empty data");
  is(result[1].type, "text",
     "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");
  is(result[2].type, "compositionend",
     "runNotRedundantChangeTest: compositionend should be fired after synthesizing composition commit with empty data after non-empty data");
  is(result[3].type, "input",
     "runNotRedundantChangeTest: input should be fired after compositionend after synthesizing composition change with empty data after non-empty data");
  checkInputEvent(result[3], false, "insertCompositionText", "",
                  "runNotRedundantChangeTest: after synthesizing composition change with empty data after non-empty data");
  is(textarea.value, "abcde", "runNotRedundantChangeTest: textarea doesn't have uncommitted string #2");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);
  textarea.removeEventListener("input", handler, true);
  textarea.removeEventListener("text", handler, true);
}

function runNativeLineBreakerTest()
{
  textarea.focus();

  var result = {};
  function clearResult()
  {
    result = { compositionupdate: null, compositionend: null };
  }

  function handler(aEvent)
  {
    result[aEvent.type] = aEvent.data;
  }

  SpecialPowers.setBoolPref("dom.compositionevent.allow_control_characters", false);

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);

  // '\n' in composition string shouldn't be changed.
  clearResult();
  textarea.value = "";
  var clauses = [ "abc\n", "def\n\ng", "hi\n", "\njkl" ];
  var caret = clauses[0] + clauses[1] + clauses[2];
  synthesizeCompositionChange(
    { "composition":
      { "string": clauses.join(''),
        "clauses":
        [
          { "length": clauses[0].length,
            "attr": COMPOSITION_ATTR_RAW_CLAUSE },
          { "length": clauses[1].length,
            "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
          { "length": clauses[2].length,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": clauses[3].length,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
        ]
      },
      "caret": { "start": caret.length, "length": 0 }
    });

  checkSelection(caret.replace(/\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#1");
  checkIMESelection("RawClause", true, 0, clauses[0].replace(/\n/g, kLF), "runNativeLineBreakerTest: \\n shouldn't be replaced with any character #1");
  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");
  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");
  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");
  is(result.compositionupdate, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionupdate.data shouldn't be removed nor replaced with other characters #1");
  is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #1");

  synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
  checkSelection(clauses.join('').replace(/\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#2");
  is(result.compositionend, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in compositionend.data shouldn't be removed nor replaced with other characters #2");
  is(textarea.value, clauses.join('').replace(/\n/g, "\n"), "runNativeLineBreakerTest: \\n in textarea.value shouldn't be removed nor replaced with other characters #2");

  // \r\n in composition string should be replaced with \n.
  clearResult();
  textarea.value = "";
  clauses = [ "abc\r\n", "def\r\n\r\ng", "hi\r\n", "\r\njkl" ];
  caret = clauses[0] + clauses[1] + clauses[2];
  synthesizeCompositionChange(
    { "composition":
      { "string": clauses.join(''),
        "clauses":
        [
          { "length": clauses[0].length,
            "attr": COMPOSITION_ATTR_RAW_CLAUSE },
          { "length": clauses[1].length,
            "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
          { "length": clauses[2].length,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": clauses[3].length,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
        ]
      },
      "caret": { "start": caret.length, "length": 0 }
    });

  checkSelection(caret.replace(/\r\n/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#3");
  checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r\n/g, kLF), "runNativeLineBreakerTest: \\r\\n should be replaced with \\n #3");
  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");
  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");
  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");
  is(result.compositionupdate, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionudpate.data should be replaced with \\n #3");
  is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #3");

  synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
  checkSelection(clauses.join('').replace(/\r\n/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#4");
  is(result.compositionend, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in compositionend.data should be replaced with \\n #4");
  is(textarea.value, clauses.join('').replace(/\r\n/g, "\n"), "runNativeLineBreakerTest: \\r\\n in textarea.value should be replaced with \\n #4");

  // \r (not followed by \n) in composition string should be replaced with \n.
  clearResult();
  textarea.value = "";
  clauses = [ "abc\r", "def\r\rg", "hi\r", "\rjkl" ];
  caret = clauses[0] + clauses[1] + clauses[2];
  synthesizeCompositionChange(
    { "composition":
      { "string": clauses.join(''),
        "clauses":
        [
          { "length": clauses[0].length,
            "attr": COMPOSITION_ATTR_RAW_CLAUSE },
          { "length": clauses[1].length,
            "attr": COMPOSITION_ATTR_SELECTED_RAW_CLAUSE },
          { "length": clauses[2].length,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
          { "length": clauses[3].length,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
        ]
      },
      "caret": { "start": caret.length, "length": 0 }
    });

  checkSelection(caret.replace(/\r/g, "\n").length + (kLFLen - 1) * 4, "", "runNativeLineBreakerTest", "#5");
  checkIMESelection("RawClause", true, 0, clauses[0].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
  checkIMESelection("SelectedRawClause", true, clauses[0].replace(/\r/g, kLF).length, clauses[1].replace(/\r/g, kLF), "runNativeLineBreakerTest: \\r should be replaced with \\n #5");
  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");
  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");
  is(result.compositionupdate, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionupdate.data should be replaced with \\n #5");
  is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #5");

  synthesizeComposition({ type: "compositioncommit", data: clauses.join('') });
  checkSelection(clauses.join('').replace(/\r/g, "\n").length + (kLFLen - 1) * 5, "", "runNativeLineBreakerTest", "#6");
  is(result.compositionend, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in compositionend.data should be replaced with \\n #6");
  is(textarea.value, clauses.join('').replace(/\r/g, "\n"), "runNativeLineBreakerTest: \\r in textarea.value should be replaced with \\n #6");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);

  SpecialPowers.clearUserPref("dom.compositionevent.allow_control_characters");
}

function runControlCharTest()
{
  textarea.focus();

  var result = {};
  function clearResult()
  {
    result = { compositionupdate: null, compositionend: null };
  }

  function handler(aEvent)
  {
    result[aEvent.type] = aEvent.data;
  }

  textarea.addEventListener("compositionupdate", handler, true);
  textarea.addEventListener("compositionend", handler, true);

  textarea.value = "";

  var controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
  var allowedChars = "\t\n\n";

  var data = "AB" + controlChars + "CD" + controlChars + "EF";
  var removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";

  var DIndex = data.indexOf("D");
  var removedDIndex = removedData.indexOf("D");

  // input string contains control characters
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": data,
        "clauses":
        [
          { "length": DIndex,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": data.length - DIndex,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
        ]
      },
      "caret": { "start": DIndex, "length": 0 }
    });

  checkSelection(removedDIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#1")

  is(result.compositionupdate, removedData, "runControlCharTest: control characters in event.data should be removed in compositionupdate event #1");
  is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #1");

  synthesizeComposition({ type: "compositioncommit", data: data });

  is(result.compositionend, removedData, "runControlCharTest: control characters in event.data should be removed in compositionend event #2");
  is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #2");

  textarea.value = "";

  clearResult();

  SpecialPowers.setBoolPref("dom.compositionevent.allow_control_characters", true);

  // input string contains control characters, allowing control characters
  clearResult();
  synthesizeCompositionChange(
    { "composition":
      { "string": data,
        "clauses":
        [
          { "length": DIndex,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": data.length - DIndex,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
        ]
      },
      "caret": { "start": DIndex, "length": 0 }
    });

  checkSelection(DIndex + (kLFLen - 1) * 2, "", "runControlCharTest", "#3")

  is(result.compositionupdate, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionupdate event #3");
  is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #3");

  synthesizeComposition({ type: "compositioncommit", data: data });

  is(result.compositionend, data.replace(/\r/g, "\n"), "runControlCharTest: control characters in event.data should not be removed in compositionend event #4");
  is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #4");

  SpecialPowers.clearUserPref("dom.compositionevent.allow_control_characters");

  textarea.removeEventListener("compositionupdate", handler, true);
  textarea.removeEventListener("compositionend", handler, true);
}

function runRemoveContentTest()
{
  var events = [];
  function eventHandler(aEvent)
  {
    events.push(aEvent);
  }
  textarea.addEventListener("compositionstart", eventHandler, true);
  textarea.addEventListener("compositionupdate", eventHandler, true);
  textarea.addEventListener("compositionend", eventHandler, true);
  textarea.addEventListener("input", eventHandler, true);
  textarea.addEventListener("text", eventHandler, true);

  textarea.focus();
  textarea.value = "";

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u306E",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  var nextSibling = textarea.nextSibling;
  var parent = textarea.parentElement;

  events = [];
  parent.removeChild(textarea);

  hitEventLoop(function () {
    // XXX Currently, "input" event isn't fired on removed content.
    is(events.length, 3,
       "runRemoveContentTest: wrong event count #1");
    is(events[0].type, "compositionupdate",
       "runRemoveContentTest: the 1st event must be compositionupdate #1");
    is(events[1].type, "text",
       "runRemoveContentTest: the 2nd event must be text #1");
    is(events[2].type, "compositionend",
       "runRemoveContentTest: the 3rd event must be compositionend #1");
    is(events[0].data, "",
       "runRemoveContentTest: compositionupdate has wrong data #1");
    is(events[2].data, "",
       "runRemoveContentTest: compositionend has wrong data #1");
    is(events[0].target, textarea,
       "runRemoveContentTest: The 1st event was fired on wrong event target #1");
    is(events[1].target, textarea,
       "runRemoveContentTest: The 2nd event was fired on wrong event target #1");
    is(events[2].target, textarea,
       "runRemoveContentTest: The 3rd event was fired on wrong event target #1");
    ok(!getEditor(textarea).isComposing,
       "runRemoveContentTest: the textarea still has composition #1");
    todo_is(textarea.value, "",
       "runRemoveContentTest: the textarea has the committed text? #1");

    parent.insertBefore(textarea, nextSibling);

    textarea.focus();
    textarea.value = "";

    synthesizeComposition({ type: "compositionstart" });

    events = [];
    parent.removeChild(textarea);

    hitEventLoop(function () {
      // XXX Currently, "input" event isn't fired on removed content.
      is(events.length, 2,
         "runRemoveContentTest: wrong event count #2");
      is(events[0].type, "text",
         "runRemoveContentTest: the 1st event must be text #2");
      is(events[1].type, "compositionend",
         "runRemoveContentTest: the 1st event must be compositionend #2");
      is(events[1].data, "",
         "runRemoveContentTest: compositionupdate has wrong data #2");
      is(events[1].target, textarea,
         "runRemoveContentTest: The 1st event was fired on wrong event target #2");
      ok(!getEditor(textarea).isComposing,
         "runRemoveContentTest: the textarea still has composition #2");
      is(textarea.value, "",
         "runRemoveContentTest: the textarea has the committed text? #2");

      parent.insertBefore(textarea, nextSibling);

      textarea.removeEventListener("compositionstart", eventHandler, true);
      textarea.removeEventListener("compositionupdate", eventHandler, true);
      textarea.removeEventListener("compositionend", eventHandler, true);
      textarea.removeEventListener("input", eventHandler, true);
      textarea.removeEventListener("text", eventHandler, true);

      SimpleTest.executeSoon(continueTest);
    }, 50);
  }, 50);
}

function runTestOnAnotherContext(aPanelOrFrame, aFocusedEditor, aTestName)
{
  aFocusedEditor.value = "";

  var editorRect = synthesizeQueryEditorRect();
  if (!checkQueryContentResult(editorRect, aTestName + ": editorRect")) {
    return;
  }

  var r = aPanelOrFrame.getBoundingClientRect();
  var parentRect = { "left": r.left, "top": r.top, "width": r.right - r.left,
                     "height": r.bottom - r.top };
  checkRectContainsRect(editorRect, parentRect, aTestName +
                        ": the editor rect coordinates are wrong");

  // input characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3078\u3093\u3057\u3093",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 }
    });

  if (!checkContent("\u3078\u3093\u3057\u3093", aTestName, "#1-1") ||
      !checkSelection(4, "", aTestName, "#1-1")) {
    return;
  }

  // convert them #1
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u8FD4\u4FE1",
        "clauses":
        [
          { "length": 2,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  if (!checkContent("\u8FD4\u4FE1", aTestName, "#1-2") ||
      !checkSelection(2, "", aTestName, "#1-2")) {
    return;
  }

  // convert them #2
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u5909\u8EAB",
        "clauses":
        [
          { "length": 2,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  if (!checkContent("\u5909\u8EAB", aTestName, "#1-3") ||
      !checkSelection(2, "", aTestName, "#1-3")) {
    return;
  }

  // commit them
  synthesizeComposition({ type: "compositioncommitasis" });
  if (!checkContent("\u5909\u8EAB", aTestName, "#1-4") ||
      !checkSelection(2, "", aTestName, "#1-4")) {
    return;
  }

  is(aFocusedEditor.value, "\u5909\u8EAB",
     aTestName + ": composition isn't in the focused editor");
  if (aFocusedEditor.value != "\u5909\u8EAB") {
    return;
  }

  var textRect = synthesizeQueryTextRect(0, 1);
  var caretRect = synthesizeQueryCaretRect(2);
  if (!checkQueryContentResult(textRect,
                               aTestName + ": synthesizeQueryTextRect") ||
      !checkQueryContentResult(caretRect,
                               aTestName + ": synthesizeQueryCaretRect")) {
    return;
  }
  checkRectContainsRect(textRect, editorRect, aTestName + ":testRect");
  checkRectContainsRect(caretRect, editorRect, aTestName + ":caretRect");
}

function runFrameTest()
{
  textareaInFrame.focus();
  runTestOnAnotherContext(iframe, textareaInFrame, "runFrameTest");
  runCharAtPointTest(textareaInFrame, "textarea in the iframe");
}

var gPanelShown = false;
var gPanelFocused = false;
function onPanelShown(aEvent)
{
  gPanelShown = true;
  textbox.focus();
  setTimeout(doPanelTest, 0);
}

function onFocusPanelTextbox(aEvent)
{
  gPanelFocused = true;
  setTimeout(doPanelTest, 0);
}

var gIsPanelHiding = false;
var gIsRunPanelTestInternal = false;
function doPanelTest()
{
  if (!gPanelFocused || !gPanelShown) {
    return;
  }
  if (gIsRunPanelTestInternal) {
    return;
  }
  gIsRunPanelTestInternal = true;
  runTestOnAnotherContext(panel, textbox, "runPanelTest");
  runCharAtPointTest(textbox, "textbox in the panel");
  gIsPanelHiding = true;
  panel.hidePopup();
}

function onPanelHidden(aEvent)
{
  panel.hidden = true;
  ok(gIsPanelHiding, "runPanelTest: the panel is hidden unexpectedly");
  SimpleTest.executeSoon(continueTest);
}

function runPanelTest()
{
  panel.hidden = false;
  panel.openPopupAtScreen(window.screenX + window.outerWidth, 0, false);
}

function runMaxLengthTest()
{
  input.maxLength = 1;
  input.value = "";
  input.focus();

  var kDesc ="runMaxLengthTest";

  // input first character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkContent("\u3089", kDesc, "#1-1") ||
      !checkSelection(1, "", kDesc, "#1-1")) {
    return;
  }

  // input second character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC", kDesc, "#1-2") ||
      !checkSelection(2, "", kDesc, "#1-2")) {
    return;
  }

  // input third character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-3") ||
      !checkSelection(3, "", kDesc, "#1-3")) {
    return;
  }

  // input fourth character
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-4") ||
      !checkSelection(4, "", kDesc, "#1-4")) {
    return;
  }


  // backspace
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081", kDesc, "#1-5") ||
      !checkSelection(3, "", kDesc, "#1-5")) {
    return;
  }

  // re-input
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093",
        "clauses":
        [
          { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093", kDesc, "#1-6") ||
      !checkSelection(4, "", kDesc, "#1-6")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055",
        "clauses":
        [
          { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 5, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", kDesc, "#1-7") ||
      !checkSelection(5, "", kDesc, "#1-7")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
        "clauses":
        [
          { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 6, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", kDesc, "#1-8") ||
      !checkSelection(6, "", kDesc, "#1-8")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
        "clauses":
        [
          { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 7, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
                    kDesc, "#1-8") ||
      !checkSelection(7, "", kDesc, "#1-8")) {
    return;
  }

  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
        "clauses":
        [
          { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 8, "length": 0 }
    });

  if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
                    kDesc, "#1-9") ||
      !checkSelection(8, "", kDesc, "#1-9")) {
    return;
  }

  // convert
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
        "clauses":
        [
          { "length": 4,
            "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
          { "length": 2,
            "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
        ]
      },
      "caret": { "start": 4, "length": 0 }
    });

  if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8", kDesc, "#1-10") ||
      !checkSelection(4, "", kDesc, "#1-10")) {
    return;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommitasis" });
  if (!checkContent("\u30E9", kDesc, "#1-11") ||
      !checkSelection(1, "", kDesc, "#1-11")) {
    return;
  }

  // input characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u3057",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkContent("\u30E9\u3057", kDesc, "#2-1") ||
      !checkSelection(1 + 1, "", kDesc, "#2-1")) {
    return;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommit", data: "\u3058" });
  if (!checkContent("\u30E9", kDesc, "#2-2") ||
      !checkSelection(1 + 0, "", kDesc, "#2-2")) {
    return;
  }

  // Undo
  synthesizeKey("Z", {accelKey: true});

  // XXX this is unexpected behavior, see bug 258291
  if (!checkContent("\u30E9", kDesc, "#3-1") ||
      !checkSelection(1 + 0, "", kDesc, "#3-1")) {
    return;
  }

  // Undo
  synthesizeKey("Z", {accelKey: true});
  if (!checkContent("", kDesc, "#3-2") ||
      !checkSelection(0, "", kDesc, "#3-2")) {
    return;
  }

  // Redo
  synthesizeKey("Z", {accelKey: true, shiftKey: true});
  if (!checkContent("\u30E9", kDesc, "#3-3") ||
      !checkSelection(1, "", kDesc, "#3-3")) {
    return;
  }

  // Redo
  synthesizeKey("Z", {accelKey: true, shiftKey: true});
  if (!checkContent("\u30E9", kDesc, "#3-4") ||
      !checkSelection(1 + 0, "", kDesc, "#3-4")) {
    return;
  }

  // The input element whose content length is already maxlength and
  // the carest is at start of the content.
  input.value = "X";
  input.selectionStart = input.selectionEnd = 0;

  // input characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u9B54",
        "clauses":
        [
          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 1, "length": 0 }
    });

  if (!checkContent("\u9B54X", kDesc, "#4-1") ||
      !checkSelection(1, "", kDesc, "#4-1")) {
    return;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommitasis" });

  // The input text must be discarded. Then, the caret position shouldn't be
  // updated from its position at compositionstart.
  if (!checkContent("X", kDesc, "#4-2") ||
      !checkSelection(0, "", kDesc, "#4-2")) {
    return;
  }

  // input characters
  synthesizeCompositionChange(
    { "composition":
      { "string": "\u9B54\u6CD5",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 }
    });

  if (!checkContent("\u9B54\u6CD5X", kDesc, "#5-1") ||
      !checkSelection(2, "", kDesc, "#5-1")) {
    return;
  }

  // commit the composition string
  synthesizeComposition({ type: "compositioncommitasis" });

  if (!checkContent("X", kDesc, "#5-2") ||
      !checkSelection(0, "", kDesc, "#5-2")) {
    return;
  }
}

function* runEditorReframeTests()
{
  function runEditorReframeTest(aEditor, aWindow, aEventType)
  {
    function getValue()
    {
      return aEditor == contenteditable ?
        aEditor.innerHTML.replace("<br>", "") : aEditor.value;
    }

    var description = "runEditorReframeTest(" + aEditor.id + ", \"" + aEventType + "\"): ";

    var tests = [
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "a",
                "clauses":
                [
                  { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 1, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "a", description + "Typing 'a'");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "ab",
                "clauses":
                [
                  { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 2, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ab", description + "Typing 'b' next to 'a'");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "abc",
                "clauses":
                [
                  { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 3, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "abc", description + "Typing 'c' next to 'ab'");
        },
      },
      { test: function () {
        synthesizeCompositionChange(
          { "composition":
            { "string": "abc",
              "clauses":
              [
                { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
                { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
              ]
            },
            "caret": { "start": 2, "length": 0 }
          });
        },
        check: function () {
          is(getValue(aEditor), "abc", description + "Starting to convert 'ab][c'");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "ABc",
                "clauses":
                [
                  { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
                  { "length": 1, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
                ]
              },
              "caret": { "start": 2, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABc", description + "Starting to convert 'AB][c'");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "ABC",
                "clauses":
                [
                  { "length": 2, "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
                  { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
                ]
              },
              "caret": { "start": 3, "length": 0 }
            });
          },
        check: function () {
          is(getValue(aEditor), "ABC", description + "Starting to convert 'AB][C'");
        },
      },
      { test: function () {
          // Commit composition
          synthesizeComposition({ type: "compositioncommitasis" });
        },
        check: function () {
          is(getValue(aEditor), "ABC", description + "Committed as 'ABC'");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "d",
                "clauses":
                [
                  { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 1, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABCd", description + "Typing 'd' next to ABC");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "de",
                "clauses":
                [
                  { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 2, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABCde", description + "Typing 'e' next to ABCd");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "def",
                "clauses":
                [
                  { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 3, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABCdef", description + "Typing 'f' next to ABCde");
        },
      },
      { test: function () {
          // Commit composition
          synthesizeComposition({ type: "compositioncommitasis" });
        },
        check: function () {
          is(getValue(aEditor), "ABCdef", description + "Commit 'def' without convert");
        },
      },
      { test: function () {
          // Select "Cd"
          synthesizeKey("KEY_ArrowLeft");
          synthesizeKey("KEY_ArrowLeft");
          synthesizeKey("KEY_Shift", {type: "keydown", shiftKey: true});
          synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
          synthesizeKey("KEY_ArrowLeft", {shiftKey: true});
          synthesizeKey("KEY_Shift", {type: "keyup"});
        },
        check: function () {
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "g",
                "clauses":
                [
                  { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 1, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABgef", description + "Typing 'g' next to AB");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "gh",
                "clauses":
                [
                  { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 2, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABghef", description + "Typing 'h' next to ABg");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "ghi",
                "clauses":
                [
                  { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
                ]
              },
              "caret": { "start": 3, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABghief", description + "Typing 'i' next to ABgh");
        },
      },
      { test: function () {
          synthesizeCompositionChange(
            { "composition":
              { "string": "GHI",
                "clauses":
                [
                  { "length": 3, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
                ]
              },
              "caret": { "start": 3, "length": 0 }
            });
        },
        check: function () {
          is(getValue(aEditor), "ABGHIef", description + "Convert 'ghi' to 'GHI'");
        },
      },
      { test: function () {
          // Commit composition
          synthesizeComposition({ type: "compositioncommitasis" });
        },
        check: function () {
          is(getValue(aEditor), "ABGHIef", description + "Commit 'GHI'");
        },
      },
    ];

    var index = 0;
    function doReframe(aEvent)
    {
      aEvent.target.style.overflow =
        aEvent.target.style.overflow != "hidden" ? "hidden" : "auto";
    }
    aEditor.focus();
    aEditor.addEventListener(aEventType, doReframe);

    function doNext()
    {
      if (tests.length <= index) {
        aEditor.style.overflow = "auto";
        aEditor.removeEventListener(aEventType, doReframe);
        requestAnimationFrame(function() { SimpleTest.executeSoon(continueTest); });
        return;
      }
      tests[index].test();
      hitEventLoop(function () {
        tests[index].check();
        index++;
        SimpleTest.executeSoon(doNext);
      }, 20);
    }
    doNext();
  }

  input.value = "";
  yield runEditorReframeTest(input, window, "input");
  input.value = "";
  yield runEditorReframeTest(input, window, "compositionupdate");
  textarea.value = "";
  yield runEditorReframeTest(textarea, window, "input");
  textarea.value = "";
  yield runEditorReframeTest(textarea, window, "compositionupdate");
  contenteditable.innerHTML = "";
  yield runEditorReframeTest(contenteditable, windowOfContenteditable, "input");
  contenteditable.innerHTML = "";
  yield runEditorReframeTest(contenteditable, windowOfContenteditable, "compositionupdate");
}

function* runIMEContentObserverTest()
{
  var notifications = [];
  var callContinueTest = false;
  function callback(aTIP, aNotification)
  {
    if (aNotification.type != "notify-end-input-transaction") {
      notifications.push(aNotification);
    }
    switch (aNotification.type) {
      case "request-to-commit":
        aTIP.commitComposition();
        break;
      case "request-to-cancel":
        aTIP.cancelComposition();
        break;
    }
    if (callContinueTest) {
      callContinueTest = false;
      SimpleTest.executeSoon(continueTest);
    }
    return true;
  }

  function dumpUnexpectedNotifications(aDescription, aExpectedCount)
  {
    if (notifications.length <= aExpectedCount) {
      return;
    }
    for (var i = aExpectedCount; i < notifications.length; i++) {
      ok(false,
         aDescription + " caused unexpected notification: " + notifications[i].type);
    }
  }

  function waitUntilNotificationsReceived()
  {
    if (notifications.length > 0) {
      SimpleTest.executeSoon(continueTest);
    } else {
      callContinueTest = true;
    }
  }

  function flushNotifications()
  {
    // FYI: Dispatching non-op keyboard events causes forcibly flushing pending
    //      notifications.
    synthesizeKey("KEY_Unidentified", { code: "" });
    SimpleTest.executeSoon(()=>{
      notifications = [];
      continueTest();
    });
  }

  function ensureToRemovePrecedingPositionChangeNotification(aDescription)
  {
    if (!notifications.length) {
      return;
    }
    if (notifications[0].type != "notify-position-change") {
      return;
    }
    // Sometimes, notify-position-change is notified first separately if
    // the operation causes scroll or something.  Tests can ignore this.
    ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
    notifications.shift();
  }

  // Bug 1374057 - On ubuntu 16.04 there are notify-position-change events that are
  // recorded after all the other events so we remove them through this function.
  function ensureToRemovePostPositionChangeNotification(aDescription, expectedCount)
  {
    if (!notifications.length) {
      return;
    }
    if (notifications.length <= expectedCount) {
      return;
    }
    if (notifications[notifications.length-1].type != "notify-position-change") {
      return;
    }
    ok(true, "notify-position-change", aDescription + "Unnecessary notify-position-change occurred, ignoring it");
    notifications.pop();
  }

  function getNativeText(aXPText)
  {
    if (kLF == "\n") {
      return aXPText;
    }
    return aXPText.replace(/\n/g, kLF);
  }

  function checkPositionChangeNotification(aNotification, aDescription)
  {
    is(!aNotification || aNotification.type, "notify-position-change",
       aDescription + " should cause position change notification");
  }

  function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
  {
    is(!aNotification || aNotification.type, "notify-selection-change",
       aDescription + " should cause selection change notification");
    if (!aNotification || (aNotification.type != "notify-selection-change")) {
      return;
    }
    is(aNotification.offset, aExpected.offset,
       aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
    is(aNotification.text, aExpected.text,
       aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
    is(aNotification.collapsed, aExpected.text.length == 0,
       aDescription + " should cause selection change notification whose collapsed is " + (aExpected.text.length == 0));
    is(aNotification.length, aExpected.text.length,
       aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
    is(aNotification.reversed, aExpected.reversed || false,
       aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
    is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
       aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
  }

  function checkTextChangeNotification(aNotification, aDescription, aExpected)
  {
    is(!aNotification || aNotification.type, "notify-text-change",
       aDescription + " should cause text change notification");
    if (!aNotification || aNotification.type != "notify-text-change") {
      return;
    }
    is(aNotification.offset, aExpected.offset,
       aDescription + " should cause text change notification whose offset is " + aExpected.offset);
    is(aNotification.removedLength, aExpected.removedLength,
       aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
    is(aNotification.addedLength, aExpected.addedLength,
       aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
  }

  function getAsInnerHTML(aElement)
  {
    if (aElement.tagName.toLowerCase() == "input" || aElement.tagName.toLowerCase() == "textarea") {
      return aElement.value;
    }
    return aElement.innerHTML;
  }

  function* testWithPlaintextEditor(aDescription, aElement, aTestLineBreaker)
  {
    aElement.value = "";
    aElement.blur();
    var doc = aElement.ownerDocument;
    var win = doc.defaultView;
    aElement.focus();
    yield flushNotifications();

    // "a[]"
    var description = aDescription + "typing 'a'";
    notifications = [];
    synthesizeKey("a", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "ab[]"
    description = aDescription + "typing 'b'";
    notifications = [];
    synthesizeKey("b", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "abc[]"
    description = aDescription + "typing 'c'";
    notifications = [];
    synthesizeKey("c", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 3, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "ab[c]"
    description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 2, text: "c", reversed: true });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "a[bc]"
    description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: "bc", reversed: true });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "[abc]"
    description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "abc", reversed: true });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "[]abc"
    description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "" });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "[a]bc"
    description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
    notifications = [];
    synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "a" });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "[ab]c"
    description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
    notifications = [];
    synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0, text: "ab" });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "[]c"
    description = aDescription + "deleting 'ab' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "[]"
    description = aDescription + "deleting following 'c' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 1, addedLength: 0 });
    checkPositionChangeNotification(notifications[1], description);
    ensureToRemovePostPositionChangeNotification(description, 2);
    dumpUnexpectedNotifications(description, 2);

    // "abc[]"
    synthesizeKey("a", {}, win, callback);
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    yield flushNotifications();

    // "ab[]"
    description = aDescription + "deleting 'c' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 1, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "[ab]"
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "[]"
    description = aDescription + "deleting 'ab' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "abcd[]"
    synthesizeKey("a", {}, win, callback);
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    synthesizeKey("d", {}, win, callback);
    yield flushNotifications();

    // "a[bc]d"
    synthesizeKey("KEY_ArrowLeft", {}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "a[]d"
    description = aDescription + "deleting 'bc' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "a[bc]d"
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "aB[]d"
    description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
    notifications = [];
    synthesizeKey("B", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 2, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    if (!aTestLineBreaker) {
      return;
    }

    // "aB\n[]d"
    description = aDescription + "inserting a line break after 'B' with pressing Enter";
    notifications = [];
    synthesizeKey("KEY_Enter", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: 0, addedLength: kLFLen });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "aB[]d"
    description = aDescription + "removing a line break after 'B' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2, removedLength: kLFLen, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "a[B]d"
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "a\n[]d"
    description = aDescription + "replacing 'B' with a line break with pressing Enter";
    notifications = [];
    synthesizeKey("KEY_Enter", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: 1, addedLength: kLFLen });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // "a[\n]d"
    description = aDescription + "selecting '\n' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 1, text: kLF, reversed: true });
    ensureToRemovePostPositionChangeNotification(description, 1);
    dumpUnexpectedNotifications(description, 1);

    // "a[]d"
    description = aDescription + "removing selected '\n' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1, removedLength: kLFLen, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 1, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // ab\ncd\nef\ngh\n[]
    description = aDescription + "setting the value property to 'ab\ncd\nef\ngh\n'";
    notifications = [];
    aElement.value = "ab\ncd\nef\ngh\n";
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: 2, addedLength: getNativeText("ab\ncd\nef\ngh\n").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("ab\ncd\nef\ngh\n").length, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);

    // []
    description = aDescription + "setting the value property to ''";
    notifications = [];
    aElement.value = "";
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    // XXX Removing invisible <br> or something? The removed length is a line breaker length longer.
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length + kLFLen, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    ensureToRemovePostPositionChangeNotification(description, 3);
    dumpUnexpectedNotifications(description, 3);
  }

  function* testWithHTMLEditor(aDescription, aElement, aDefaultParagraphSeparator)
  {
    var doc = aElement.ownerDocument;
    var win = doc.defaultView;
    var sel = doc.getSelection();
    var inDesignMode = doc.designMode == "on";
    var offsetAtStart = 0;
    var offsetAtContainer = 0;
    var isDefaultParagraphSeparatorBlock = aDefaultParagraphSeparator != "br";
    doc.execCommand("defaultParagraphSeparator", false, aDefaultParagraphSeparator);

    // "[]", "<p>[]</p>" or "<div>[]</div>"
    switch (aDefaultParagraphSeparator) {
      case "br":
        aElement.innerHTML = "";
        break;
      case "p":
      case "div":
        aElement.innerHTML = "<" + aDefaultParagraphSeparator + "></" + aDefaultParagraphSeparator + ">";
        sel.collapse(aElement.firstChild, 0);
        offsetAtContainer = offsetAtStart + kLFLen;
        break;
      default:
        ok(false, aDescription + "aDefaultParagraphSeparator is illegal value");
        yield flushPendingNotifications();
        return;
    }

    if (inDesignMode) {
      win.focus();
    } else {
      aElement.focus();
    }
    yield flushNotifications();

    // "a[]"
    var description = aDescription + "typing 'a'";
    notifications = [];
    synthesizeKey("a", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "ab[]"
    description = aDescription + "typing 'b'";
    notifications = [];
    synthesizeKey("b", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "abc[]"
    description = aDescription + "typing 'c'";
    notifications = [];
    synthesizeKey("c", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 0, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 3 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "ab[c]"
    description = aDescription + "selecting 'c' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, text: "c", reversed: true });
    dumpUnexpectedNotifications(description, 1);

    // "a[bc]"
    description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: "bc", reversed: true });
    dumpUnexpectedNotifications(description, 1);

    // "[abc]"
    description = aDescription + "selecting 'bc' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "abc", reversed: true });
    dumpUnexpectedNotifications(description, 1);

    // "[]abc"
    description = aDescription + "collapsing selection to the left-most with pressing ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "" });
    dumpUnexpectedNotifications(description, 1);

    // "[a]bc"
    description = aDescription + "selecting 'a' with pressing Shift+ArrowRight";
    notifications = [];
    synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "a" });
    dumpUnexpectedNotifications(description, 1);

    // "[ab]c"
    description = aDescription + "selecting 'ab' with pressing Shift+ArrowRight";
    notifications = [];
    synthesizeKey("KEY_ArrowRight", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, text: "ab" });
    dumpUnexpectedNotifications(description, 1);

    // "[]c"
    description = aDescription + "deleting 'ab' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "[]"
    description = aDescription + "deleting following 'c' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
    checkPositionChangeNotification(notifications[1], description);
    dumpUnexpectedNotifications(description, 2);

    // "abc[]"
    synthesizeKey("a", {}, win, callback);
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    yield flushNotifications();

    // "ab[]"
    description = aDescription + "deleting 'c' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: 1, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "[ab]"
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "[]"
    description = aDescription + "deleting 'ab' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "abcd[]"
    synthesizeKey("a", {}, win, callback);
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    synthesizeKey("d", {}, win, callback);
    yield flushNotifications();

    // "a[bc]d"
    synthesizeKey("KEY_ArrowLeft", {}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "a[]d"
    description = aDescription + "deleting 'bc' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "a[bc]d"
    synthesizeKey("b", {}, win, callback);
    synthesizeKey("c", {}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "aB[]d"
    description = aDescription + "replacing 'bc' with 'B' with pressing Shift+B";
    notifications = [];
    synthesizeKey("B", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 2, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "aB<br>[]d" or "<block>ab</block><block>[]d</block>"
    description = aDescription + "inserting a line break after 'B' with pressing Enter";
    notifications = [];
    synthesizeKey("KEY_Enter", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    if (isDefaultParagraphSeparatorBlock) {
      // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\naB").length, addedLength: getNativeText("\naB\n").length });
    } else {
      // Oddly, inserting <br> causes removing "aB" and inserting "ab<br>".
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2, addedLength: getNativeText("ab\n").length });
    }
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("aB\n").length + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "aB[]d"
    description = aDescription + "removing a line break after 'B' with pressing Backspace";
    notifications = [];
    synthesizeKey("KEY_Backspace", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    if (isDefaultParagraphSeparatorBlock) {
      // Joining two blocks causes removing both block elements and inserting new block element.
      checkTextChangeNotification(notifications[0], description, { offset: offsetAtContainer - kLFLen, removedLength: getNativeText("\naB\nd").length, addedLength: getNativeText("\naBd").length });
      checkSelectionChangeNotification(notifications[1], description, { offset: 2 + offsetAtContainer, text: "" });
      checkPositionChangeNotification(notifications[2], description);
      dumpUnexpectedNotifications(description, 3);
    } else {
      checkTextChangeNotification(notifications[0], description, { offset: 2 + offsetAtContainer, removedLength: kLFLen, addedLength: 0 });
      is(notifications.length, 3, description + " should cause 3 notifications");
      is(notifications[1] && notifications[1].type, "notify-selection-change", description + " should cause selection change notification");
      is(notifications[2] && notifications[2].type, "notify-position-change", description + " should cause position change notification");
    }

    // "a[B]d"
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield flushNotifications();

    // "a<br>[]d" or "<block>a</block><block>[]d</block>"
    description = aDescription + "replacing 'B' with a line break with pressing Enter";
    notifications = [];
    synthesizeKey("KEY_Enter", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    if (isDefaultParagraphSeparatorBlock) {
      // Splitting current block causes removing "<block>aB" and inserting "<block>aB</block><block>".
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\naB").length, addedLength: getNativeText("\na\n").length });
    } else {
      checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: 1, addedLength: kLFLen });
    }
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("a\n").length + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "a[<br>]d" or "<block>a[</block><block>]d</block>"
    description = aDescription + "selecting '\\n' with pressing Shift+ArrowLeft";
    notifications = [];
    synthesizeKey("KEY_ArrowLeft", {shiftKey: true}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkSelectionChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, text: kLF, reversed: true });
    dumpUnexpectedNotifications(description, 1);

    // "a[]d"
    description = aDescription + "removing selected '\\n' with pressing Delete";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    if (isDefaultParagraphSeparatorBlock) {
      // Joining the blocks causes removing "<block>a</block><block>d</block>" and inserting "<block>ad</block>".
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\na\nd").length, addedLength: getNativeText("\nad").length });
    } else {
      checkTextChangeNotification(notifications[0], description, { offset: 1 + offsetAtContainer, removedLength: kLFLen, addedLength: 0 });
    }
    checkSelectionChangeNotification(notifications[1], description, { offset: 1 + offsetAtContainer, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>"
    description = aDescription + "inserting HTML which has nested block elements";
    notifications = [];
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    // There is <br> after the end of the line.  Therefore, removed length includes a line breaker length.
    if (isDefaultParagraphSeparatorBlock) {
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer - kLFLen, removedLength: getNativeText("\nad\n").length, addedLength: getNativeText("\n1\n2\n345").length });
    } else {
      checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtContainer, removedLength: 2 + kLFLen, addedLength: getNativeText("\n1\n2\n345").length });
    }
    checkSelectionChangeNotification(notifications[1], description, { offset: 0, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1[<div>2<div>3</div>4</div>]5</div>" and removing selection
    sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    is(aElement.innerHTML, "<div>15</div>", description + " should remove '<div>2<div>3</div>4</div>'");
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 0 });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1[<div>2<div>3</div>]4</div>5</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
    sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(2), 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (partially #1) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    is(aElement.innerHTML, "<div>145</div>", description + " should remove '<div>2<div>3</div></div>'");
    // It causes removing '<div>2<div>3</div>4</div>' and inserting '4'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n34").length, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1[<div>2<div>]3</div>4</div>5</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
    sel.setBaseAndExtent(aElement.firstChild.firstChild, 1, aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (partially #2) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    is(aElement.innerHTML, "<div>13<div>4</div>5</div>", description + " should remove '<div>2</div>'");
    // It causes removing '1<div>2<div>3</div></div>' and inserting '13<div>'.
    checkTextChangeNotification(notifications[0], description, { offset: kLFLen + offsetAtStart, removedLength: getNativeText("1\n2\n3").length, addedLength: getNativeText("13\n").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2<div>3[</div>4</div>]5</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5</div>";
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(2), 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (partially #3) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    is(aElement.innerHTML, "<div>1<div>2<div>35</div></div></div>", description + " should remove '4'");
    // It causes removing '45' and inserting '5'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: 2, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>"
    description = aDescription + "inserting HTML which has a pair of nested block elements";
    notifications = [];
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    checkTextChangeNotification(notifications[0], description, { offset: 0 + offsetAtStart, removedLength: getNativeText("\n1\n2\n35").length, addedLength: getNativeText("\n1\n2\n345\n6\n789").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: 0 + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (between same level descendants) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    is(aElement.innerHTML, "<div>1<div>2<div>37</div></div><div>8</div>9</div>", description + " should remove '456<div>7'");
    // It causes removing '<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>37</div><div>'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n7").length, addedLength: getNativeText("\n37\n").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>]7</div>8</div>9</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).childNodes.item(1).firstChild, 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (between different level descendants #1) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    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>'");
    // It causes removing '<div>2<div>3</div>4</div>5<div>6<div>7</div>' and inserting '<div>27</div>'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1").length + offsetAtStart, removedLength: getNativeText("\n2\n345\n6\n7").length, addedLength: getNativeText("\n27\n").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2[<div>3</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (between different level descendants #2) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    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>'");
    // It causes removing '<div>3</div>4</div>5</div>6<div>7</div>8</div>9' and inserting '9</div>'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n789").length, addedLength: 1 });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2<div>3[</div>4</div>5<div>]6<div>7</div>8</div>9</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(3).firstChild, 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (between different level descendants #3) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    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>'");
    // 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>'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2").length + offsetAtStart, removedLength: getNativeText("\n345\n6\n78").length, addedLength: getNativeText("\n36\n78").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);

    // "<div>1<div>2<div>3[</div>4</div>5<div>6<div>7</div>8</div>]9</div>" and removing selection
    aElement.innerHTML = "<div>1<div>2<div>3</div>4</div>5<div>6<div>7</div>8</div>9</div>";
    sel.setBaseAndExtent(aElement.firstChild.childNodes.item(1).childNodes.item(1).firstChild, 1, aElement.firstChild.childNodes.item(4), 0);
    yield flushNotifications();
    description = aDescription + "deleting child nodes (between different level descendants #4) with pressing Delete key";
    notifications = [];
    synthesizeKey("KEY_Delete", {}, win, callback);
    yield waitUntilNotificationsReceived();
    ensureToRemovePrecedingPositionChangeNotification();
    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>'");
    // It causes removing '</div>4</div>5<div>6<div>7</div>8</div>' and inserting '<div>36<div>7</div>8</div>'.
    checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, removedLength: getNativeText("45\n6\n789").length, addedLength: getNativeText("9").length });
    checkSelectionChangeNotification(notifications[1], description, { offset: getNativeText("\n1\n2\n3").length + offsetAtStart, text: "" });
    checkPositionChangeNotification(notifications[2], description);
    dumpUnexpectedNotifications(description, 3);
  }

  yield* testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
  yield* testWithPlaintextEditor("runIMEContentObserverTest with textarea element: ", textarea, true);
  yield* testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is br): ", contenteditable, "br");
  yield* testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is p): ", contenteditable, "p");
  yield* testWithHTMLEditor("runIMEContentObserverTest with contenteditable (defaultParagraphSeparator is div): ", contenteditable, "div");
  // XXX Due to the difference of HTML editor behavior between designMode and contenteditable,
  //     testWithHTMLEditor() gets some unexpected behavior.  However, IMEContentObserveri is
  //     not depend on editor's detail.  So, we should investigate this issue later.  It's not
  //     so important for now.
  // yield* testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is br): ", iframe2.contentDocument.body, "br");
  // yield* testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is p): ", iframe2.contentDocument.body, "p");
  // yield* testWithHTMLEditor("runIMEContentObserverTest in designMode (defaultParagraphSeparator is div): ", iframe2.contentDocument.body, "div");
}

var gTestContinuation = null;

function continueTest()
{
  if (!gTestContinuation) {
    gTestContinuation = testBody();
  }
  var ret = gTestContinuation.next();
  if (ret.done) {
    finish();
  }
}

function* testBody()
{
  runUndoRedoTest();
  runCompositionCommitAsIsTest();
  runCompositionCommitTest();
  runCompositionTest();
  runCompositionEventTest();
  runQueryTextRectInContentEditableTest();
  runCharAtPointTest(textarea, "textarea in the document");
  runCharAtPointAtOutsideTest();
  runSetSelectionEventTest();
  runQueryTextContentEventTest();
  runQuerySelectionEventTest();
  runQueryIMESelectionTest();
  runQueryContentEventRelativeToInsertionPoint();
  yield* runIMEContentObserverTest();
  runCSSTransformTest();
  runBug722639Test();
  runBug1375825Test();
  runForceCommitTest();
  runNestedSettingValue();
  runBug811755Test();
  runIsComposingTest();
  runRedundantChangeTest();
  runNotRedundantChangeTest();
  runNativeLineBreakerTest();
  runControlCharTest();
  yield* runEditorReframeTests();
  yield runAsyncForceCommitTest();
  yield runRemoveContentTest();
  runFrameTest();
  yield runPanelTest();
  runMaxLengthTest();
}

function runTest()
{
  contenteditable = document.getElementById("iframe4").contentDocument.getElementById("contenteditable");
  windowOfContenteditable = document.getElementById("iframe4").contentWindow;
  textareaInFrame = iframe.contentDocument.getElementById("textarea");
  continueTest();
}

]]>
</script>

</window>