tor-browser

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

delete-space-after-double-click-selection.html (10738B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <meta name="timeout" content="long">
      6  <title>Test for Bug 1783641</title>
      7  <script src=/resources/testharness.js></script>
      8  <script src=/resources/testharnessreport.js></script>
      9  <script src="/resources/testdriver.js"></script>
     10  <script src="/resources/testdriver-actions.js"></script>
     11  <style>
     12    .testStyle {
     13      font-family: 'Courier New', Courier, monospace;
     14      font-size: 12px;
     15      padding: 0px;
     16      width: 200px;
     17    }
     18  </style>
     19 </head>
     20 <body>
     21  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783641">Mozilla Bug 1783641</a><br />
     22  <span class="testStyle" id="placeholder"></span>
     23  <input class="testStyle" type="text" />
     24  <div class="testStyle" contenteditable></div>
     25  <textarea class="testStyle"></textarea>
     26  <script>
     27 
     28    promise_test(async t => {
     29      await new Promise(resolve => { window.onload = resolve; });
     30 
     31      await SpecialPowers.pushPrefEnv({
     32        set: [
     33          ["editor.word_select.delete_space_after_doubleclick_selection", true],
     34          ["layout.word_select.eat_space_to_next_word", false]
     35        ]
     36      });
     37    }, "Test setup");
     38    const placeHolder = document.getElementById("placeholder");
     39    const deleteKey = "\uE017";
     40 
     41    function waitForRender() {
     42      return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
     43    };
     44 
     45    for (const selector of ["input", "div[contenteditable]", "textarea"]) {
     46      const editableElement = document.querySelector(selector);
     47      editableElement.focus();
     48 
     49      /**
     50       * Helper functions to set or get the value and the current selection of `editableElement`,
     51       * regardless of its actual type.
     52       */
     53      const setValue = (aValue) => {
     54        editableElement.tagName.toLowerCase() == "div"
     55          ? editableElement.innerHTML = aValue
     56          : editableElement.value = aValue;
     57      }
     58      const getValue = () => {
     59        return editableElement.tagName.toLowerCase() == "div"
     60          ? editableElement.innerHTML
     61          : editableElement.value;
     62      };
     63      const getSelection = () => {
     64        return editableElement.tagName.toLowerCase() == "div"
     65          ? document.getSelection().toString()
     66          : editableElement.value.substring(
     67            editableElement.selectionStart,
     68            editableElement.selectionEnd
     69          );
     70      };
     71 
     72      /**
     73       * Places a double click in `editableElement` at exactly the end of `aPlaceHolderText` and press delete.
     74       * `aPlaceholderText` therefore should contain the same text as the value of the `editableElement`
     75       * up to the point where the doubleclick should happen.
     76       *
     77       * If `aSelectionValue` is defined, the selection created by the double click is compared to `aSelectionValue`.
     78       */
     79      const doubleClickAndDelete = async (aPlaceHolderText, aSelectionValue = undefined) => {
     80        placeHolder.innerHTML = aPlaceHolderText;
     81        editableElement.focus();
     82        await waitForRender();
     83        const absInputPos = editableElement.getBoundingClientRect();
     84        selectionOffset = {
     85          x: placeHolder.getBoundingClientRect().width,
     86          y: Math.floor(placeHolder.getBoundingClientRect().height / 2)
     87        };
     88        await (new test_driver.Actions()
     89          // for some reason this still doesn't work:
     90          // .pointerMove(Math.floor(selectionOffset.x), Math.floor(selectionOffset.y), { origin: editableElement })
     91          // but this does:
     92          .pointerMove(
     93            Math.floor(absInputPos.x + selectionOffset.x),
     94            Math.floor(absInputPos.y + selectionOffset.y),
     95            { origin: "viewport" }
     96          )
     97          .pointerDown()
     98          .pointerUp()
     99          .pointerDown()
    100          .pointerUp())
    101          .send()
    102        await waitForRender();
    103        if (aSelectionValue !== undefined) {
    104          assert_equals(getSelection(), aSelectionValue, "Wrong selection value!");
    105        }
    106        return test_driver.send_keys(editableElement, deleteKey);
    107      };
    108      if (editableElement.tagName.toLowerCase() == "div") {
    109        promise_test(async t => {
    110          setValue("<p>abc def<span></span></p>");
    111          await doubleClickAndDelete("abc de", "def");
    112          await waitForRender();
    113          assert_equals(
    114            getValue(),
    115            "<p>abc</p>",
    116            "The <span> at the end of the string must be removed, as well as the whitespace in between words.");
    117        }, `${editableElement.tagName}: An empty span at the end of the selection should be considered end of selection!`);
    118      }
    119      promise_test(async t => {
    120        setValue("one two");
    121        await doubleClickAndDelete("on", "one");
    122        await waitForRender();
    123        assert_equals(
    124          getValue(),
    125          "two",
    126          "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
    127        );
    128      }, `${editableElement.tagName}: Remove word at the beginning of string should remove the whitespace in between.`);
    129 
    130      promise_test(async t => {
    131        setValue("one two");
    132        await doubleClickAndDelete("one tw", "two");
    133        await waitForRender();
    134        assert_equals(
    135          getValue(),
    136          "one",
    137          "The whitespace between words must be removed when a word is selected at the end of the string and deleted!"
    138        );
    139      }, `${editableElement.tagName}: Remove word at the end of a string should remove the whitespace in between.`);
    140 
    141      promise_test(async t => {
    142        setValue("one two three");
    143        await doubleClickAndDelete("one tw", "two");
    144        await waitForRender();
    145        assert_equals(
    146          getValue(),
    147          "one three",
    148          "One whitespace between words must be removed when a word is selected and deleted!"
    149        );
    150        await waitForRender();
    151        if (editableElement.tagName.toLowerCase() == "div") {
    152          document.getSelection().setBaseAndExtent(
    153            editableElement.firstChild,
    154            0,
    155            editableElement.firstChild,
    156            3
    157          );
    158        }
    159        else {
    160          editableElement.setSelectionRange(0, 3);
    161        }
    162        await test_driver.send_keys(editableElement, deleteKey);
    163        // div[contenteditable] returns '&nbsp;three' here.
    164        assert_equals(
    165          getValue().replace(/&nbsp;/g, " "),
    166          " three",
    167          "The whitespace must not be removed when selecting a word without doubleclicking it!"
    168        );
    169 
    170      }, `${editableElement.tagName}: Remove word in the middle of a string should remove one whitespace ` +
    171      "only if selection is created by double click.");
    172 
    173      promise_test(async t => {
    174        setValue("one  two three");
    175        await doubleClickAndDelete("one  tw", "two");
    176        await waitForRender();
    177        assert_equals(
    178          getValue(),
    179          "one  three",
    180          "One whitespace between words must be removed when a word is selected and deleted!"
    181        );
    182      }, `${editableElement.tagName}: Only one whitespace character should be removed when there are multiple.`);
    183 
    184      promise_test(async t => {
    185        setValue("one  two");
    186        await doubleClickAndDelete("one  tw", "two");
    187        await waitForRender();
    188        assert_equals(
    189          getValue(),
    190          "one ",
    191          "One whitespace character between words must be removed when a word is selected and deleted!"
    192        );
    193      }, `${editableElement.tagName}: Only one whitespace character should be removed when ` +
    194      "there are multiple whitespaces and the deleted range is the end of the string.");
    195 
    196      promise_test(async t => {
    197        setValue("one two, three");
    198        await doubleClickAndDelete("one tw", "two");
    199        await waitForRender();
    200        assert_equals(
    201          getValue(),
    202          "one, three",
    203          "The whitespace in front of the selected word must be removed when punctuation follows selection!"
    204        );
    205      }, `${editableElement.tagName}: Removing a word before punctuation should remove the whitespace.`);
    206 
    207      promise_test(async t => {
    208        setValue("one, two");
    209        await doubleClickAndDelete("one, tw", "two");
    210        await waitForRender();
    211        assert_equals(
    212          getValue(),
    213          "one,",
    214          "The whitespace in front of the selected word must be removed!"
    215        );
    216      }, `${editableElement.tagName}: Remove a word after punctuation should remove the whitespace.`);
    217 
    218      promise_test(async t => {
    219        setValue("one\u00A0two, three"); // adds a &nbsp;
    220        await doubleClickAndDelete("one tw", "two");
    221        await waitForRender();
    222        assert_equals(
    223          getValue(),
    224          "one, three",
    225          "The whitespace between words must be removed when a word is selected and deleted!"
    226        );
    227      }, `${editableElement.tagName}: Removing a word between a &nbsp; and punctuation should remove the nbsp character.`);
    228 
    229      if (editableElement.tagName.toLowerCase() == "div") {
    230        promise_test(async t => {
    231          setValue("one two<br>");
    232          await doubleClickAndDelete("one tw", "two");
    233          await waitForRender();
    234          assert_equals(
    235            getValue(),
    236            "one",
    237            "The line break must be preserved!"
    238          );
    239        }, `${editableElement.tagName}: Removing a word in front of a line break should preserve the line break.`);
    240      }
    241      if (editableElement.tagName.toLowerCase() == "textarea") {
    242        promise_test(async t => {
    243          setValue("one two\n");
    244          await doubleClickAndDelete("one tw", "two");
    245          await waitForRender();
    246          assert_equals(
    247            getValue(),
    248            "one\n",
    249            "The line break must be preserved!"
    250          );
    251        }, `${editableElement.tagName}: RRemoving a word in front of a line break should preserve the line break.`);
    252      }
    253      promise_test(async t => {
    254        setValue("one two");
    255        await doubleClickAndDelete("on", "one");
    256        await waitForRender();
    257        assert_equals(
    258          getValue(),
    259          "two",
    260          "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
    261        );
    262        document.execCommand("undo", false, null);
    263        assert_equals(
    264          getValue(),
    265          "one two",
    266          "Undo action must restore the original state!"
    267        );
    268        document.execCommand("redo", false, null);
    269        assert_equals(
    270          getValue(),
    271          "two",
    272          "Redo action must remove the word and whitespace again!"
    273        );
    274      }, `${editableElement.tagName}: Undo and Redo actions should take the removed whitespace into account.`);
    275    }
    276  </script>
    277 </body>
    278 </html>