Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"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 => {
info("Opening Windows file dialog");
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(
window.browsingContext,
"Test: browser_utility_filepicker_crashed.js",
fp.modeOpen
);
fp.open(result => {
Assert.equal(
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 it's not obvious at this level that we're not.)
ok(false, "another test has a file dialog open; aborting");
return;
}
// the file-picker-crashed notification should be shown after the process
// dies, but we must set up the listener before the notification is shown
let notificationAppeared = BrowserTestUtils.waitForNotificationBar(
gBrowser,
undefined,
"file-picker-crashed"
);
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`);
let notification = await notificationAppeared;
ok(notification, "file-picker notification should appear");
notification.close();
// 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
/* [
"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");
}