tor-browser

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

browser_treeWidget_keyboard_interaction.js (8532B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Tests that keyboard interaction works fine with the tree widget
      7 
      8 const TEST_URI =
      9  "data:text/html;charset=utf-8,<head>" +
     10  "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
     11  "ets.css'></head><body><div></div><span></span></body>";
     12 const {
     13  TreeWidget,
     14 } = require("resource://devtools/client/shared/widgets/TreeWidget.js");
     15 
     16 add_task(async function () {
     17  await SpecialPowers.pushPrefEnv({
     18    set: [["security.allow_unsafe_parent_loads", true]],
     19  });
     20 
     21  await addTab("about:blank");
     22  const { host, win, doc } = await createHost("bottom", TEST_URI);
     23 
     24  // Creating a host is not correctly waiting when DevTools run in content frame
     25  // See Bug 1571421.
     26  await wait(1000);
     27 
     28  const tree = new TreeWidget(doc.querySelector("div"), {
     29    defaultType: "store",
     30  });
     31 
     32  populateTree(tree, doc);
     33 
     34  await testKeyboardInteraction(tree, win);
     35 
     36  tree.destroy();
     37  host.destroy();
     38  gBrowser.removeCurrentTab();
     39 });
     40 
     41 function populateTree(tree, doc) {
     42  tree.add([
     43    {
     44      id: "level1",
     45      label: "Level 1",
     46    },
     47    {
     48      id: "level2-1",
     49      label: "Level 2",
     50    },
     51    {
     52      id: "level3-1",
     53      label: "Level 3 - Child 1",
     54      type: "dir",
     55    },
     56  ]);
     57  tree.add([
     58    "level1",
     59    "level2-1",
     60    { id: "level3-2", label: "Level 3 - Child 2" },
     61  ]);
     62  tree.add([
     63    "level1",
     64    "level2-1",
     65    { id: "level3-3", label: "Level 3 - Child 3" },
     66  ]);
     67  tree.add([
     68    "level1",
     69    {
     70      id: "level2-2",
     71      label: "Level 2.1",
     72    },
     73    {
     74      id: "level3-1",
     75      label: "Level 3.1",
     76    },
     77  ]);
     78  tree.add([
     79    {
     80      id: "level1",
     81      label: "Level 1",
     82    },
     83    {
     84      id: "level2",
     85      label: "Level 2",
     86    },
     87    {
     88      id: "level3",
     89      label: "Level 3",
     90      type: "js",
     91    },
     92  ]);
     93  tree.add(["level1.1", "level2", { id: "level3", type: "url" }]);
     94 
     95  // Adding a new non text item in the tree.
     96  const node = doc.createElement("div");
     97  node.textContent = "Foo Bar";
     98  node.className = "foo bar";
     99  tree.add([
    100    {
    101      id: "level1.2",
    102      node,
    103      attachment: {
    104        foo: "bar",
    105      },
    106    },
    107  ]);
    108 }
    109 
    110 // Sends a click event on the passed DOM node in an async manner
    111 function click(node) {
    112  const win = node.ownerDocument.defaultView;
    113  executeSoon(() => EventUtils.synthesizeMouseAtCenter(node, {}, win));
    114 }
    115 
    116 /**
    117 * Tests if pressing navigation keys on the tree items does the expected behavior
    118 */
    119 async function testKeyboardInteraction(tree, win) {
    120  info("Testing keyboard interaction with the tree");
    121  const waitForSelect = () =>
    122    new Promise(resolve => {
    123      tree.once("select", (d, a) => resolve({ data: d, attachment: a }));
    124    });
    125 
    126  info("clicking on first top level item");
    127  let node = tree.root.children.firstChild.firstChild;
    128 
    129  // The select event handler will be called before the click event hasn't
    130  // fully finished, so wait for both of them.
    131  const clicked = once(node, "click");
    132  let onTreeSelect = waitForSelect();
    133  click(node);
    134 
    135  info("Wait for the click event");
    136  await clicked;
    137 
    138  info("Wait for the select event on tree");
    139  await onTreeSelect;
    140 
    141  node = tree.root.children.firstChild.nextSibling.firstChild;
    142  // node should not have selected class
    143  ok(
    144    !node.classList.contains("theme-selected"),
    145    "Node should not have selected class"
    146  );
    147  ok(!node.hasAttribute("expanded"), "Node is not expanded");
    148 
    149  info("Pressing down key to select next item");
    150  onTreeSelect = waitForSelect();
    151  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    152 
    153  let { data, attachment } = await onTreeSelect;
    154  is(data.length, 1, "Correct level item was selected after keydown");
    155  is(data[0], "level1", "Correct item was selected after pressing down");
    156  ok(!attachment, "null attachment was emitted");
    157  ok(node.classList.contains("theme-selected"), "Node has selected class");
    158  ok(node.hasAttribute("expanded"), "Node is expanded now");
    159 
    160  info("Pressing down key again to select next item");
    161  onTreeSelect = waitForSelect();
    162  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    163  ({ data, attachment } = await onTreeSelect);
    164  is(
    165    data.length,
    166    2,
    167    "Correct level item was selected after second down keypress"
    168  );
    169  is(data[0], "level1", "Correct parent level");
    170  is(data[1], "level2", "Correct second level");
    171 
    172  info("Pressing down key again to select next item");
    173  onTreeSelect = waitForSelect();
    174  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    175  ({ data, attachment } = await onTreeSelect);
    176  is(
    177    data.length,
    178    3,
    179    "Correct level item was selected after third down keypress"
    180  );
    181  is(data[0], "level1", "Correct parent level");
    182  is(data[1], "level2", "Correct second level");
    183  is(data[2], "level3", "Correct third level");
    184 
    185  info("Pressing down key again to select next item");
    186  onTreeSelect = waitForSelect();
    187  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    188  ({ data, attachment } = await onTreeSelect);
    189  is(
    190    data.length,
    191    2,
    192    "Correct level item was selected after fourth down keypress"
    193  );
    194  is(data[0], "level1", "Correct parent level");
    195  is(data[1], "level2-1", "Correct second level");
    196 
    197  const waitForKeydown = () =>
    198    new Promise(resolve => {
    199      tree.root.children.addEventListener(
    200        "keydown",
    201        () => {
    202          // executeSoon so that other listeners on the same method are executed first
    203          executeSoon(() => resolve());
    204        },
    205        { once: true }
    206      );
    207    });
    208 
    209  // pressing left to check expand collapse feature.
    210  // This does not emit any event, so listening for keypress
    211  let onTreeKeydown = waitForKeydown();
    212  info("Pressing left key to collapse the item");
    213  node = tree._selectedLabel;
    214  ok(node.hasAttribute("expanded"), "Item is expanded before left keypress");
    215  EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
    216  await onTreeKeydown;
    217 
    218  ok(
    219    !node.hasAttribute("expanded"),
    220    "Item is not expanded after left keypress"
    221  );
    222 
    223  // pressing left on collapsed item should select the previous item
    224 
    225  info("Pressing left key on collapsed item to select previous");
    226  onTreeSelect = waitForSelect();
    227  // parent node should have no effect of this keypress
    228  node = tree.root.children.firstChild.nextSibling.firstChild;
    229  ok(node.hasAttribute("expanded"), "Parent is expanded");
    230  EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
    231  ({ data } = await onTreeSelect);
    232  is(
    233    data.length,
    234    3,
    235    "Correct level item was selected after second left keypress"
    236  );
    237  is(data[0], "level1", "Correct parent level");
    238  is(data[1], "level2", "Correct second level");
    239  is(data[2], "level3", "Correct third level");
    240  ok(
    241    node.hasAttribute("expanded"),
    242    "Parent is still expanded after left keypress"
    243  );
    244 
    245  // pressing down again
    246 
    247  info("Pressing down key to select next item");
    248  onTreeSelect = waitForSelect();
    249  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    250  ({ data, attachment } = await onTreeSelect);
    251  is(
    252    data.length,
    253    2,
    254    "Correct level item was selected after fifth down keypress"
    255  );
    256  is(data[0], "level1", "Correct parent level");
    257  is(data[1], "level2-1", "Correct second level");
    258 
    259  // collapsing the item to check expand feature.
    260  onTreeKeydown = waitForKeydown();
    261  info("Pressing left key to collapse the item");
    262  node = tree._selectedLabel;
    263  ok(node.hasAttribute("expanded"), "Item is expanded before left keypress");
    264  EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
    265  await onTreeKeydown;
    266  ok(!node.hasAttribute("expanded"), "Item is collapsed after left keypress");
    267 
    268  // pressing right should expand this now.
    269  onTreeKeydown = waitForKeydown();
    270  info("Pressing right key to expend the collapsed item");
    271  node = tree._selectedLabel;
    272  ok(!node.hasAttribute("expanded"), "Item is collapsed before right keypress");
    273  EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
    274  await onTreeKeydown;
    275  ok(node.hasAttribute("expanded"), "Item is expanded after right keypress");
    276 
    277  // selecting last item node to test edge navigation case
    278 
    279  tree.selectedItem = ["level1.1", "level2", "level3"];
    280  node = tree._selectedLabel;
    281  // pressing down again should not change selection
    282  onTreeKeydown = waitForKeydown();
    283  info("Pressing down key on last item of the tree");
    284  EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    285  await onTreeKeydown;
    286 
    287  ok(
    288    tree.isSelected(["level1.1", "level2", "level3"]),
    289    "Last item is still selected after pressing down on last item of the tree"
    290  );
    291 }