tor-browser

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

test_contenteditable_focus.html (13075B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <title>Test for contenteditable focus</title>
      6  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      7  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      8 </head>
      9 <body>
     10 <div id="display">
     11  First text in this document.<br>
     12  <input id="inputText" type="text"><br>
     13  <input id="inputTextReadonly" type="text" readonly><br>
     14  <input id="inputButton" type="button" value="input[type=button]"><br>
     15  <button id="button">button</button><br>
     16  <div id="primaryEditor" contenteditable="true">
     17    editable contents.<br>
     18    <input id="inputTextInEditor" type="text"><br>
     19    <input id="inputTextReadonlyInEditor" type="text" readonly><br>
     20    <input id="inputButtonInEditor" type="button" value="input[type=button]"><br>
     21    <button id="buttonInEditor">button</button><br>
     22    <div id="noeditableInEditor" contenteditable="false">
     23      <span id="spanInNoneditableInEditor">span element in noneditable in editor</span><br>
     24      <input id="inputTextInNoneditableInEditor" type="text"><br>
     25      <input id="inputTextReadonlyInNoneditableInEditor" type="text" readonly><br>
     26      <input id="inputButtonInNoneditableInEditor" type="button" value="input[type=button]"><br>
     27      <button id="buttonInNoneditableInEditor">button</button><br>
     28    </div>
     29    <span id="spanInEditor">span element in editor</span><br>
     30  </div>
     31  <div id="otherEditor" contenteditable="true">
     32    other editor.
     33  </div>
     34 </div>
     35 <div id="content" style="display: none">
     36 
     37 </div>
     38 <pre id="test">
     39 </pre>
     40 
     41 <script>
     42 "use strict";
     43 
     44 SimpleTest.waitForExplicitFinish();
     45 SimpleTest.waitForFocus(() => {
     46  function getNodeDescription(aNode) {
     47    if (aNode === undefined) {
     48      return "undefined";
     49    }
     50    if (aNode === null) {
     51      return "null";
     52    }
     53    switch (aNode.nodeType) {
     54      case Node.TEXT_NODE:
     55        return `${aNode.nodeName}, "${aNode.data.replace(/\n/g, "\\n")}"`;
     56      case Node.ELEMENT_NODE:
     57        return `<${aNode.tagName.toLowerCase()}${
     58          aNode.getAttribute("id") !== null
     59            ? ` id="${aNode.getAttribute("id")}"`
     60            : ""
     61        }${
     62          aNode.getAttribute("readonly") !== null
     63            ? " readonly"
     64            : ""
     65        }${
     66          aNode.getAttribute("type") !== null
     67            ? ` type="${aNode.getAttribute("type")}"`
     68            : ""
     69        }>`;
     70    }
     71    return aNode.nodeName;
     72  }
     73 
     74  const fm = SpecialPowers.Services.focus;
     75  // XXX using selCon for checking the visibility of the caret, however,
     76  // selCon is shared in document, cannot get the element of owner of the
     77  // caret from javascript?
     78  const selCon = SpecialPowers.wrap(window).docShell.
     79        QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
     80        getInterface(SpecialPowers.Ci.nsISelectionDisplay).
     81        QueryInterface(SpecialPowers.Ci.nsISelectionController);
     82 
     83  const primaryEditor = document.getElementById("primaryEditor");
     84  const spanInEditor = document.getElementById("spanInEditor");
     85  const otherEditor = document.getElementById("otherEditor");
     86 
     87  (function test_initial_state_on_load() {
     88    is(
     89      getSelection().rangeCount,
     90      0,
     91      "There should be no selection range at start"
     92    );
     93    ok(!selCon.caretVisible, "The caret should not be visible in the document");
     94    // Move focus to <input type="text"> in the primary editor
     95    primaryEditor.querySelector("input[type=text]").focus();
     96    is(
     97      SpecialPowers.unwrap(fm.focusedElement),
     98      primaryEditor.querySelector("input[type=text]"),
     99      '<input type="text"> in the primary editor should get focus'
    100    );
    101    todo_is(
    102      getSelection().rangeCount,
    103      0,
    104      'There should be no selection range after calling focus() of <input type="text"> in the primary editor'
    105    );
    106    ok(
    107      selCon.caretVisible,
    108      'The caret should not be visible in the <input type="text"> in the primary editor'
    109    );
    110  })();
    111  // Move focus to the editor
    112  (function test_move_focus_from_child_input_to_parent_editor() {
    113    primaryEditor.focus();
    114    is(
    115      SpecialPowers.unwrap(fm.focusedElement),
    116      primaryEditor,
    117      `The editor should steal focus from <input type="text"> in the primary editor with calling its focus() (got ${
    118        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    119      }`
    120    );
    121    is(
    122      getSelection().rangeCount,
    123      1,
    124      "There should be one range after focus() of the editor is called"
    125    );
    126    const range = getSelection().getRangeAt(0);
    127    ok(
    128      range.collapsed,
    129      "The selection range should be collapsed (immediately after calling focus() of the editor)"
    130    );
    131    is(
    132      range.startContainer,
    133      primaryEditor.firstChild,
    134      `The selection range should be in the first text node of the editor (immediately after calling focus() of the editor, got ${
    135        getNodeDescription(range.startContainer)
    136      })`
    137    );
    138    ok(
    139      selCon.caretVisible,
    140      "The caret should be visible in the primary editor (immediately after calling focus() of the editor)"
    141    );
    142  })();
    143  // Move focus to other editor
    144  (function test_move_focus_from_editor_to_the_other_editor() {
    145    otherEditor.focus();
    146    is(
    147      SpecialPowers.unwrap(fm.focusedElement),
    148      otherEditor,
    149      `The other editor should steal focus from the editor (got ${
    150        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    151      }`
    152    );
    153    is(
    154      getSelection().rangeCount,
    155      1,
    156      "There should be one range after focus() of the other editor is called"
    157    );
    158    const range = getSelection().getRangeAt(0);
    159    ok(
    160      range.collapsed,
    161      "The selection range should be collapsed (immediately after calling focus() of the other editor)"
    162    );
    163    is(
    164      range.startContainer,
    165      otherEditor.firstChild,
    166      `The selection range should be in the first text node of the editor (immediately after calling focus() of the other editor, got ${
    167        getNodeDescription(range.startContainer)
    168      })`
    169    );
    170    ok(
    171      selCon.caretVisible,
    172      "The caret should be visible in the primary editor (immediately after calling focus() of the other editor)"
    173    );
    174  })();
    175  // Move focus to <input type="text"> in the primary editor
    176  (function test_move_focus_from_the_other_editor_to_input_in_the_editor() {
    177    primaryEditor.querySelector("input[type=text]").focus();
    178    is(
    179      SpecialPowers.unwrap(fm.focusedElement),
    180      primaryEditor.querySelector("input[type=text]"),
    181      `<input type="text"> in the primary editor should steal focus from the other editor (got ${
    182        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    183      }`);
    184    is(
    185      getSelection().rangeCount,
    186      1,
    187      'There should be one range after focus() of the <input type="text"> in the primary editor is called'
    188    );
    189    const range = getSelection().getRangeAt(0);
    190    ok(
    191      range.collapsed,
    192      'The selection range should be collapsed (immediately after calling focus() of the <input type="text"> in the primary editor)'
    193    );
    194    // XXX maybe, the caret can stay on the other editor if it's better.
    195    is(
    196      range.startContainer,
    197      primaryEditor.firstChild,
    198      `The selection range should be in the first text node of the editor (immediately after calling focus() of the <input type="text"> in the primary editor, got ${
    199        getNodeDescription(range.startContainer)
    200      })`
    201    );
    202    ok(
    203      selCon.caretVisible,
    204      'The caret should be visible in the <input type="text"> (immediately after calling focus() of the <input type="text"> in the primary editor)'
    205    );
    206  })();
    207  // Move focus to the other editor again
    208  (function test_move_focus_from_the_other_editor_to_the_editor_with_Selection_API() {
    209    otherEditor.focus();
    210    is(
    211      SpecialPowers.unwrap(fm.focusedElement),
    212      otherEditor,
    213      `The other editor should steal focus from the <input type="text"> in the primary editor with its focus() (got ${
    214        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    215      })`
    216    );
    217    // Set selection to the span element in the primary editor.
    218    getSelection().collapse(spanInEditor.firstChild, 5);
    219    is(
    220      getSelection().rangeCount,
    221      1,
    222      "There should be one selection range after collapsing selection into the <span> in the primary editor when the other editor has focus"
    223    );
    224    is(
    225      SpecialPowers.unwrap(fm.focusedElement),
    226      primaryEditor,
    227      `The editor should steal focus from the other editor with Selection API (got ${
    228        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    229      }`
    230    );
    231    ok(
    232      selCon.caretVisible,
    233      "The caret should be visible in the primary editor (immediately after moving focus with Selection API)"
    234    );
    235  })();
    236  // Move focus to the editor
    237  (function test_move_focus() {
    238    primaryEditor.focus();
    239    is(
    240      SpecialPowers.unwrap(fm.focusedElement),
    241      primaryEditor,
    242      "The editor should keep having focus (immediately after calling focus() of the editor when it has focus)"
    243    );
    244    is(
    245      getSelection().rangeCount,
    246      1,
    247      "There should be one selection range in the primary editor (immediately after calling focus() of the editor when it has focus)"
    248    );
    249    const range = getSelection().getRangeAt(0);
    250    ok(
    251      range.collapsed,
    252      "The selection range should be collapsed in the primary editor (immediately after calling focus() of the editor when it has focus)"
    253    );
    254    is(
    255      range.startOffset,
    256      5,
    257      "The startOffset of the selection range shouldn't be changed (immediately after calling focus() of the editor when it has focus)"
    258    );
    259    is(
    260      range.startContainer.parentNode,
    261      spanInEditor,
    262      `The startContainer of the selection range shouldn't be changed (immediately after calling focus() of the editor when it has focus, got ${
    263        getNodeDescription(range.startContainer)
    264      })`);
    265    ok(
    266      selCon.caretVisible,
    267      "The caret should be visible in the primary editor (immediately after calling focus() of the editor when it has focus)"
    268    );
    269  })();
    270 
    271  // Move focus to each focusable element in the primary editor.
    272  function test_move_focus_from_the_editor(aTargetElement, aFocusable, aCaretVisible) {
    273    primaryEditor.focus();
    274    is(
    275      SpecialPowers.unwrap(fm.focusedElement),
    276      primaryEditor,
    277      `The editor should have focus at preparing to move focus to ${
    278        getNodeDescription(aTargetElement)
    279      } (got ${
    280        getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    281      }`);
    282    aTargetElement.focus();
    283    if (aFocusable) {
    284      is(
    285        SpecialPowers.unwrap(fm.focusedElement),
    286        aTargetElement,
    287        `${
    288          getNodeDescription(aTargetElement)
    289        } should get focus with calling its focus() (got ${
    290          getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    291        }`
    292      );
    293    } else {
    294      is(
    295        SpecialPowers.unwrap(fm.focusedElement),
    296        primaryEditor,
    297        `${
    298          getNodeDescription(aTargetElement)
    299        } should not take focus with calling its focus() (got ${
    300          getNodeDescription(SpecialPowers.unwrap(fm.focusedElement))
    301        }`
    302      );
    303    }
    304    is(
    305      selCon.caretVisible,
    306      aCaretVisible,
    307      `The caret ${
    308        aCaretVisible ? "should" : "should not"
    309      } visible after calling focus() of ${
    310          getNodeDescription(aTargetElement)
    311      }`
    312    );
    313  }
    314  test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=text]"), true, true);
    315  test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=text][readonly]"), true, true);
    316  // XXX shouldn't the caret become invisible?
    317  test_move_focus_from_the_editor(primaryEditor.querySelector("input[type=button]"), true, true);
    318  test_move_focus_from_the_editor(primaryEditor.querySelector("button"), true, true);
    319  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false]"), false, true);
    320  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > span"), false, true);
    321  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=text]"), true, true);
    322  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=text][readonly]"), true, true);
    323  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > input[type=button]"), true, false);
    324  test_move_focus_from_the_editor(primaryEditor.querySelector("div[contenteditable=false] > button"), true, false);
    325  test_move_focus_from_the_editor(spanInEditor, false, true);
    326  test_move_focus_from_the_editor(document.querySelector("input[type=text]"), true, true);
    327  test_move_focus_from_the_editor(document.querySelector("input[type=text][readonly]"), true, true);
    328  test_move_focus_from_the_editor(document.querySelector("input[type=button]"), true, false);
    329  test_move_focus_from_the_editor(document.querySelector("button"), true, false);
    330 
    331  SimpleTest.finish();
    332 });
    333 </script>
    334 </body>
    335 </html>