tor-browser

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

browser_utility_filepicker_crashed.js (6062B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 SimpleTest.requestCompleteLog();
      7 
      8 // Wait until the child process with the given PID has indeed been terminated.
      9 //
     10 // Note that `checkUtilityExists`, and other functions deriving from the output
     11 // of `ChromeUtils.requestProcInfo()`, do not suffice for this purpose! It is an
     12 // attested failure mode that the file-dialog utility process has been removed
     13 // from the proc-info list, but is still live with the file-picker dialog still
     14 // displayed.
     15 function untilChildProcessDead(pid) {
     16  return utilityProcessTest().untilChildProcessDead(pid);
     17 }
     18 
     19 async function fileDialogProcessExists() {
     20  return !!(await tryGetUtilityPid("windowsFileDialog"));
     21 }
     22 
     23 // Poll for the creation of a file dialog process.
     24 function untilFileDialogProcessExists(options = { maxTime: 2000 }) {
     25  // milliseconds
     26  const maxTime = options.maxTime ?? 2000,
     27    pollTime = options.pollTime ?? 100;
     28  const count = maxTime / pollTime;
     29 
     30  return TestUtils.waitForCondition(
     31    () => tryGetUtilityPid("windowsFileDialog", { quiet: true }),
     32    "waiting for file dialog process",
     33    pollTime, // interval
     34    count // maxTries
     35  );
     36 }
     37 
     38 function openFileDialog() {
     39  const process = (async () => {
     40    await untilFileDialogProcessExists();
     41    let pid = await tryGetUtilityPid("windowsFileDialog");
     42    ok(pid, `pid should be acquired in openFileDialog::process (got ${pid})`);
     43    // HACK: Wait briefly for the file dialog to open.
     44    //
     45    // If this is not done, we may attempt to crash the process while it's in
     46    // the middle of creating and showing the file dialog window. There _should_
     47    // be no problem with this, but `::MiniDumpWriteDump()` occasionally fails
     48    // with mysterious errors (`ERROR_BAD_LENGTH`) if we crashed the process
     49    // while that was happening, yielding no minidump and therefore a failing
     50    // test.
     51    //
     52    // Use of an arbitrary timeout could presumably be avoided by setting a
     53    // window hook for the file dialog being shown and `await`ing on that.
     54    //
     55    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
     56    await new Promise(res => setTimeout(res, 1000));
     57    return pid;
     58  })();
     59 
     60  const file = new Promise(resolve => {
     61    info("Opening Windows file dialog");
     62    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     63    fp.init(
     64      window.browsingContext,
     65      "Test: browser_utility_filepicker_crashed.js",
     66      fp.modeOpen
     67    );
     68    fp.open(result => {
     69      Assert.equal(
     70        result,
     71        fp.returnCancel,
     72        "filepicker should resolve to cancellation"
     73      );
     74      resolve();
     75    });
     76  });
     77 
     78  return { process, file };
     79 }
     80 
     81 add_setup(async function () {
     82  await SpecialPowers.pushPrefEnv({
     83    set: [
     84      // remote, no fallback
     85      ["widget.windows.utility_process_file_picker", 2],
     86    ],
     87  });
     88 });
     89 
     90 function makeTask(description, Describe, action) {
     91  let task = async function () {
     92    if (await fileDialogProcessExists()) {
     93      // If this test proceeds, it will probably cause whatever other test has a
     94      // file dialog open to fail.
     95      //
     96      // (We shouldn't be running two such tests in parallel on the same Fx
     97      // instance, but it's not obvious at this level that we're not.)
     98      ok(false, "another test has a file dialog open; aborting");
     99      return;
    100    }
    101 
    102    // the file-picker-crashed notification should be shown after the process
    103    // dies, but we must set up the listener before the notification is shown
    104    let notificationAppeared = BrowserTestUtils.waitForNotificationBar(
    105      gBrowser,
    106      undefined,
    107      "file-picker-crashed"
    108    );
    109 
    110    const { process, file } = openFileDialog();
    111    const pid = await process;
    112    const untilDead = untilChildProcessDead(pid);
    113 
    114    info(Describe + " the file-dialog utility process");
    115    await action();
    116 
    117    // the file-picker's callback should have been promptly cancelled
    118    const _before = Date.now();
    119    await file;
    120    const _after = Date.now();
    121    const delta = _after - _before;
    122    info(`file callback resolved after ${description} after ${delta}ms`);
    123 
    124    let notification = await notificationAppeared;
    125    ok(notification, "file-picker notification should appear");
    126    notification.close();
    127 
    128    // depending on the test configuration, this may take some time while
    129    // cleanup occurs
    130    await untilDead;
    131  };
    132 
    133  // give this task a legible name
    134  Object.defineProperty(task, "name", {
    135    value: "testFileDialogProcess-" + Describe.replace(" ", ""),
    136  });
    137 
    138  return task;
    139 }
    140 
    141 for (let [description, Describe, action] of [
    142  ["crash", "Crash", () => crashSomeUtilityActor("windowsFileDialog")],
    143  [
    144    "being killed",
    145    "Kill",
    146    () => cleanUtilityProcessShutdown("windowsFileDialog", true),
    147  ],
    148  // Unfortunately, a controlled shutdown doesn't actually terminate the utility
    149  // process; the file dialog remains open. (This is expected to be resolved with
    150  // bug 1837008.)
    151  /* [
    152    "shutdown",
    153    "Shut down",
    154    () => cleanUtilityProcessShutdown("windowsFileDialog"),
    155  ] */
    156 ]) {
    157  add_task(makeTask(description, Describe, action));
    158  add_task(testCleanup);
    159 }
    160 
    161 async function testCleanup() {
    162  const killFileDialogProcess = async () => {
    163    if (await tryGetUtilityPid("windowsFileDialog", { quiet: true })) {
    164      await cleanUtilityProcessShutdown("windowsFileDialog", true);
    165      return true;
    166    }
    167    return false;
    168  };
    169 
    170  // If a test failure occurred, the file dialog process may or may not already
    171  // exist...
    172  if (await killFileDialogProcess()) {
    173    console.warn("File dialog process found and killed");
    174    return;
    175  }
    176 
    177  // ... and if not, may or may not be pending creation.
    178  info("Active file dialog process not found; waiting...");
    179  try {
    180    await untilFileDialogProcessExists({ maxTime: 1000 });
    181  } catch (e) {
    182    info("File dialog process not found during cleanup (as expected)");
    183    return;
    184  }
    185  await killFileDialogProcess();
    186  console.warn("Delayed file dialog process found and killed");
    187 }