tor-browser

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

browser_selectpopup.js (28039B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 /* eslint-disable mozilla/no-arbitrary-setTimeout */
      4 
      5 // This test tests <select> in a child process. This is different than
      6 // single-process as a <menulist> is used to implement the dropdown list.
      7 
      8 // FIXME(bug 1774835): This test should be split.
      9 requestLongerTimeout(2);
     10 
     11 const XHTML_DTD =
     12  '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
     13 
     14 const PAGECONTENT =
     15  "<html xmlns='http://www.w3.org/1999/xhtml'>" +
     16  "<body onload='gChangeEvents = 0;gInputEvents = 0; gClickEvents = 0; document.getElementById(\"select\").focus();'>" +
     17  "<select id='select' oninput='gInputEvents++' onchange='gChangeEvents++' onclick='if (event.target == this) gClickEvents++'>" +
     18  "  <optgroup label='First Group'>" +
     19  "    <option value='One'>One</option>" +
     20  "    <option value='Two'>Two</option>" +
     21  "  </optgroup>" +
     22  "  <option value='Three'>Three</option>" +
     23  "  <optgroup label='Second Group' disabled='true'>" +
     24  "    <option value='Four'>Four</option>" +
     25  "    <option value='Five'>Five</option>" +
     26  "  </optgroup>" +
     27  "  <option value='Six' disabled='true'>Six</option>" +
     28  "  <optgroup label='Third Group'>" +
     29  "    <option value='Seven'>   Seven  </option>" +
     30  "    <option value='Eight'>&nbsp;&nbsp;Eight&nbsp;&nbsp;</option>" +
     31  "  </optgroup></select><input />Text" +
     32  "</body></html>";
     33 
     34 const PAGECONTENT_XSLT =
     35  "<?xml-stylesheet type='text/xml' href='#style1'?>" +
     36  "<xsl:stylesheet id='style1'" +
     37  "                version='1.0'" +
     38  "                xmlns:xsl='http://www.w3.org/1999/XSL/Transform'" +
     39  "                xmlns:html='http://www.w3.org/1999/xhtml'>" +
     40  "<xsl:template match='xsl:stylesheet'>" +
     41  PAGECONTENT +
     42  "</xsl:template>" +
     43  "</xsl:stylesheet>";
     44 
     45 const PAGECONTENT_SMALL =
     46  "<html>" +
     47  "<body><select id='one'>" +
     48  "  <option value='One'>One</option>" +
     49  "  <option value='Two'>Two</option>" +
     50  "</select><select id='two'>" +
     51  "  <option value='Three'>Three</option>" +
     52  "  <option value='Four'>Four</option>" +
     53  "</select><select id='three'>" +
     54  "  <option value='Five'>Five</option>" +
     55  "  <option value='Six'>Six</option>" +
     56  "</select></body></html>";
     57 
     58 const PAGECONTENT_GROUPS =
     59  "<html>" +
     60  "<body><select id='one'>" +
     61  "  <optgroup label='Group 1'>" +
     62  "    <option value='G1 O1'>G1 O1</option>" +
     63  "    <option value='G1 O2'>G1 O2</option>" +
     64  "    <option value='G1 O3'>G1 O3</option>" +
     65  "  </optgroup>" +
     66  "  <optgroup label='Group 2'>" +
     67  "    <option value='G2 O1'>G2 O4</option>" +
     68  "    <option value='G2 O2'>G2 O5</option>" +
     69  "    <option value='Hidden' style='display: none;'>Hidden</option>" +
     70  "  </optgroup>" +
     71  "</select></body></html>";
     72 
     73 const PAGECONTENT_SOMEHIDDEN =
     74  "<html><head><style>.hidden { display: none; }</style></head>" +
     75  "<body><select id='one'>" +
     76  "  <option value='One' style='display: none;'>OneHidden</option>" +
     77  "  <option value='Two' class='hidden'>TwoHidden</option>" +
     78  "  <option value='Three'>ThreeVisible</option>" +
     79  "  <option value='Four'style='display: table;'>FourVisible</option>" +
     80  "  <option value='Five'>FiveVisible</option>" +
     81  "  <optgroup label='GroupHidden' class='hidden'>" +
     82  "    <option value='Four'>Six.OneHidden</option>" +
     83  "    <option value='Five' style='display: block;'>Six.TwoHidden</option>" +
     84  "  </optgroup>" +
     85  "  <option value='Six' class='hidden' style='display: block;'>SevenVisible</option>" +
     86  "</select></body></html>";
     87 
     88 const PAGECONTENT_TRANSLATED =
     89  "<html><body>" +
     90  "<div id='div'>" +
     91  "<iframe id='frame' width='320' height='295' style='border: none;'" +
     92  "        src='data:text/html,<select id=select><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'>" +
     93  "</iframe>" +
     94  "</div></body></html>";
     95 
     96 function getInputEvents() {
     97  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
     98    return content.wrappedJSObject.gInputEvents;
     99  });
    100 }
    101 
    102 function getChangeEvents() {
    103  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    104    return content.wrappedJSObject.gChangeEvents;
    105  });
    106 }
    107 
    108 function getClickEvents() {
    109  return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    110    return content.wrappedJSObject.gClickEvents;
    111  });
    112 }
    113 
    114 async function doSelectTests(contentType, content) {
    115  const pageUrl = "data:" + contentType + "," + encodeURIComponent(content);
    116  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    117 
    118  let selectPopup = await openSelectPopup();
    119  let menulist = selectPopup.parentNode;
    120 
    121  let isWindows = navigator.platform.includes("Win");
    122 
    123  is(menulist.selectedIndex, 1, "Initial selection");
    124  is(
    125    selectPopup.firstElementChild.localName,
    126    "menucaption",
    127    "optgroup is caption"
    128  );
    129  is(
    130    selectPopup.firstElementChild.getAttribute("label"),
    131    "First Group",
    132    "optgroup label"
    133  );
    134  is(selectPopup.children[1].localName, "menuitem", "option is menuitem");
    135  is(selectPopup.children[1].getAttribute("label"), "One", "option label");
    136 
    137  EventUtils.synthesizeKey("KEY_ArrowDown");
    138  is(menulist.activeChild, menulist.getItemAtIndex(2), "Select item 2");
    139  is(menulist.selectedIndex, isWindows ? 2 : 1, "Select item 2 selectedIndex");
    140 
    141  EventUtils.synthesizeKey("KEY_ArrowDown");
    142  is(menulist.activeChild, menulist.getItemAtIndex(3), "Select item 3");
    143  is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
    144 
    145  EventUtils.synthesizeKey("KEY_ArrowDown");
    146 
    147  // On Windows, one can navigate on disabled menuitems
    148  is(
    149    menulist.activeChild,
    150    menulist.getItemAtIndex(9),
    151    "Skip optgroup header and disabled items select item 7"
    152  );
    153  is(
    154    menulist.selectedIndex,
    155    isWindows ? 9 : 1,
    156    "Select or skip disabled item selectedIndex"
    157  );
    158 
    159  for (let i = 0; i < 10; i++) {
    160    is(
    161      menulist.getItemAtIndex(i).disabled,
    162      i >= 4 && i <= 7,
    163      "item " + i + " disabled"
    164    );
    165  }
    166 
    167  EventUtils.synthesizeKey("KEY_ArrowUp");
    168  is(menulist.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
    169  is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
    170 
    171  is(await getInputEvents(), 0, "Before closed - number of input events");
    172  is(await getChangeEvents(), 0, "Before closed - number of change events");
    173  is(await getClickEvents(), 0, "Before closed - number of click events");
    174 
    175  EventUtils.synthesizeKey("a", { accelKey: true });
    176  await SpecialPowers.spawn(
    177    gBrowser.selectedBrowser,
    178    [{ isWindows }],
    179    function (args) {
    180      Assert.equal(
    181        String(content.getSelection()).trim(),
    182        args.isWindows ? "Text" : "",
    183        "Select all while popup is open"
    184      );
    185    }
    186  );
    187 
    188  // Backspace should not go back
    189  let handleKeyPress = function () {
    190    ok(false, "Should not get keypress event");
    191  };
    192  window.addEventListener("keypress", handleKeyPress);
    193  EventUtils.synthesizeKey("KEY_Backspace");
    194  window.removeEventListener("keypress", handleKeyPress);
    195 
    196  await hideSelectPopup();
    197 
    198  is(menulist.selectedIndex, 3, "Item 3 still selected");
    199  is(await getInputEvents(), 1, "After closed - number of input events");
    200  is(await getChangeEvents(), 1, "After closed - number of change events");
    201  is(await getClickEvents(), 0, "After closed - number of click events");
    202 
    203  // Opening and closing the popup without changing the value should not fire a change event.
    204  await openSelectPopup("click");
    205  await hideSelectPopup("escape");
    206  is(
    207    await getInputEvents(),
    208    1,
    209    "Open and close with no change - number of input events"
    210  );
    211  is(
    212    await getChangeEvents(),
    213    1,
    214    "Open and close with no change - number of change events"
    215  );
    216  is(
    217    await getClickEvents(),
    218    1,
    219    "Open and close with no change - number of click events"
    220  );
    221  EventUtils.synthesizeKey("KEY_Tab");
    222  EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
    223  is(
    224    await getInputEvents(),
    225    1,
    226    "Tab away from select with no change - number of input events"
    227  );
    228  is(
    229    await getChangeEvents(),
    230    1,
    231    "Tab away from select with no change - number of change events"
    232  );
    233  is(
    234    await getClickEvents(),
    235    1,
    236    "Tab away from select with no change - number of click events"
    237  );
    238 
    239  await openSelectPopup("click");
    240  EventUtils.synthesizeKey("KEY_ArrowDown");
    241  await hideSelectPopup("escape");
    242  is(
    243    await getInputEvents(),
    244    isWindows ? 2 : 1,
    245    "Open and close with change - number of input events"
    246  );
    247  is(
    248    await getChangeEvents(),
    249    isWindows ? 2 : 1,
    250    "Open and close with change - number of change events"
    251  );
    252  is(
    253    await getClickEvents(),
    254    2,
    255    "Open and close with change - number of click events"
    256  );
    257  EventUtils.synthesizeKey("KEY_Tab");
    258  EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
    259  is(
    260    await getInputEvents(),
    261    isWindows ? 2 : 1,
    262    "Tab away from select with change - number of input events"
    263  );
    264  is(
    265    await getChangeEvents(),
    266    isWindows ? 2 : 1,
    267    "Tab away from select with change - number of change events"
    268  );
    269  is(
    270    await getClickEvents(),
    271    2,
    272    "Tab away from select with change - number of click events"
    273  );
    274 
    275  is(
    276    selectPopup.lastElementChild.previousElementSibling.label,
    277    "Seven",
    278    "Spaces collapsed"
    279  );
    280  is(
    281    selectPopup.lastElementChild.label,
    282    "\xA0\xA0Eight\xA0\xA0",
    283    "Non-breaking spaces not collapsed"
    284  );
    285 
    286  BrowserTestUtils.removeTab(tab);
    287 }
    288 
    289 add_setup(async function () {
    290  await SpecialPowers.pushPrefEnv({
    291    set: [
    292      ["test.wait300msAfterTabSwitch", true],
    293      ["dom.forms.select.customstyling", true],
    294    ],
    295  });
    296 });
    297 
    298 add_task(async function () {
    299  await doSelectTests("text/html", PAGECONTENT);
    300 });
    301 
    302 add_task(async function () {
    303  await doSelectTests("application/xhtml+xml", XHTML_DTD + "\n" + PAGECONTENT);
    304 });
    305 
    306 add_task(async function () {
    307  await doSelectTests("application/xml", XHTML_DTD + "\n" + PAGECONTENT_XSLT);
    308 });
    309 
    310 // This test opens a select popup and removes the content node of a popup while
    311 // The popup should close if its node is removed.
    312 add_task(async function () {
    313  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
    314  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    315 
    316  // First, try it when a different <select> element than the one that is open is removed
    317  const selectPopup = await openSelectPopup("click", "#one");
    318 
    319  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    320    content.document.body.removeChild(content.document.getElementById("two"));
    321  });
    322 
    323  // Wait a bit just to make sure the popup won't close.
    324  await new Promise(resolve => setTimeout(resolve, 1000));
    325 
    326  is(selectPopup.state, "open", "Different popup did not affect open popup");
    327 
    328  await hideSelectPopup();
    329 
    330  // Next, try it when the same <select> element than the one that is open is removed
    331  await openSelectPopup("click", "#three");
    332 
    333  let popupHiddenPromise = BrowserTestUtils.waitForEvent(
    334    selectPopup,
    335    "popuphidden"
    336  );
    337  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () {
    338    content.document.body.removeChild(content.document.getElementById("three"));
    339  });
    340  await popupHiddenPromise;
    341 
    342  ok(true, "Popup hidden when select is removed");
    343 
    344  // Finally, try it when the tab is closed while the select popup is open.
    345  await openSelectPopup("click", "#one");
    346 
    347  popupHiddenPromise = BrowserTestUtils.waitForEvent(
    348    selectPopup,
    349    "popuphidden"
    350  );
    351  BrowserTestUtils.removeTab(tab);
    352  await popupHiddenPromise;
    353 
    354  ok(true, "Popup hidden when tab is closed");
    355 });
    356 
    357 // This test opens a select popup that is isn't a frame and has some translations applied.
    358 add_task(async function () {
    359  const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
    360  info(`pageUrl: data:text/html,${PAGECONTENT_TRANSLATED}`);
    361  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    362 
    363  // We need to explicitly call Element.focus() since dataURL is treated as
    364  // cross-origin, thus autofocus doesn't work there.
    365  const iframe = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
    366    return content.document.querySelector("iframe").browsingContext;
    367  });
    368  await SpecialPowers.spawn(iframe, [], async () => {
    369    const input = content.document.getElementById("select");
    370    const focusPromise = new Promise(resolve => {
    371      input.addEventListener("focus", resolve, { once: true });
    372    });
    373    input.focus();
    374    await focusPromise;
    375  });
    376 
    377  // First, get the position of the select popup when no translations have been applied.
    378  const selectPopup = await openSelectPopup();
    379 
    380  let rect = selectPopup.getBoundingClientRect();
    381  let expectedX = rect.left;
    382  let expectedY = rect.top;
    383 
    384  await hideSelectPopup();
    385 
    386  // Iterate through a set of steps which each add more translation to the select's expected position.
    387  let steps = [
    388    ["div", "transform: translateX(7px) translateY(13px);", 7, 13],
    389    [
    390      "frame",
    391      "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;",
    392      10,
    393      5,
    394    ],
    395    [
    396      "frame",
    397      "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;",
    398      -4,
    399      -3,
    400    ],
    401    ["select", "margin: 9px; transform: translateY(-3px);", 9, 6],
    402  ];
    403 
    404  for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
    405    let step = steps[stepIndex];
    406 
    407    await SpecialPowers.spawn(
    408      gBrowser.selectedBrowser,
    409      [step],
    410      async function (contentStep) {
    411        return new Promise(resolve => {
    412          let changedWin = content;
    413 
    414          let elem;
    415          if (contentStep[0] == "select") {
    416            changedWin = content.document.getElementById("frame").contentWindow;
    417            elem = changedWin.document.getElementById("select");
    418          } else {
    419            elem = content.document.getElementById(contentStep[0]);
    420          }
    421 
    422          changedWin.addEventListener(
    423            "MozAfterPaint",
    424            function () {
    425              resolve();
    426            },
    427            { once: true }
    428          );
    429 
    430          elem.style = contentStep[1];
    431          elem.getBoundingClientRect();
    432        });
    433      }
    434    );
    435 
    436    await openSelectPopup();
    437 
    438    expectedX += step[2];
    439    expectedY += step[3];
    440 
    441    // FIXME: These expectations are not aware of HiDPI environment.
    442    let popupRect = selectPopup.getBoundingClientRect();
    443    is(popupRect.left, expectedX, "step " + (stepIndex + 1) + " x");
    444    is(popupRect.top, expectedY, "step " + (stepIndex + 1) + " y");
    445 
    446    await hideSelectPopup();
    447  }
    448 
    449  BrowserTestUtils.removeTab(tab);
    450 });
    451 
    452 // Test that we get the right events when a select popup is changed.
    453 add_task(async function test_event_order() {
    454  const URL = "data:text/html," + escape(PAGECONTENT_SMALL);
    455  await BrowserTestUtils.withNewTab(
    456    {
    457      gBrowser,
    458      url: URL,
    459    },
    460    async function (browser) {
    461      // According to https://html.spec.whatwg.org/#the-select-element,
    462      // we want to fire input, change, and then click events on the
    463      // <select> (in that order) when it has changed.
    464      let expectedEnter = [
    465        {
    466          type: "input",
    467          cancelable: false,
    468          targetIsOption: false,
    469          composed: true,
    470        },
    471        {
    472          type: "change",
    473          cancelable: false,
    474          targetIsOption: false,
    475          composed: false,
    476        },
    477      ];
    478 
    479      let expectedClick = [
    480        {
    481          type: "mousedown",
    482          cancelable: true,
    483          targetIsOption: true,
    484          composed: true,
    485        },
    486        {
    487          type: "mouseup",
    488          cancelable: true,
    489          targetIsOption: true,
    490          composed: true,
    491        },
    492        {
    493          type: "input",
    494          cancelable: false,
    495          targetIsOption: false,
    496          composed: true,
    497        },
    498        {
    499          type: "change",
    500          cancelable: false,
    501          targetIsOption: false,
    502          composed: false,
    503        },
    504        {
    505          type: "click",
    506          cancelable: true,
    507          targetIsOption: true,
    508          composed: true,
    509        },
    510      ];
    511 
    512      for (let mode of ["enter", "click"]) {
    513        let expected = mode == "enter" ? expectedEnter : expectedClick;
    514        await openSelectPopup("click", mode == "enter" ? "#one" : "#two");
    515 
    516        let eventsPromise = SpecialPowers.spawn(
    517          browser,
    518          [[mode, expected]],
    519          async function ([contentMode, contentExpected]) {
    520            return new Promise(resolve => {
    521              function onEvent(event) {
    522                select.removeEventListener(event.type, onEvent);
    523                Assert.ok(
    524                  contentExpected.length,
    525                  "Unexpected event " + event.type
    526                );
    527                let expectation = contentExpected.shift();
    528                Assert.equal(
    529                  event.type,
    530                  expectation.type,
    531                  "Expected the right event order"
    532                );
    533                Assert.ok(event.bubbles, "All of these events should bubble");
    534                Assert.equal(
    535                  event.cancelable,
    536                  expectation.cancelable,
    537                  "Cancellation property should match"
    538                );
    539                Assert.equal(
    540                  event.target.localName,
    541                  expectation.targetIsOption ? "option" : "select",
    542                  "Target matches"
    543                );
    544                Assert.equal(
    545                  event.composed,
    546                  expectation.composed,
    547                  "Composed property should match"
    548                );
    549                if (!contentExpected.length) {
    550                  resolve();
    551                }
    552              }
    553 
    554              let select = content.document.getElementById(
    555                contentMode == "enter" ? "one" : "two"
    556              );
    557              for (let event of [
    558                "input",
    559                "change",
    560                "mousedown",
    561                "mouseup",
    562                "click",
    563              ]) {
    564                select.addEventListener(event, onEvent);
    565              }
    566            });
    567          }
    568        );
    569 
    570        EventUtils.synthesizeKey("KEY_ArrowDown");
    571        await hideSelectPopup(mode);
    572        await eventsPromise;
    573      }
    574    }
    575  );
    576 });
    577 
    578 async function performSelectSearchTests(win) {
    579  let browser = win.gBrowser.selectedBrowser;
    580  await SpecialPowers.spawn(browser, [], async function () {
    581    let doc = content.document;
    582    let select = doc.getElementById("one");
    583 
    584    for (var i = 0; i < 40; i++) {
    585      select.add(new content.Option("Test" + i));
    586    }
    587 
    588    select.options[1].selected = true;
    589    select.focus();
    590  });
    591 
    592  let selectPopup = await openSelectPopup(false, "select", win);
    593 
    594  let searchElement = selectPopup.querySelector(
    595    ".contentSelectDropdown-searchbox"
    596  );
    597  searchElement.focus();
    598 
    599  EventUtils.synthesizeKey("O", {}, win);
    600  is(selectPopup.children[2].hidden, false, "First option should be visible");
    601  is(selectPopup.children[3].hidden, false, "Second option should be visible");
    602 
    603  EventUtils.synthesizeKey("3", {}, win);
    604  is(selectPopup.children[2].hidden, true, "First option should be hidden");
    605  is(selectPopup.children[3].hidden, true, "Second option should be hidden");
    606  is(selectPopup.children[4].hidden, false, "Third option should be visible");
    607 
    608  EventUtils.synthesizeKey("Z", {}, win);
    609  is(selectPopup.children[4].hidden, true, "Third option should be hidden");
    610  is(
    611    selectPopup.children[1].hidden,
    612    true,
    613    "First group header should be hidden"
    614  );
    615 
    616  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    617  is(selectPopup.children[4].hidden, false, "Third option should be visible");
    618 
    619  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    620  is(
    621    selectPopup.children[5].hidden,
    622    false,
    623    "Second group header should be visible"
    624  );
    625 
    626  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    627  EventUtils.synthesizeKey("O", {}, win);
    628  EventUtils.synthesizeKey("5", {}, win);
    629  is(
    630    selectPopup.children[5].hidden,
    631    false,
    632    "Second group header should be visible"
    633  );
    634  is(
    635    selectPopup.children[1].hidden,
    636    true,
    637    "First group header should be hidden"
    638  );
    639 
    640  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    641  is(
    642    selectPopup.children[1].hidden,
    643    false,
    644    "First group header should be shown"
    645  );
    646 
    647  EventUtils.synthesizeKey("KEY_Backspace", {}, win);
    648  is(
    649    selectPopup.children[8].hidden,
    650    true,
    651    "Option hidden by content should remain hidden"
    652  );
    653 
    654  await hideSelectPopup("escape", win);
    655 }
    656 
    657 // This test checks the functionality of search in select elements with groups
    658 // and a large number of options.
    659 add_task(async function test_select_search() {
    660  await SpecialPowers.pushPrefEnv({
    661    set: [["dom.forms.selectSearch", true]],
    662  });
    663  const pageUrl = "data:text/html," + escape(PAGECONTENT_GROUPS);
    664  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    665 
    666  await performSelectSearchTests(window);
    667 
    668  BrowserTestUtils.removeTab(tab);
    669 
    670  await SpecialPowers.popPrefEnv();
    671 });
    672 
    673 // This test checks that a mousemove event is fired correctly at the menu and
    674 // not at the browser, ensuring that any mouse capture has been cleared.
    675 add_task(async function test_mousemove_correcttarget() {
    676  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
    677  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    678 
    679  const selectPopup = await openSelectPopup("mousedown");
    680 
    681  await new Promise(resolve => {
    682    window.addEventListener(
    683      "mousemove",
    684      function (event) {
    685        is(event.target.localName.indexOf("menu"), 0, "mouse over menu");
    686        resolve();
    687      },
    688      { capture: true, once: true }
    689    );
    690 
    691    EventUtils.synthesizeMouseAtCenter(selectPopup.firstElementChild, {
    692      type: "mousemove",
    693      buttons: 1,
    694    });
    695  });
    696 
    697  await BrowserTestUtils.synthesizeMouseAtCenter(
    698    "#one",
    699    { type: "mouseup" },
    700    gBrowser.selectedBrowser
    701  );
    702 
    703  await hideSelectPopup();
    704 
    705  // The popup should be closed when fullscreen mode is entered or exited.
    706  for (let steps = 0; steps < 2; steps++) {
    707    await openSelectPopup("click");
    708    let popupHiddenPromise = BrowserTestUtils.waitForEvent(
    709      selectPopup,
    710      "popuphidden"
    711    );
    712    let sizeModeChanged = BrowserTestUtils.waitForEvent(
    713      window,
    714      "sizemodechange"
    715    );
    716    BrowserCommands.fullScreen();
    717    await sizeModeChanged;
    718    await popupHiddenPromise;
    719  }
    720 
    721  BrowserTestUtils.removeTab(tab);
    722 });
    723 
    724 // This test checks when a <select> element has some options with altered display values.
    725 add_task(async function test_somehidden() {
    726  const pageUrl = "data:text/html," + escape(PAGECONTENT_SOMEHIDDEN);
    727  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    728 
    729  let selectPopup = await openSelectPopup("click");
    730 
    731  // The exact number is not needed; just ensure the height is larger than 4 items to accommodate any popup borders.
    732  Assert.greaterOrEqual(
    733    selectPopup.getBoundingClientRect().height,
    734    selectPopup.lastElementChild.getBoundingClientRect().height * 4,
    735    "Height contains at least 4 items"
    736  );
    737  Assert.less(
    738    selectPopup.getBoundingClientRect().height,
    739    selectPopup.lastElementChild.getBoundingClientRect().height * 5,
    740    "Height doesn't contain 5 items"
    741  );
    742 
    743  // The label contains the substring 'Visible' for items that are visible.
    744  // Otherwise, it is expected to be display: none.
    745  is(selectPopup.parentNode.itemCount, 9, "Correct number of items");
    746  let child = selectPopup.firstElementChild;
    747  let idx = 1;
    748  while (child) {
    749    is(
    750      getComputedStyle(child).display,
    751      child.label.indexOf("Visible") > 0 ? "flex" : "none",
    752      "Item " + idx++ + " is visible"
    753    );
    754    child = child.nextElementSibling;
    755  }
    756 
    757  await hideSelectPopup("escape");
    758  BrowserTestUtils.removeTab(tab);
    759 });
    760 
    761 // This test checks that the popup is closed when the select element is blurred.
    762 add_task(async function test_blur_hides_popup() {
    763  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
    764  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    765 
    766  await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    767    content.addEventListener(
    768      "blur",
    769      function (event) {
    770        event.preventDefault();
    771        event.stopPropagation();
    772      },
    773      true
    774    );
    775 
    776    content.document.getElementById("one").focus();
    777  });
    778 
    779  let selectPopup = await openSelectPopup();
    780 
    781  let popupHiddenPromise = BrowserTestUtils.waitForEvent(
    782    selectPopup,
    783    "popuphidden"
    784  );
    785 
    786  await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    787    content.document.getElementById("one").blur();
    788  });
    789 
    790  await popupHiddenPromise;
    791 
    792  ok(true, "Blur closed popup");
    793 
    794  BrowserTestUtils.removeTab(tab);
    795 });
    796 
    797 // Test zoom handling.
    798 add_task(async function test_zoom() {
    799  const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
    800  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    801 
    802  info("Opening the popup");
    803  const selectPopup = await openSelectPopup("click");
    804 
    805  info("Opened the popup");
    806  let nonZoomedFontSize = parseFloat(
    807    getComputedStyle(selectPopup.querySelector("menuitem")).fontSize,
    808    10
    809  );
    810 
    811  info("font-size is " + nonZoomedFontSize);
    812  await hideSelectPopup();
    813 
    814  info("Hide the popup");
    815 
    816  for (let i = 0; i < 2; ++i) {
    817    info("Testing with full zoom: " + ZoomManager.useFullZoom);
    818 
    819    // This is confusing, but does the right thing.
    820    FullZoom.setZoom(2.0, tab.linkedBrowser);
    821 
    822    info("Opening popup again");
    823    await openSelectPopup("click");
    824 
    825    let zoomedFontSize = parseFloat(
    826      getComputedStyle(selectPopup.querySelector("menuitem")).fontSize,
    827      10
    828    );
    829    info("Zoomed font-size is " + zoomedFontSize);
    830 
    831    Assert.less(
    832      Math.abs(zoomedFontSize - nonZoomedFontSize * 2.0),
    833      0.01,
    834      `Zoom should affect menu popup size, got ${zoomedFontSize}, ` +
    835        `expected ${nonZoomedFontSize * 2.0}`
    836    );
    837 
    838    await hideSelectPopup();
    839    info("Hid the popup again");
    840 
    841    ZoomManager.toggleZoom();
    842  }
    843 
    844  FullZoom.setZoom(1.0, tab.linkedBrowser); // make sure the zoom level is reset
    845  BrowserTestUtils.removeTab(tab);
    846 });
    847 
    848 // Test that input and change events are dispatched consistently (bug 1561882).
    849 add_task(async function test_event_destroys_popup() {
    850  const PAGE_CONTENT = `
    851 <!doctype html>
    852 <select>
    853  <option>a</option>
    854  <option>b</option>
    855 </select>
    856 <script>
    857 gChangeEvents = 0;
    858 gInputEvents = 0;
    859 let select = document.querySelector("select");
    860  select.addEventListener("input", function() {
    861    gInputEvents++;
    862    this.style.display = "none";
    863    this.getBoundingClientRect();
    864  })
    865  select.addEventListener("change", function() {
    866    gChangeEvents++;
    867  })
    868 </script>`;
    869 
    870  const pageUrl = "data:text/html," + escape(PAGE_CONTENT);
    871  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    872 
    873  // Test change and input events get handled consistently
    874  await openSelectPopup("click");
    875  EventUtils.synthesizeKey("KEY_ArrowDown");
    876  await hideSelectPopup();
    877 
    878  is(
    879    await getChangeEvents(),
    880    1,
    881    "Should get change and input events consistently"
    882  );
    883  is(
    884    await getInputEvents(),
    885    1,
    886    "Should get change and input events consistently (input)"
    887  );
    888 
    889  BrowserTestUtils.removeTab(tab);
    890 });
    891 
    892 add_task(async function test_label_not_text() {
    893  const PAGE_CONTENT = `
    894 <!doctype html>
    895 <select>
    896  <option label="Some nifty Label">Some Element Text Instead</option>
    897  <option label="">Element Text</option>
    898 </select>
    899 `;
    900 
    901  const pageUrl = "data:text/html," + escape(PAGE_CONTENT);
    902  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
    903 
    904  const selectPopup = await openSelectPopup("click");
    905 
    906  is(
    907    selectPopup.children[0].label,
    908    "Some nifty Label",
    909    "Use the label not the text."
    910  );
    911 
    912  is(
    913    selectPopup.children[1].label,
    914    "Element Text",
    915    "Uses the text if the label is empty, like HTMLOptionElement::GetRenderedLabel."
    916  );
    917 
    918  BrowserTestUtils.removeTab(tab);
    919 });