summaryrefslogtreecommitdiffstats
path: root/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
blob: e8eb83cf30c86bbe6270e1f4a4688f7bd75c5ab5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

SimpleTest.requestCompleteLog();

// Wait until the child process with the given PID has indeed been terminated.
//
// Note that `checkUtilityExists`, and other functions deriving from the output
// of `ChromeUtils.requestProcInfo()`, do not suffice for this purpose! It is an
// attested failure mode that the file-dialog utility process has been removed
// from the proc-info list, but is still live with the file-picker dialog still
// displayed.
function untilChildProcessDead(pid) {
  return utilityProcessTest().untilChildProcessDead(pid);
}

async function fileDialogProcessExists() {
  return !!(await tryGetUtilityPid("windowsFileDialog"));
}

// Poll for the creation of a file dialog process.
function untilFileDialogProcessExists(options = { maxTime: 2000 }) {
  // milliseconds
  const maxTime = options.maxTime ?? 2000,
    pollTime = options.pollTime ?? 100;
  const count = maxTime / pollTime;

  return TestUtils.waitForCondition(
    () => tryGetUtilityPid("windowsFileDialog", { quiet: true }),
    "waiting for file dialog process",
    pollTime, // interval
    count // maxTries
  );
}

function openFileDialog() {
  const process = (async () => {
    await untilFileDialogProcessExists();
    let pid = await tryGetUtilityPid("windowsFileDialog");
    ok(pid, `pid should be acquired in openFileDialog::process (got ${pid})`);
    // HACK: Wait briefly for the file dialog to open.
    //
    // If this is not done, we may attempt to crash the process while it's in
    // the middle of creating and showing the file dialog window. There _should_
    // be no problem with this, but `::MiniDumpWriteDump()` occasionally fails
    // with mysterious errors (`ERROR_BAD_LENGTH`) if we crashed the process
    // while that was happening, yielding no minidump and therefore a failing
    // test.
    //
    // Use of an arbitrary timeout could presumably be avoided by setting a
    // window hook for the file dialog being shown and `await`ing on that.
    //
    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    await new Promise(res => setTimeout(res, 1000));
    return pid;
  })();

  const file = new Promise((resolve, reject) => {
    info("Opening Windows file dialog");
    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    fp.init(window, "Test: browser_utility_filepicker_crashed.js", fp.modeOpen);
    fp.open(result => {
      ok(
        result == fp.returnCancel,
        "filepicker should resolve to cancellation"
      );
      resolve();
    });
  });

  return { process, file };
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      // remote, no fallback
      ["widget.windows.utility_process_file_picker", 2],
    ],
  });
});

function makeTask(description, Describe, action) {
  let task = async function () {
    if (await fileDialogProcessExists()) {
      // If this test proceeds, it will probably cause whatever other test has a
      // file dialog open to fail.
      //
      // (We shouldn't be running two such tests in parallel on the same Fx
      // instance, but that's not obvious at this level.)
      ok(false, "another test has a file dialog open; aborting");
      return;
    }

    const { process, file } = openFileDialog();
    const pid = await process;
    const untilDead = untilChildProcessDead(pid);

    info(Describe + " the file-dialog utility process");
    await action();

    // the file-picker's callback should have been promptly cancelled
    const _before = Date.now();
    await file;
    const _after = Date.now();
    const delta = _after - _before;
    info(`file callback resolved after ${description} after ${delta}ms`);

    // depending on the test configuration, this may take some time while
    // cleanup occurs
    await untilDead;
  };

  // give this task a legible name
  Object.defineProperty(task, "name", {
    value: "testFileDialogProcess-" + Describe.replace(" ", ""),
  });

  return task;
}

for (let [description, Describe, action] of [
  ["crash", "Crash", () => crashSomeUtilityActor("windowsFileDialog")],
  [
    "being killed",
    "Kill",
    () => cleanUtilityProcessShutdown("windowsFileDialog", true),
  ],
  // Unfortunately, a controlled shutdown doesn't actually terminate the utility
  // process; the file dialog remains open. (This is expected to be resolved with
  // bug 1837008.)
  /* [
    "shutdown",
    "Shut down",
    () => cleanUtilityProcessShutdown("windowsFileDialog"),
  ] */
]) {
  add_task(makeTask(description, Describe, action));
  add_task(testCleanup);
}

async function testCleanup() {
  const killFileDialogProcess = async () => {
    if (await tryGetUtilityPid("windowsFileDialog", { quiet: true })) {
      await cleanUtilityProcessShutdown("windowsFileDialog", true);
      return true;
    }
    return false;
  };

  // If a test failure occurred, the file dialog process may or may not already
  // exist...
  if (await killFileDialogProcess()) {
    console.warn("File dialog process found and killed");
    return;
  }

  // ... and if not, may or may not be pending creation.
  info("Active file dialog process not found; waiting...");
  try {
    await untilFileDialogProcessExists({ maxTime: 1000 });
  } catch (e) {
    info("File dialog process not found during cleanup (as expected)");
    return;
  }
  await killFileDialogProcess();
  console.warn("Delayed file dialog process found and killed");
}