tor-browser

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

browser_subdialogs.js (21089B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 /**
      7 * Tests for the sub-dialog infrastructure, not for actual sub-dialog functionality.
      8 */
      9 
     10 const gDialogURL = getRootDirectory(gTestPath) + "subdialog.xhtml";
     11 const gDialogURL2 = getRootDirectory(gTestPath) + "subdialog2.xhtml";
     12 
     13 function open_subdialog_and_test_generic_start_state(
     14  browser,
     15  domcontentloadedFn,
     16  url = gDialogURL
     17 ) {
     18  let domcontentloadedFnStr = domcontentloadedFn
     19    ? "(" + domcontentloadedFn.toString() + ")()"
     20    : "";
     21  return SpecialPowers.spawn(
     22    browser,
     23    [{ url, domcontentloadedFnStr }],
     24    async function (args) {
     25      let rv = { acceptCount: 0 };
     26      let win = content.window;
     27      content.gSubDialog.open(args.url, undefined, rv);
     28      let subdialog = content.gSubDialog._topDialog;
     29 
     30      info("waiting for subdialog DOMFrameContentLoaded");
     31      let dialogOpenPromise;
     32      await new Promise(resolve => {
     33        win.addEventListener(
     34          "DOMFrameContentLoaded",
     35          function frameContentLoaded(ev) {
     36            // We can get events for loads in other frames, so we have to filter
     37            // those out.
     38            if (ev.target != subdialog._frame) {
     39              return;
     40            }
     41            win.removeEventListener(
     42              "DOMFrameContentLoaded",
     43              frameContentLoaded
     44            );
     45            dialogOpenPromise = ContentTaskUtils.waitForEvent(
     46              subdialog._overlay,
     47              "dialogopen"
     48            );
     49            resolve();
     50          },
     51          { capture: true }
     52        );
     53      });
     54      let result;
     55      if (args.domcontentloadedFnStr) {
     56        // eslint-disable-next-line no-eval
     57        result = eval(args.domcontentloadedFnStr);
     58      }
     59 
     60      info("waiting for subdialog load");
     61      await dialogOpenPromise;
     62      info("subdialog window is loaded");
     63 
     64      let expectedStyleSheetURLs = subdialog._injectedStyleSheets;
     65      let foundStyleSheetURLs = Array.from(
     66        subdialog._frame.contentDocument.styleSheets,
     67        s => s.href
     68      );
     69 
     70      Assert.greaterOrEqual(
     71        expectedStyleSheetURLs.length,
     72        0,
     73        "Should be at least one injected stylesheet."
     74      );
     75      // We can't test that the injected stylesheets come later if those are
     76      // the only ones. If this test fails it indicates the test needs to be
     77      // changed somehow.
     78      Assert.greater(
     79        foundStyleSheetURLs.length,
     80        expectedStyleSheetURLs.length,
     81        "Should see at least all one additional stylesheet from the document."
     82      );
     83 
     84      // The expected stylesheets should be at the end of the list of loaded
     85      // stylesheets.
     86      foundStyleSheetURLs = foundStyleSheetURLs.slice(
     87        foundStyleSheetURLs.length - expectedStyleSheetURLs.length
     88      );
     89 
     90      Assert.deepEqual(
     91        foundStyleSheetURLs,
     92        expectedStyleSheetURLs,
     93        "Should have seen the injected stylesheets in the correct order"
     94      );
     95 
     96      Assert.ok(
     97        !!subdialog._frame.contentWindow,
     98        "The dialog should be non-null"
     99      );
    100      Assert.notEqual(
    101        subdialog._frame.contentWindow.location.toString(),
    102        "about:blank",
    103        "Subdialog URL should not be about:blank"
    104      );
    105      Assert.equal(
    106        win.getComputedStyle(subdialog._overlay).visibility,
    107        "visible",
    108        "Overlay should be visible"
    109      );
    110 
    111      return result;
    112    }
    113  );
    114 }
    115 
    116 async function close_subdialog_and_test_generic_end_state(
    117  browser,
    118  closingFn,
    119  closingButton,
    120  acceptCount,
    121  options
    122 ) {
    123  let getDialogsCount = () => {
    124    return SpecialPowers.spawn(
    125      browser,
    126      [],
    127      () => content.window.gSubDialog._dialogs.length
    128    );
    129  };
    130  let getStackChildrenCount = () => {
    131    return SpecialPowers.spawn(
    132      browser,
    133      [],
    134      () => content.window.gSubDialog._dialogStack.children.length
    135    );
    136  };
    137  let dialogclosingPromise = SpecialPowers.spawn(
    138    browser,
    139    [{ closingButton, acceptCount }],
    140    async function (expectations) {
    141      let win = content.window;
    142      let subdialog = win.gSubDialog._topDialog;
    143      let frame = subdialog._frame;
    144 
    145      let frameWinUnload = ContentTaskUtils.waitForEvent(
    146        frame.contentWindow,
    147        "unload",
    148        true
    149      );
    150 
    151      let actualAcceptCount;
    152      info("waiting for dialogclosing");
    153      info("URI " + frame.currentURI?.spec);
    154      let closingEvent = await ContentTaskUtils.waitForEvent(
    155        frame.contentWindow,
    156        "dialogclosing",
    157        true,
    158        () => {
    159          actualAcceptCount = frame.contentWindow?.arguments[0].acceptCount;
    160          return true;
    161        }
    162      );
    163 
    164      info("Waiting for subdialog unload");
    165      await frameWinUnload;
    166 
    167      let contentClosingButton = closingEvent.detail.button;
    168 
    169      Assert.notEqual(
    170        win.getComputedStyle(subdialog._overlay).visibility,
    171        "visible",
    172        "overlay is not visible"
    173      );
    174      Assert.equal(
    175        frame.getAttribute("style"),
    176        null,
    177        "inline styles should be cleared"
    178      );
    179      Assert.equal(
    180        contentClosingButton,
    181        expectations.closingButton,
    182        "closing event should indicate button was '" +
    183          expectations.closingButton +
    184          "'"
    185      );
    186      Assert.equal(
    187        actualAcceptCount,
    188        expectations.acceptCount,
    189        "should be 1 if accepted, 0 if canceled, undefined if closed w/out button"
    190      );
    191    }
    192  );
    193  let initialDialogsCount = await getDialogsCount();
    194  let initialStackChildrenCount = await getStackChildrenCount();
    195  if (options && options.runClosingFnOutsideOfContentTask) {
    196    await closingFn();
    197  } else {
    198    SpecialPowers.spawn(browser, [], closingFn);
    199  }
    200 
    201  await dialogclosingPromise;
    202  let endDialogsCount = await getDialogsCount();
    203  let endStackChildrenCount = await getStackChildrenCount();
    204  Assert.equal(
    205    initialDialogsCount - 1,
    206    endDialogsCount,
    207    "dialog count should decrease by 1"
    208  );
    209  Assert.equal(
    210    initialStackChildrenCount - 1,
    211    endStackChildrenCount,
    212    "stack children count should decrease by 1"
    213  );
    214 }
    215 
    216 let tab;
    217 
    218 add_setup(async function () {
    219  await SpecialPowers.pushPrefEnv({
    220    set: [["security.allow_eval_with_system_principal", true]],
    221  });
    222 
    223  tab = await BrowserTestUtils.openNewForegroundTab(
    224    gBrowser,
    225    "about:preferences"
    226  );
    227 
    228  registerCleanupFunction(() => {
    229    gBrowser.removeTab(tab);
    230  });
    231 });
    232 
    233 add_task(
    234  async function check_titlebar_focus_returnval_titlechanges_accepting() {
    235    await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    236 
    237    let domtitlechangedPromise = BrowserTestUtils.waitForEvent(
    238      tab.linkedBrowser,
    239      "DOMTitleChanged"
    240    );
    241    await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    242      let dialog = content.window.gSubDialog._topDialog;
    243      let dialogWin = dialog._frame.contentWindow;
    244      let dialogTitleElement = dialog._titleElement;
    245      Assert.equal(
    246        dialogTitleElement.textContent,
    247        "Sample sub-dialog",
    248        "Title should be correct initially"
    249      );
    250      Assert.equal(
    251        dialogWin.document.activeElement.value,
    252        "Default text",
    253        "Textbox with correct text is focused"
    254      );
    255      dialogWin.document.title = "Updated title";
    256    });
    257 
    258    info("waiting for DOMTitleChanged event");
    259    await domtitlechangedPromise;
    260 
    261    SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    262      let dialogTitleElement =
    263        content.window.gSubDialog._topDialog._titleElement;
    264      Assert.equal(
    265        dialogTitleElement.textContent,
    266        "Updated title",
    267        "subdialog should have updated title"
    268      );
    269    });
    270 
    271    // Accept the dialog
    272    await close_subdialog_and_test_generic_end_state(
    273      tab.linkedBrowser,
    274      function () {
    275        content.window.gSubDialog._topDialog._frame.contentDocument
    276          .getElementById("subDialog")
    277          .acceptDialog();
    278      },
    279      "accept",
    280      1
    281    );
    282  }
    283 );
    284 
    285 add_task(async function check_canceling_dialog() {
    286  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    287 
    288  info("canceling the dialog");
    289  await close_subdialog_and_test_generic_end_state(
    290    tab.linkedBrowser,
    291    function () {
    292      content.window.gSubDialog._topDialog._frame.contentDocument
    293        .getElementById("subDialog")
    294        .cancelDialog();
    295    },
    296    "cancel",
    297    0
    298  );
    299 });
    300 
    301 add_task(async function check_reopening_dialog() {
    302  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    303  info("opening another dialog which will close the first");
    304  await open_subdialog_and_test_generic_start_state(
    305    tab.linkedBrowser,
    306    "",
    307    gDialogURL2
    308  );
    309 
    310  SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    311    let win = content.window;
    312    let dialogs = win.gSubDialog._dialogs;
    313    let lowerDialog = dialogs[0];
    314    let topDialog = dialogs[1];
    315    Assert.equal(dialogs.length, 2, "There should be two visible dialogs");
    316    Assert.equal(
    317      win.getComputedStyle(topDialog._overlay).visibility,
    318      "visible",
    319      "The top dialog should be visible"
    320    );
    321    Assert.equal(
    322      win.getComputedStyle(lowerDialog._overlay).visibility,
    323      "visible",
    324      "The lower dialog should be visible"
    325    );
    326    Assert.equal(
    327      win.getComputedStyle(topDialog._overlay).backgroundColor,
    328      "oklch(0 0 0 / 0.5)",
    329      "The top dialog should have a semi-transparent overlay"
    330    );
    331    Assert.equal(
    332      win.getComputedStyle(lowerDialog._overlay).backgroundColor,
    333      "rgba(0, 0, 0, 0)",
    334      "The lower dialog should not have an overlay"
    335    );
    336  });
    337 
    338  info("closing two dialogs");
    339  await close_subdialog_and_test_generic_end_state(
    340    tab.linkedBrowser,
    341    function () {
    342      content.window.gSubDialog._topDialog._frame.contentDocument
    343        .getElementById("subDialog")
    344        .acceptDialog();
    345    },
    346    "accept",
    347    1
    348  );
    349  await close_subdialog_and_test_generic_end_state(
    350    tab.linkedBrowser,
    351    function () {
    352      content.window.gSubDialog._topDialog._frame.contentDocument
    353        .getElementById("subDialog")
    354        .acceptDialog();
    355    },
    356    "accept",
    357    1
    358  );
    359 });
    360 
    361 add_task(async function check_opening_while_closing() {
    362  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    363  info("closing");
    364  content.window.gSubDialog._topDialog.close();
    365  info("reopening immediately after calling .close()");
    366  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    367  await close_subdialog_and_test_generic_end_state(
    368    tab.linkedBrowser,
    369    function () {
    370      content.window.gSubDialog._topDialog._frame.contentDocument
    371        .getElementById("subDialog")
    372        .acceptDialog();
    373    },
    374    "accept",
    375    1
    376  );
    377 });
    378 
    379 add_task(async function window_close_on_dialog() {
    380  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    381 
    382  info("canceling the dialog");
    383  await close_subdialog_and_test_generic_end_state(
    384    tab.linkedBrowser,
    385    function () {
    386      content.window.gSubDialog._topDialog._frame.contentWindow.close();
    387    },
    388    null,
    389    0
    390  );
    391 });
    392 
    393 add_task(async function click_close_button_on_dialog() {
    394  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    395 
    396  info("canceling the dialog");
    397  await close_subdialog_and_test_generic_end_state(
    398    tab.linkedBrowser,
    399    function () {
    400      return BrowserTestUtils.synthesizeMouseAtCenter(
    401        ".dialogClose",
    402        {},
    403        tab.linkedBrowser
    404      );
    405    },
    406    null,
    407    0,
    408    { runClosingFnOutsideOfContentTask: true }
    409  );
    410 });
    411 
    412 add_task(async function background_click_should_close_dialog() {
    413  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    414 
    415  // Clicking on an inactive part of dialog itself should not close the dialog.
    416  // Click the dialog title bar here to make sure nothing happens.
    417  info("clicking the dialog title bar");
    418  // We intentionally turn off this a11y check, because the following click
    419  // is purposefully targeting a non-interactive element to confirm the opened
    420  // dialog won't be dismissed. It is not meant to be interactive and is not
    421  // expected to be accessible, therefore this rule check shall be ignored by
    422  // a11y_checks suite.
    423  AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
    424  BrowserTestUtils.synthesizeMouseAtCenter(
    425    ".dialogTitle",
    426    {},
    427    tab.linkedBrowser
    428  );
    429  AccessibilityUtils.resetEnv();
    430 
    431  // Close the dialog by clicking on the overlay background. Simulate a click
    432  // at point (2,2) instead of (0,0) so we are sure we're clicking on the
    433  // overlay background instead of some boundary condition that a real user
    434  // would never click.
    435  info("clicking the overlay background");
    436  // We intentionally turn off this a11y check, because the following click
    437  // is purposefully targeting a non-interactive element to dismiss the opened
    438  // dialog with a mouse which can be done by assistive technology and keyboard
    439  // by pressing `Esc` key, this rule check shall be ignored by a11y_checks.
    440  AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
    441  await close_subdialog_and_test_generic_end_state(
    442    tab.linkedBrowser,
    443    function () {
    444      return BrowserTestUtils.synthesizeMouseAtPoint(
    445        2,
    446        2,
    447        {},
    448        tab.linkedBrowser
    449      );
    450    },
    451    null,
    452    0,
    453    { runClosingFnOutsideOfContentTask: true }
    454  );
    455  AccessibilityUtils.resetEnv();
    456 });
    457 
    458 add_task(async function escape_should_close_dialog() {
    459  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    460 
    461  info("canceling the dialog");
    462  await close_subdialog_and_test_generic_end_state(
    463    tab.linkedBrowser,
    464    function () {
    465      return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser);
    466    },
    467    "cancel",
    468    0,
    469    { runClosingFnOutsideOfContentTask: true }
    470  );
    471 });
    472 
    473 add_task(async function correct_width_and_height_should_be_used_for_dialog() {
    474  await open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
    475 
    476  await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    477    function fuzzyEqual(value, expectedValue, fuzz, msg) {
    478      Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
    479      Assert.lessOrEqual(expectedValue - fuzz, value, msg);
    480    }
    481    let topDialog = content.gSubDialog._topDialog;
    482    let frameStyle = content.getComputedStyle(topDialog._frame);
    483    let dialogStyle = topDialog.frameContentWindow.getComputedStyle(
    484      topDialog.frameContentWindow.document.documentElement
    485    );
    486    let fontSize = parseFloat(dialogStyle.fontSize);
    487    let height = parseFloat(frameStyle.height);
    488    let width = parseFloat(frameStyle.width);
    489 
    490    fuzzyEqual(
    491      width,
    492      fontSize * 32,
    493      2,
    494      "Width should be set on the frame from the dialog"
    495    );
    496    fuzzyEqual(
    497      height,
    498      fontSize * 5,
    499      2,
    500      "Height should be set on the frame from the dialog"
    501    );
    502  });
    503 
    504  await close_subdialog_and_test_generic_end_state(
    505    tab.linkedBrowser,
    506    function () {
    507      content.window.gSubDialog._topDialog._frame.contentWindow.close();
    508    },
    509    null,
    510    0
    511  );
    512 });
    513 
    514 add_task(
    515  async function wrapped_text_in_dialog_should_have_expected_scrollHeight() {
    516    let oldHeight = await open_subdialog_and_test_generic_start_state(
    517      tab.linkedBrowser,
    518      function domcontentloadedFn() {
    519        let frame = content.window.gSubDialog._topDialog._frame;
    520        let doc = frame.contentDocument;
    521        let scrollHeight = doc.documentElement.scrollHeight;
    522        doc.documentElement.style.removeProperty("height");
    523        doc.getElementById("desc").textContent = `
    524      Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
    525      laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
    526      architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
    527      sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
    528      laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
    529      architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
    530      sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
    531      laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
    532      architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
    533      sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
    534      voluptatem sequi nesciunt.`;
    535        return scrollHeight;
    536      }
    537    );
    538 
    539    await SpecialPowers.spawn(
    540      tab.linkedBrowser,
    541      [oldHeight],
    542      async function (contentOldHeight) {
    543        function fuzzyEqual(value, expectedValue, fuzz, msg) {
    544          Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
    545          Assert.lessOrEqual(expectedValue - fuzz, value, msg);
    546        }
    547        let topDialog = content.gSubDialog._topDialog;
    548        let frame = topDialog._frame;
    549        let frameStyle = content.getComputedStyle(frame);
    550        let docEl = frame.contentDocument.documentElement;
    551        let dialogStyle = topDialog.frameContentWindow.getComputedStyle(docEl);
    552        let fontSize = parseFloat(dialogStyle.fontSize);
    553        let height = parseFloat(frameStyle.height);
    554        let width = parseFloat(frameStyle.width);
    555 
    556        fuzzyEqual(
    557          width,
    558          32 * fontSize,
    559          2,
    560          "Width should be set on the frame from the dialog"
    561        );
    562        Assert.greater(
    563          docEl.scrollHeight,
    564          contentOldHeight,
    565          "Content height increased (from " +
    566            contentOldHeight +
    567            " to " +
    568            docEl.scrollHeight +
    569            ")."
    570        );
    571        fuzzyEqual(
    572          height,
    573          docEl.scrollHeight,
    574          2,
    575          "Height on the frame should be higher now. " +
    576            "This test may fail on certain screen resoluition. " +
    577            "See bug 1420576 and bug 1205717."
    578        );
    579      }
    580    );
    581 
    582    await close_subdialog_and_test_generic_end_state(
    583      tab.linkedBrowser,
    584      function () {
    585        content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
    586      },
    587      null,
    588      0
    589    );
    590  }
    591 );
    592 
    593 add_task(async function dialog_too_tall_should_get_reduced_in_height() {
    594  await open_subdialog_and_test_generic_start_state(
    595    tab.linkedBrowser,
    596    function domcontentloadedFn() {
    597      let frame = content.window.gSubDialog._topDialog._frame;
    598      frame.contentDocument.documentElement.style.height = "100000px";
    599    }
    600  );
    601 
    602  await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    603    function fuzzyEqual(value, expectedValue, fuzz, msg) {
    604      Assert.greaterOrEqual(expectedValue + fuzz, value, msg);
    605      Assert.lessOrEqual(expectedValue - fuzz, value, msg);
    606    }
    607    let topDialog = content.gSubDialog._topDialog;
    608    let frame = topDialog._frame;
    609    let frameStyle = content.getComputedStyle(frame);
    610    let dialogStyle = topDialog.frameContentWindow.getComputedStyle(
    611      frame.contentDocument.documentElement
    612    );
    613    let fontSize = parseFloat(dialogStyle.fontSize);
    614    let height = parseFloat(frameStyle.height);
    615    let width = parseFloat(frameStyle.width);
    616    fuzzyEqual(
    617      width,
    618      32 * fontSize,
    619      2,
    620      "Width should be set on the frame from the dialog"
    621    );
    622    Assert.less(
    623      height,
    624      content.window.innerHeight,
    625      "Height on the frame should be smaller than window's innerHeight"
    626    );
    627  });
    628 
    629  await close_subdialog_and_test_generic_end_state(
    630    tab.linkedBrowser,
    631    function () {
    632      content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
    633    },
    634    null,
    635    0
    636  );
    637 });
    638 
    639 add_task(
    640  async function scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
    641    await open_subdialog_and_test_generic_start_state(
    642      tab.linkedBrowser,
    643      function domcontentloadedFn() {
    644        let frame = content.window.gSubDialog._topDialog._frame;
    645        frame.contentDocument.documentElement.style.removeProperty("height");
    646        frame.contentDocument.documentElement.style.removeProperty("width");
    647      }
    648    );
    649 
    650    await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
    651      let frame = content.window.gSubDialog._topDialog._frame;
    652      Assert.ok(
    653        frame.style.width.endsWith("px"),
    654        "Width (" +
    655          frame.style.width +
    656          ") should be set to a px value of the scrollWidth from the dialog"
    657      );
    658      let cs = content.getComputedStyle(frame);
    659      Assert.stringMatches(
    660        cs.getPropertyValue("--subdialog-inner-height"),
    661        /px$/,
    662        "Height (" +
    663          cs.getPropertyValue("--subdialog-inner-height") +
    664          ") should be set to a px value of the scrollHeight from the dialog"
    665      );
    666    });
    667 
    668    await close_subdialog_and_test_generic_end_state(
    669      tab.linkedBrowser,
    670      function () {
    671        content.window.gSubDialog._topDialog._frame.contentWindow.window.close();
    672      },
    673      null,
    674      0
    675    );
    676  }
    677 );