DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (7f9f804e9dbe)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
<!DOCTYPE html>
<html>
<head>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<div id="display"></div>
<textarea id="textarea">abc abx abc</textarea>
<div id="contenteditable" contenteditable>abc abx abc</div>
<pre id="test">
</pre>

<script class="testbody" type="application/javascript">
"use strict";

SimpleTest.waitForExplicitFinish();
SimpleTest.expectAssertions(0, 1);  // In a11y module
SimpleTest.waitForFocus(async () => {
  await SpecialPowers.pushPrefEnv({
    set: [["dom.input_events.beforeinput.enabled", true]],
  });

  let textarea = document.getElementById("textarea");
  let textEditor = SpecialPowers.wrap(textarea).editor;
  let contenteditable = document.getElementById("contenteditable");
  let htmlEditor = SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window);

  function doTest(aElement, aRootElement, aEditor, aDescription) {
    return new Promise(resolve => {
      let inlineSpellChecker = aEditor.getInlineSpellChecker(true);

      aElement.focus();

      function checkInputEvent(aEvent, aInputType, aData, aDataTransfer, aTargetRanges, aDescriptionInner) {
        ok(aEvent instanceof InputEvent,
           `${aDescription}"${aEvent.type}" event should be dispatched with InputEvent interface ${aDescriptionInner}`);
        is(aEvent.cancelable, aEvent.type === "beforeinput" && aInputType !== "",
           `${aDescription}"${aEvent.type}" event should ${aEvent.type === "beforeinput" ? "be" : "be never"} cancelable ${aDescriptionInner}`);
        is(aEvent.bubbles, true,
           `${aDescription}"${aEvent.type}" event should always bubble ${aDescriptionInner}`);
        is(aEvent.inputType, aInputType,
           `${aDescription}inputType of "${aEvent.type}" event should be "${aInputType}" ${aDescriptionInner}`);
        is(aEvent.data, aData,
           `${aDescription}data of "${aEvent.type}" event should be ${aData} ${aDescriptionInner}`);
        if (aDataTransfer === null) {
          is(aEvent.dataTransfer, null,
             `${aDescription}dataTransfer of "${aEvent.type}" event should be null ${aDescriptionInner}`);
        } else {
          for (let item of aDataTransfer) {
            is(aEvent.dataTransfer.getData(item.type), item.data,
               `${aDescription}dataTransfer of "${aEvent.type}" event should have ${item.data} as ${item.type} ${aDescriptionInner}`);
          }
        }
        let targetRanges = aEvent.getTargetRanges();
        if (aTargetRanges.length === 0) {
          is(targetRanges.length, 0,
             `${aDescription}getTargetRange() of "${aEvent.type}" event should return empty array ${aDescriptionInner}`);
        } else {
          is(targetRanges.length, aTargetRanges.length,
             `${aDescription}getTargetRange() of "${aEvent.type}" event should return static range array ${aDescriptionInner}`);
          if (targetRanges.length == aTargetRanges.length) {
            for (let i = 0; i < targetRanges.length; i++) {
              is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
                 `${aDescription}startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match ${aDescriptionInner}`);
              is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
                 `${aDescription}startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match ${aDescriptionInner}`);
              is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
                 `${aDescription}endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match ${aDescriptionInner}`);
              is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
                 `${aDescription}endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match ${aDescriptionInner}`);
            }
          }
        }
      }

      let beforeInputEvents = [];
      let inputEvents = [];
      function onBeforeInput(aEvent) {
        beforeInputEvents.push(aEvent);
      }
      function onInput(aEvent) {
        inputEvents.push(aEvent);
      }

      function getValue() {
        return aElement === textarea ? aElement.value : aElement.innerHTML;
      }

      SpecialPowers.Cu.import(
        "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
      .onSpellCheck(aElement, () => {
        SimpleTest.executeSoon(() => {
          aElement.addEventListener("beforeinput", onBeforeInput);
          aElement.addEventListener("input", onInput);

          let misspelledWord = inlineSpellChecker.getMisspelledWord(aRootElement.firstChild, 5);
          is(misspelledWord.startOffset, 4,
             `${aDescription}Misspelled word should start from 4`);
          is(misspelledWord.endOffset, 7,
             `${aDescription}Misspelled word should end at 7`);
          beforeInputEvents = [];
          inputEvents = [];
          inlineSpellChecker.replaceWord(aRootElement.firstChild, 5, "aux");
          is(getValue(), "abc aux abc",
             `${aDescription}'abx' should be replaced with 'aux'`);
          is(beforeInputEvents.length, 1,
             `${aDescription}Only one "beforeinput" event should be fired when replacing a word with spellchecker`);
          if (aElement === textarea) {
            checkInputEvent(beforeInputEvents[0], "insertReplacementText", "aux", null, [],
                            "when replacing a word with spellchecker");
          } else {
            checkInputEvent(beforeInputEvents[0], "insertReplacementText", null, [{type: "text/plain", data: "aux"}],
                            [{startContainer: aRootElement.firstChild, startOffset: 4,
                              endContainer: aRootElement.firstChild, endOffset: 7}],
                            "when replacing a word with spellchecker");
          }
          is(inputEvents.length, 1,
             `${aDescription}Only one "input" event should be fired when replacing a word with spellchecker`);
          if (aElement === textarea) {
            checkInputEvent(inputEvents[0], "insertReplacementText", "aux", null, [],
                            "when replacing a word with spellchecker");
          } else {
            checkInputEvent(inputEvents[0], "insertReplacementText", null, [{type: "text/plain", data: "aux"}], [],
                            "when replacing a word with spellchecker");
          }

          beforeInputEvents = [];
          inputEvents = [];
          synthesizeKey("z", { accelKey: true });
          is(getValue(), "abc abx abc",
             `${aDescription}'abx' should be restored by undo`);
          is(beforeInputEvents.length, 1,
             `${aDescription}Only one "beforeinput" event should be fired when undoing the replacing word`);
          checkInputEvent(beforeInputEvents[0], "historyUndo", null, null, [],
                          "when undoing the replacing word");
          is(inputEvents.length, 1,
             `${aDescription}Only one "input" event should be fired when undoing the replacing word`);
          checkInputEvent(inputEvents[0], "historyUndo", null, null, [],
                          "when undoing the replacing word");

          beforeInputEvents = [];
          inputEvents = [];
          synthesizeKey("z", { accelKey: true, shiftKey: true });
          is(getValue(), "abc aux abc",
             `${aDescription}'aux' should be restored by redo`);
          is(beforeInputEvents.length, 1,
             `${aDescription}Only one "beforeinput" event should be fired when redoing the replacing word`);
          checkInputEvent(beforeInputEvents[0], "historyRedo", null, null, [],
                          "when redoing the replacing word");
          is(inputEvents.length, 1,
             `${aDescription}Only one "input" event should be fired when redoing the replacing word`);
          checkInputEvent(inputEvents[0], "historyRedo", null, null, [],
                          "when redoing the replacing word");

          aElement.removeEventListener("beforeinput", onBeforeInput);
          aElement.removeEventListener("input", onInput);

          resolve();
        });
      });
    });
  }

  await doTest(textarea, textEditor.rootElement, textEditor, "<textarea>: ");
  await doTest(contenteditable, contenteditable, htmlEditor, "<div contenteditable>: ");

  SimpleTest.finish();
});
</script>
</body>
</html>