tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_replace_text.html (12946B)


      1 <!DOCTYPE html>
      2 <!--
      3 https://bugzilla.mozilla.org/show_bug.cgi?id=1149826
      4 -->
      5 <html>
      6 <head>
      7 <title>Test for replaceText</title>
      8 <script src="/tests/SimpleTest/SimpleTest.js"></script>
      9 <script src="/tests/SimpleTest/EventUtils.js"></script>
     10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
     11 </head>
     12 
     13 <body>
     14 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1149826">Mozilla Bug 1149826</a><br>
     15 <input type="text"><br>
     16 <textarea></textarea>
     17 <div contenteditable></div>
     18 
     19 <script>
     20 const gDOMWindowUtils = _getDOMWindowUtils(window);
     21 const Ci = SpecialPowers.Ci;
     22 const IS_WIN = navigator.platform.indexOf("Win") == 0;
     23 
     24 async function testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, aPreventSetSelection) {
     25  await SimpleTest.promiseFocus();
     26 
     27  const flags = aPreventSetSelection ?
     28    Ci.nsIDOMWindowUtils.CONTENT_COMMAND_FLAG_PREVENT_SET_SELECTION :
     29    0;
     30 
     31  const input = document.querySelector("input");
     32  input.focus();
     33  await new Promise(resolve => SimpleTest.executeSoon(resolve));
     34 
     35  info("for <input>");
     36 
     37  for (const TEST of INPUT_TESTS) {
     38    input.value = TEST.before.value;
     39    input.selectionStart = TEST.before.start;
     40    input.selectionEnd = TEST.before.end;
     41    await new Promise(resolve => SimpleTest.executeSoon(resolve));
     42 
     43    input.addEventListener("beforeinput", e => {
     44      is(e.inputType, "insertReplacementText",
     45         "inputType in input must be insertReplacementText by replaceText");
     46      is(input.selectionStart, TEST.replace.start,
     47         "Before inputReplacementText, start offset should be changed to the replace start");
     48      is(input.selectionEnd, TEST.replace.start + TEST.replace.src.length,
     49         "Before inputReplacementText, end offset should be changed to the replace end");
     50    }, { once: true } );
     51 
     52    const promiseAfterOnInput =
     53      new Promise(resolve => input.addEventListener("input", e => {
     54        is(e.inputType, "insertReplacementText",
     55           "inputType must be insertReplacementText by replaceText");
     56        resolve();
     57      }, { once: true } ));
     58    gDOMWindowUtils.sendContentCommandEvent(
     59      "replaceText",
     60      null,
     61      TEST.replace.value,
     62      TEST.replace.start,
     63      TEST.replace.src,
     64      flags
     65    );
     66    await promiseAfterOnInput
     67 
     68    is(input.value, TEST.after.value,
     69       "replaceText in input replaces inner text");
     70    is(input.selectionStart, TEST.after.start,
     71       "replaceText in input sets expected selection start");
     72    is(input.selectionEnd, TEST.after.end,
     73       "replaceText in input sets expected selection end");
     74  }
     75 
     76  const textarea = document.querySelector("textarea");
     77  textarea.focus();
     78  await new Promise(resolve => SimpleTest.executeSoon(resolve));
     79 
     80  info("for <textarea>");
     81 
     82  for (const TEST of TEXTAREA_TESTS) {
     83    textarea.value = TEST.before.value;
     84    textarea.selectionStart = TEST.before.start;
     85    textarea.selectionEnd = TEST.before.end;
     86 
     87    textarea.addEventListener("beforeinput", e => {
     88      is(e.inputType, "insertReplacementText",
     89         "inputType must be insertReplacementText by replaceText");
     90      is(textarea.selectionStart, TEST.replace.start,
     91         "Before inputReplacementText, start offset be changed to the replace start");
     92      is(textarea.selectionEnd, TEST.replace.start + TEST.replace.src.length,
     93         "Before inputReplacementText, end offset be changed to the replace end");
     94    }, { once: true } );
     95 
     96    const promiseAfterOnTextarea =
     97      new Promise(resolve => textarea.addEventListener("input", e => {
     98        is(e.inputType, "insertReplacementText",
     99           "inputType must be insertReplacementText by replaceText");
    100        resolve();
    101      }, { once: true } ));
    102    gDOMWindowUtils.sendContentCommandEvent(
    103      "replaceText",
    104      null,
    105      TEST.replace.value,
    106      TEST.replace.start,
    107      TEST.replace.src,
    108      flags
    109    );
    110    await promiseAfterOnTextarea
    111 
    112    is(textarea.value, TEST.after.value,
    113      "replaceText in textarea replaces inner text");
    114    is(textarea.selectionStart, TEST.after.start,
    115       "replaceText in textarea sets expected selection start");
    116    is(textarea.selectionEnd, TEST.after.end,
    117       "replaceText in textarea sets expected selection end");
    118  }
    119 
    120  const editingHost = document.querySelector("div[contenteditable]");
    121  editingHost.focus();
    122  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    123 
    124  info("for contenteditable");
    125 
    126  for (const TEST of CONTENTEDITABLE_TESTS) {
    127    editingHost.innerHTML = TEST.before.value;
    128    window.getSelection().setBaseAndExtent(
    129        // eslint-disable-next-line no-eval
    130        eval(TEST.before.focusNode),
    131        TEST.before.focusOffset,
    132        // eslint-disable-next-line no-eval
    133        eval(TEST.before.focusNode),
    134        TEST.before.focusOffset
    135    );
    136 
    137    editingHost.addEventListener("beforeinput", e => {
    138      const selection = window.getSelection();
    139      is(e.inputType, "insertReplacementText",
    140         "inputType must be insertReplacementText by replaceText");
    141      // eslint-disable-next-line no-eval
    142      is(selection.anchorNode, eval(TEST.replace.textNode),
    143         "Before inputReplacementText, focus node is the Text containing replacing text");
    144      is(selection.anchorOffset, TEST.replace.start,
    145         "Before inputReplacementText, focus offset is start of the replace start");
    146      // eslint-disable-next-line no-eval
    147      is(selection.focusNode, eval(TEST.replace.textNode),
    148         "Before inputReplacementText, focus node is the Text containing replacing text");
    149      is(selection.focusOffset, TEST.replace.start + TEST.replace.src.length,
    150         "Before inputReplacementText, focus offset is start of the replace start");
    151    }, { once: true } );
    152 
    153    const promiseAfterEditingHost =
    154      new Promise(resolve => editingHost.addEventListener("input", e => {
    155        is(e.inputType, "insertReplacementText",
    156           "inputType must be insertReplacementText by replaceText");
    157        resolve();
    158      }, { once: true } ));
    159    gDOMWindowUtils.sendContentCommandEvent(
    160      "replaceText",
    161      null,
    162      TEST.replace.value,
    163      TEST.replace.start,
    164      TEST.replace.src,
    165      flags
    166    );
    167    await promiseAfterEditingHost
    168 
    169    is(editingHost.textContent, TEST.after.value,
    170       "replaceText in contenteditable replaces inner text");
    171    const selection = window.getSelection();
    172    // eslint-disable-next-line no-eval
    173    is(selection.focusNode, eval(TEST.after.focusNode),
    174       "replaceText in contenteditable sets expected focusNode");
    175    is(selection.focusOffset, TEST.after.focusOffset,
    176       "replaceText in contenteditable sets expected focusOffset");
    177  }
    178 }
    179 
    180 add_task(async function testReplaceTextWithoutPreventSetSelection() {
    181  const INPUT_TESTS = [
    182  { before: {
    183      value: "foo", start: 3, end: 3
    184    },
    185    replace: {
    186      src: "o", value: "bar", start: 1
    187    },
    188    after: {
    189      value: "fbaro", start: 4, end: 4
    190    }
    191  },
    192  { before: {
    193      value: "foo ", start: 4, end: 4
    194    },
    195    replace: {
    196      src: "oo", value: "bar", start: 1
    197    },
    198    after: {
    199      value: "fbar ", start: 4, end: 4
    200    }
    201  }];
    202 
    203  const TEXTAREA_TESTS = [
    204  { before: {
    205      value: "foo", start: 3, end: 3
    206    },
    207    replace: {
    208      src: "o", value: "bar", start: 1
    209    },
    210    after: {
    211      value: "fbaro", start: 4, end: 4
    212    }
    213  },
    214  { before: {
    215      value: "foo ", start: 4, end: 4
    216    },
    217    replace: {
    218      src: "oo", value: "bar", start: 1
    219    },
    220    after: {
    221      value: "fbar ", start: 4, end: 4
    222    }
    223  }];
    224 
    225  const CONTENTEDITABLE_TESTS = [
    226  { before: {
    227      value: "foo", focusNode: "editingHost.firstChild", focusOffset: 3
    228    },
    229    replace: {
    230      src: "o", value: "bar", start: 1, textNode: "editingHost.firstChild",
    231    },
    232    after: {
    233      value: "fbaro", focusNode: "editingHost.firstChild", focusOffset: 4, isCollapsed: true
    234    },
    235  },
    236  { before: {
    237      value: "foo foo", focusNode: "editingHost.firstChild", focusOffset: 4
    238    },
    239    replace: {
    240      src: "oo", value: "bar", start: 1, textNode: "editingHost.firstChild",
    241    },
    242    after: {
    243      value: "fbar foo", focusNode: "editingHost.firstChild", focusOffset: 4, isCollapsed: true
    244    },
    245  }];
    246 
    247  await testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, false);
    248 });
    249 
    250 add_task(async function testReplaceTextWithPreventSetSelection() {
    251  const INPUT_TESTS = [
    252  { before: {
    253      value: "foo", start: 3, end: 3
    254    },
    255    replace: {
    256      src: "o", value: "bar", start: 1
    257    },
    258    after: {
    259      value: "fbaro", start: 5, end: 5
    260    }
    261  },
    262  { before: {
    263      value: "foo ", start: 4, end: 4
    264    },
    265    replace: {
    266      src: "oo", value: "bar", start: 1
    267    },
    268    after: {
    269      value: "fbar ", start: 5, end: 5
    270    }
    271  }];
    272 
    273  const TEXTAREA_TESTS = [
    274  { before: {
    275      value: "foo", start: 3, end: 3
    276    },
    277    replace: {
    278      src: "o", value: "bar", start: 1
    279    },
    280    after: {
    281      value: "fbaro", start: 5, end: 5
    282    }
    283  },
    284  { before: {
    285      value: "foo ", start: 4, end: 4
    286    },
    287    replace: {
    288      src: "oo", value: "bar", start: 1
    289    },
    290    after: {
    291      value: "fbar ", start: 5, end: 5
    292    }
    293  }];
    294 
    295  const CONTENTEDITABLE_TESTS = [
    296  { before: {
    297      value: "foo", focusNode: "editingHost.firstChild", focusOffset: 3
    298    },
    299    replace: {
    300      src: "o", value: "bar", start: 1, textNode: "editingHost.firstChild",
    301    },
    302    after: {
    303      value: "fbaro", focusNode: "editingHost.firstChild", focusOffset: 5, isCollapsed: true
    304    },
    305  },
    306  { before: {
    307      value: "foo foo", focusNode: "editingHost.firstChild", focusOffset: 4
    308    },
    309    replace: {
    310      src: "oo", value: "bar", start: 1, textNode: "editingHost.firstChild",
    311    },
    312    after: {
    313      value: "fbar foo", focusNode: "editingHost.firstChild", focusOffset: 5, isCollapsed: true
    314    },
    315  }];
    316 
    317  await testReplaceText(INPUT_TESTS, TEXTAREA_TESTS, CONTENTEDITABLE_TESTS, true);
    318 });
    319 
    320 add_task(async function testReplaceTextWithCompositionText() {
    321  await SimpleTest.promiseFocus();
    322 
    323  // Don't replace text during composition
    324  const input = document.querySelector("input");
    325  input.value = "";
    326  input.focus();
    327  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    328 
    329  let promise =
    330    new Promise(resolve => input.addEventListener("compositionupdate", resolve, { once: true }));
    331  synthesizeCompositionChange(
    332    { "composition":
    333      { "string": "foo",
    334        "clauses":
    335        [
    336          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
    337        ]
    338      },
    339    });
    340  await promise;
    341 
    342  input.addEventListener("input", e => {
    343    isnot(e.inputType, "insertReplacementText",
    344          "Don't fire insertReplacementText input event by replaceText");
    345  }, { once: true } );
    346  gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "o");
    347  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    348 
    349  promise = new Promise(resolve => input.addEventListener("compositionend", resolve, { once: true }));
    350  synthesizeComposition({type: "compositioncommitasis", key: {key: "KEY_Enter"}});
    351  await promise;
    352 
    353  is(input.value, "foo",
    354     "replaceText doesn't replace inner text when having composition");
    355  is(input.selectionStart, 3, "replaceText sets caret position to next of replaced text");
    356  is(input.selectionStart, input.selectionEnd, "replaceText sets that selection is collapsed");
    357 });
    358 
    359 add_task(async function testReplaceTextBeforeCallingPreventDefault() {
    360  await SimpleTest.promiseFocus();
    361 
    362  // Call preventDefault on beforeinput
    363  const input = document.querySelector("input");
    364  input.value = "foo";
    365  input.focus();
    366  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    367 
    368  input.selectionStart = 1
    369  input.selectionEnd = 2;
    370 
    371  const promise = new Promise(resolve => input.addEventListener("beforeinput", e => {
    372    e.preventDefault();
    373    resolve();
    374  }, { once: true }));
    375  gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "o");
    376  await promise;
    377 
    378  is(input.value, "foo",
    379     "replaceText doesn't replace inner text of <input> since preventDefault is called");
    380  is(input.selectionStart, 1, "selectionStart isn't changed since preventDefault is called");
    381  is(input.selectionEnd, 2, "selectionEnd isn't changed since preventDefault is called");
    382 });
    383 
    384 add_task(async function testReplaceTextWithoutMatch() {
    385  await SimpleTest.promiseFocus();
    386 
    387  const input = document.querySelector("input");
    388  input.value = "foo";
    389  input.focus();
    390  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    391 
    392  input.selectionStart = 1
    393  input.selectionEnd = 2;
    394 
    395  gDOMWindowUtils.sendContentCommandEvent("replaceText", null, "bar", 1, "a");
    396  await new Promise(resolve => SimpleTest.executeSoon(resolve));
    397 
    398  is(input.value, "foo",
    399     "replaceText doesn't replace inner text of <input> due to not matched");
    400  is(input.selectionStart, 1, "selectionStart isn't changed due to failed");
    401  is(input.selectionEnd, 2, "selectionEnd isn't changed due to failed");
    402 });
    403 </script>
    404 </body>
    405 </html>