summaryrefslogtreecommitdiffstats
path: root/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/test/browser/browser_utility_filepicker_crashed.js')
-rw-r--r--ipc/glue/test/browser/browser_utility_filepicker_crashed.js170
1 files changed, 170 insertions, 0 deletions
diff --git a/ipc/glue/test/browser/browser_utility_filepicker_crashed.js b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
new file mode 100644
index 0000000000..e8eb83cf30
--- /dev/null
+++ b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js
@@ -0,0 +1,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");
+}