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 }