test_webkitdirectory.html (11073B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>Test for webkitdirectory and webkitRelativePath</title> 5 <script src="/tests/SimpleTest/SimpleTest.js"></script> 6 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 7 </head> 8 9 <body> 10 <input id="inputFileWebkitDirectory" type="file" webkitdirectory></input> 11 <input id="inputFileWebkitFile" type="file"></input> 12 <input id="inputFileDirectoryChange" type="file" webkitdirectory></input> 13 14 <script type="application/javascript"> 15 16 const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( 17 "resource://gre/modules/AppConstants.sys.mjs" 18 ); 19 20 let promptHandler; 21 22 function waitForEvent(element, eventName) { 23 return new Promise(function(resolve) { 24 element.addEventListener(eventName, e => resolve(e.detail), { once: true }); 25 }); 26 } 27 28 function waitForPromptHandled() { 29 if (AppConstants.platform === "android") { 30 return new Promise(resolve => { 31 let MockPromptCollection = SpecialPowers.MockPromptCollection; 32 MockPromptCollection.showConfirmFolderUploadCallback = function() { 33 resolve(); 34 return true; 35 }; 36 }); 37 } 38 return new Promise(resolve => promptHandler.addMessageListener("promptAccepted", resolve)); 39 } 40 41 // Populate the given input type=file `aInputFile`'s `files` attribute by: 42 // - loading `script_fileList.js` in the parent process 43 // - telling it to generate the "test" template directory pattern which will 44 // create "foo.txt", "subdir/bar.txt", and if symlinks are available on the 45 // platform, "symlink.txt" which will be a symlink to "foo.txt". (Note that 46 // we explicitly expect the symlink to be filtered out if generated, and 47 // during the enhancement of the test we verified the file was created on 48 // linux by running the test before fixing the GetFilesHelper logic to filter 49 // the symlink out and verifying the subsequent `test_fileList` check failed.) 50 // - Triggering the mock file picker with the base directory of the "test" 51 // template directory. 52 // 53 // It's expected that `test_fileList` will be used after this step completes in 54 // order to validate the results. 55 function populateInputFile(aInputFile) { 56 var url = SimpleTest.getTestFileURL("script_fileList.js"); 57 var script = SpecialPowers.loadChromeScript(url); 58 59 var MockFilePicker = SpecialPowers.MockFilePicker; 60 MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); 61 62 async function onOpened(message) { 63 MockFilePicker.useDirectory(message.dir); 64 65 let input = document.getElementById(aInputFile); 66 input.setAttribute("data-name", message.name); 67 68 let promptHandled = waitForPromptHandled(); 69 let changeEvent = waitForEvent(input, "change"); 70 71 SpecialPowers.wrap(document).notifyUserGestureActivation(); 72 input.click(); 73 74 await promptHandled; 75 await changeEvent; 76 77 MockFilePicker.cleanup(); 78 script.destroy(); 79 next(); 80 } 81 82 script.addMessageListener("dir.opened", onOpened); 83 script.sendAsyncMessage("dir.open", { path: "test" }); 84 } 85 86 function checkFile(file, fileList, dirName) { 87 for (var i = 0; i < fileList.length; ++i) { 88 ok(fileList[i] instanceof File, "We want just files."); 89 if (fileList[i].name == file.name) { 90 is(fileList[i].webkitRelativePath, dirName + file.path, "Path matches"); 91 return; 92 } 93 } 94 95 ok(false, "File not found."); 96 } 97 98 // Validate the contents of the given input type=file `aInputFile`'s' `files` 99 // property against the expected list of files `aWhat`. 100 function test_fileList(aInputFile, aWhat) { 101 var input = document.getElementById(aInputFile); 102 var fileList = input.files; 103 104 if (aWhat == null) { 105 is(fileList, null, "We want a null fileList for " + aInputFile); 106 next(); 107 return; 108 } 109 110 is(fileList.length, aWhat.length, "We want just " + aWhat.length + " elements for " + aInputFile); 111 for (var i = 0; i < aWhat.length; ++i) { 112 checkFile(aWhat[i], fileList, input.dataset.name); 113 } 114 115 next(); 116 } 117 118 // Verify that we can explicitly select a symlink and it will not be filtered 119 // out. This is really a verification that GetFileHelper's file-handling logic 120 // https://searchfox.org/mozilla-central/rev/065102493dfc49234120c37fc6a334a5b1d86d9e/dom/filesystem/GetFilesHelper.cpp#81-86 121 // does not proactively take an action to filter out a selected symlink. 122 // 123 // This is a glass box test that is not entirely realistic for our actual system 124 // file pickers but does reflect what will happen in the drag-and-drop case 125 // for `HTMLInputElement::MozSetDndFilesAndDirectories` and this helps ensure 126 // that future implementation changes will behave as expected. Specifically, 127 // the presence of webkitdirectory will result in the file picker using 128 // `modeGetFolder` which will only allow selection of a directory and forbid 129 // file selection. 130 // 131 // This test explicitly does not validate HTMLInputElement's non-webkitdirectory 132 // file selection mechanism because it does not involve GetFileHelper. 133 async function test_individualSymlink(aInputFile) { 134 const input = document.getElementById(aInputFile); 135 136 // -- Create the symlink and get a `File` instance pointing at it. 137 const url = SimpleTest.getTestFileURL("script_fileList.js"); 138 const script = SpecialPowers.loadChromeScript(url); 139 140 let opened = new Promise(resolve => script.addMessageListener("symlink.opened", resolve)); 141 script.sendAsyncMessage("symlink.open", {}); 142 let { dir, file: symlinkFile } = await opened; 143 info(`symlink.open provided dir: ${dir}`) 144 145 // -- Have the picker pick it 146 var MockFilePicker = SpecialPowers.MockFilePicker; 147 MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); 148 149 MockFilePicker.displayDirectory = dir; 150 let pickerShown = new Promise(resolve => { 151 MockFilePicker.showCallback = function() { 152 // This is where we are diverging from a realistic scenario in order to get 153 // the expected coverage. 154 MockFilePicker.setFiles([symlinkFile]); 155 resolve(); 156 } 157 }); 158 MockFilePicker.returnValue = MockFilePicker.returnOK; 159 160 let changeEvent = waitForEvent(input, "change"); 161 162 SpecialPowers.wrap(document).notifyUserGestureActivation(); 163 input.click(); 164 165 await pickerShown; 166 await changeEvent; 167 168 MockFilePicker.cleanup(); 169 script.destroy(); 170 171 // -- Verify that we see the symlink. 172 let fileList = input.files; 173 is(fileList.length, 1, "There should be 1 file."); 174 is(fileList[0].name, "symlink.txt", "The file should be the symlink."); 175 next(); 176 } 177 178 function test_webkitdirectory_attribute() { 179 var a = document.createElement("input"); 180 a.setAttribute("type", "file"); 181 182 ok("webkitdirectory" in a, "HTMLInputElement.webkitdirectory exists"); 183 184 ok(!a.hasAttribute("webkitdirectory"), "No webkitdirectory DOM attribute by default"); 185 ok(!a.webkitdirectory, "No webkitdirectory attribute by default"); 186 187 a.webkitdirectory = true; 188 189 ok(a.hasAttribute("webkitdirectory"), "Webkitdirectory DOM attribute is set"); 190 ok(a.webkitdirectory, "Webkitdirectory attribute is set"); 191 192 next(); 193 } 194 195 function test_changeDataWhileWorking() { 196 var url = SimpleTest.getTestFileURL("script_fileList.js"); 197 var script = SpecialPowers.loadChromeScript(url); 198 199 var MockFilePicker = SpecialPowers.MockFilePicker; 200 MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); 201 let promptHandled; 202 203 // Let's start retrieving the root nsIFile object 204 new Promise(function(resolve) { 205 function onOpened(message) { 206 script.removeMessageListener("dir.opened", onOpened); 207 resolve(message.dir); 208 } 209 210 script.addMessageListener("dir.opened", onOpened); 211 script.sendAsyncMessage("dir.open", { path: "root" }); 212 }) 213 214 // input.click() pointing to the root dir 215 .then(async function(aDir) { 216 MockFilePicker.cleanup(); 217 MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); 218 MockFilePicker.useDirectory(aDir); 219 var input = document.getElementById("inputFileDirectoryChange"); 220 221 promptHandled = waitForPromptHandled(); 222 223 SpecialPowers.wrap(document).notifyUserGestureActivation(); 224 input.click(); 225 }) 226 227 // Before onchange, let's take the 'test' directory 228 .then(function() { 229 return new Promise(function(resolve) { 230 function onOpened(message) { 231 script.removeMessageListener("dir.opened", onOpened); 232 script.destroy(); 233 resolve(message.dir); 234 } 235 236 script.addMessageListener("dir.opened", onOpened); 237 script.sendAsyncMessage("dir.open", { path: "test" }); 238 }); 239 }) 240 241 // Now let's click again and wait for onchange. 242 .then(async function(aDir) { 243 MockFilePicker.cleanup(); 244 MockFilePicker.init(SpecialPowers.wrap(window).browsingContext); 245 MockFilePicker.useDirectory(aDir); 246 247 let input = document.getElementById("inputFileDirectoryChange"); 248 let changeEvent = waitForEvent(input, "change"); 249 250 SpecialPowers.wrap(document).notifyUserGestureActivation(); 251 input.click(); 252 253 await promptHandled; 254 await changeEvent; 255 256 MockFilePicker.cleanup(); 257 }) 258 .then(function() { 259 test_fileList("inputFileWebkitDirectory", testDirData); 260 }); 261 } 262 263 async function test_setup() { 264 if (AppConstants.platform === "android") { 265 let MockPromptCollection = SpecialPowers.MockPromptCollection; 266 MockPromptCollection.init(SpecialPowers.wrap(window).browsingContext); 267 } else { 268 let promptHandlerUrl = SimpleTest.getTestFileURL("script_promptHandler.js") 269 promptHandler = SpecialPowers.loadChromeScript(promptHandlerUrl); 270 271 let promptHandlerReady = new Promise(resolve => promptHandler.addMessageListener("initDone", resolve)); 272 promptHandler.sendAsyncMessage("init"); 273 await promptHandlerReady; 274 } 275 276 SpecialPowers.pushPrefEnv({"set": [["dom.filesystem.pathcheck.disabled", true], 277 ["dom.webkitBlink.dirPicker.enabled", true]]}, next); 278 } 279 280 async function test_cleanup() { 281 if (AppConstants.platform === "android") { 282 return; 283 } 284 let promptHandlerDone = new Promise(resolve => promptHandler.addMessageListener("cleanupDone", resolve)); 285 promptHandler.sendAsyncMessage("cleanup"); 286 await promptHandlerDone; 287 promptHandler.destroy(); 288 } 289 290 var testDirData = [ { name: "foo.txt", path: "/foo.txt" }, 291 { name: "bar.txt", path: "/subdir/bar.txt" }]; 292 293 var tests = [ 294 test_setup, 295 296 function() { populateInputFile("inputFileWebkitDirectory"); }, 297 298 function() { test_fileList("inputFileWebkitDirectory", testDirData); }, 299 300 function() { 301 // Symlinks are not available on Windows and so will not be created. 302 if (AppConstants.platform === "win" || AppConstants.platform === "android") { 303 info("Skipping individual symlink check on Windows and Android."); 304 next(); 305 return; 306 } 307 308 test_individualSymlink("inputFileWebkitFile").catch(err => ok(false, `Problem in symlink case: ${err}`)); 309 }, 310 311 test_webkitdirectory_attribute, 312 313 test_changeDataWhileWorking, 314 ]; 315 316 async function next() { 317 if (!tests.length) { 318 await test_cleanup(); 319 SimpleTest.finish(); 320 return; 321 } 322 323 var test = tests.shift(); 324 await test(); 325 } 326 327 SimpleTest.waitForExplicitFinish(); 328 next(); 329 </script> 330 </body> 331 </html>