<?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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
  <script 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/>
<iframe id="iframe5" width="300" height="150"
        src="data:text/html,&lt;span contenteditable id='contenteditable'&gt;&lt;/span&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 runBug1530649Test()
{
  // Vietnamese IME on macOS commits composition with typing space key.
  // Then, typing new word shouldn't trim the trailing whitespace.
  contenteditable.focus();
  contenteditable.innerHTML = "";
  synthesizeCompositionChange(
    {composition: {string: "abc", clauses: [{length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
     caret: {start: 3, length: 0}});
  synthesizeComposition({type: "compositioncommit", data: "abc ", key: " "});

  is(contenteditable.innerHTML, "abc <br>",
     "runBug1530649Test: The trailing space shouldn't be removed");

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

  is(contenteditable.innerHTML, "abc d<br>",
     "runBug1530649Test: The new composition string shouldn't remove the last space");

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

  is(contenteditable.innerHTML, "abc d<br>",
     "runBug1530649Test: Committing the new composition string shouldn't remove the last space");
}

function runBug1571375Test()
{
  let contenteditableBySpan = document.getElementById("iframe5").contentDocument.getElementById("contenteditable");
  let windowOfContenteditableBySpan = document.getElementById("iframe5").contentWindow;
  let selection = windowOfContenteditableBySpan.getSelection();
  let doc = document.getElementById("iframe5").contentDocument;

  contenteditableBySpan.focus();

  contenteditableBySpan.innerHTML = "hello world";
  let range = doc.createRange();
  range.setStart(contenteditableBySpan.firstChild, 6);
  range.setEnd(contenteditableBySpan.firstChild, 11);
  selection.removeAllRanges();
  selection.addRange(range);

  synthesizeCompositionChange({
    composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
    caret: { start: 5, length: 0 },
  });
  synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
  is(contenteditableBySpan.innerHTML, "hello world",
     "runBug1571375Test: space must not be removed by commit");

  contenteditableBySpan.innerHTML = "hello world";
  range = doc.createRange();
  range.setStart(contenteditableBySpan.firstChild, 0);
  range.setEnd(contenteditableBySpan.firstChild, 5);
  selection.removeAllRanges();
  selection.addRange(range);

  synthesizeCompositionChange({
    composition: {string: "hello", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
    caret: { start: 5, length: 0 },
  });
  synthesizeComposition({type: "compositioncommit", data: "hello", key: " "});
  is(contenteditableBySpan.innerHTML, "hello world",
     "runBug1571375Test: space must not be removed by commit");

  contenteditableBySpan.innerHTML = "hello world<div>.</div>";
  range = doc.createRange();
  range.setStart(contenteditableBySpan.firstChild, 6);
  range.setEnd(contenteditableBySpan.firstChild, 11);
  selection.removeAllRanges();
  selection.addRange(range);

  synthesizeCompositionChange({
    composition: {string: "world", clauses: [{length: 5, attr: COMPOSITION_ATTR_RAW_CLAUSE}]},
    caret: {start: 0, length: 0}}
  );
  synthesizeComposition({type: "compositioncommit", data: "world", key: " "});
  is(contenteditableBySpan.innerHTML, "hello world<div>.</div>",
     "runBug1571375Test: space must not be removed by commit");
}

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 = "";

  // The frames and panel are cross-origin, and we no longer
  // propagate flushes to parent cross-origin iframes explicitly,
  // so flush our own layout here so the positions are correct.
  document.documentElement.getBoundingClientRect();

  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();
    checkTextChangeNotification(notifications[0], description, { offset: 0, removedLength: getNativeText("ab\ncd\nef\ngh\n").length, 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();
  runBug1530649Test();
  runBug1571375Test();
  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>
