1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5var EXPORTED_SYMBOLS = ["MockFilePicker"]; 6 7ChromeUtils.defineModuleGetter( 8 this, 9 "WrapPrivileged", 10 "resource://specialpowers/WrapPrivileged.jsm" 11); 12 13const Cm = Components.manager; 14 15const CONTRACT_ID = "@mozilla.org/filepicker;1"; 16 17ChromeUtils.defineModuleGetter( 18 this, 19 "FileUtils", 20 "resource://gre/modules/FileUtils.jsm" 21); 22const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 23 24/* globals __URI__ */ 25if (__URI__.includes("specialpowers")) { 26 Cu.crashIfNotInAutomation(); 27} 28 29var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); 30var oldClassID; 31var newClassID = Cc["@mozilla.org/uuid-generator;1"] 32 .getService(Ci.nsIUUIDGenerator) 33 .generateUUID(); 34var newFactory = function(window) { 35 return { 36 createInstance(aOuter, aIID) { 37 if (aOuter) { 38 throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION); 39 } 40 return new MockFilePickerInstance(window).QueryInterface(aIID); 41 }, 42 lockFactory(aLock) { 43 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 44 }, 45 QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]), 46 }; 47}; 48 49var MockFilePicker = { 50 returnOK: Ci.nsIFilePicker.returnOK, 51 returnCancel: Ci.nsIFilePicker.returnCancel, 52 returnReplace: Ci.nsIFilePicker.returnReplace, 53 54 filterAll: Ci.nsIFilePicker.filterAll, 55 filterHTML: Ci.nsIFilePicker.filterHTML, 56 filterText: Ci.nsIFilePicker.filterText, 57 filterImages: Ci.nsIFilePicker.filterImages, 58 filterXML: Ci.nsIFilePicker.filterXML, 59 filterXUL: Ci.nsIFilePicker.filterXUL, 60 filterApps: Ci.nsIFilePicker.filterApps, 61 filterAllowURLs: Ci.nsIFilePicker.filterAllowURLs, 62 filterAudio: Ci.nsIFilePicker.filterAudio, 63 filterVideo: Ci.nsIFilePicker.filterVideo, 64 65 window: null, 66 pendingPromises: [], 67 68 init(window) { 69 this.window = window; 70 71 this.reset(); 72 this.factory = newFactory(window); 73 if (!registrar.isCIDRegistered(newClassID)) { 74 oldClassID = registrar.contractIDToCID(CONTRACT_ID); 75 registrar.registerFactory(newClassID, "", CONTRACT_ID, this.factory); 76 } 77 }, 78 79 reset() { 80 this.appendFilterCallback = null; 81 this.appendFiltersCallback = null; 82 this.displayDirectory = null; 83 this.displaySpecialDirectory = ""; 84 this.filterIndex = 0; 85 this.mode = null; 86 this.returnData = []; 87 this.returnValue = null; 88 this.showCallback = null; 89 this.afterOpenCallback = null; 90 this.shown = false; 91 this.showing = false; 92 }, 93 94 cleanup() { 95 var previousFactory = this.factory; 96 this.reset(); 97 this.factory = null; 98 if (oldClassID) { 99 registrar.unregisterFactory(newClassID, previousFactory); 100 registrar.registerFactory(oldClassID, "", CONTRACT_ID, null); 101 } 102 }, 103 104 internalFileData(obj) { 105 return { 106 nsIFile: "nsIFile" in obj ? obj.nsIFile : null, 107 domFile: "domFile" in obj ? obj.domFile : null, 108 domDirectory: "domDirectory" in obj ? obj.domDirectory : null, 109 }; 110 }, 111 112 useAnyFile() { 113 var file = FileUtils.getDir("TmpD", [], false); 114 file.append("testfile"); 115 file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); 116 let promise = this.window.File.createFromNsIFile(file) 117 .then( 118 domFile => domFile, 119 () => null 120 ) 121 // domFile can be null. 122 .then(domFile => { 123 this.returnData = [this.internalFileData({ nsIFile: file, domFile })]; 124 }) 125 .then(() => file); 126 127 this.pendingPromises = [promise]; 128 129 // We return a promise in order to support some existing mochitests. 130 return promise; 131 }, 132 133 useBlobFile() { 134 var blob = new this.window.Blob([]); 135 var file = new this.window.File([blob], "helloworld.txt", { 136 type: "plain/text", 137 }); 138 this.returnData = [this.internalFileData({ domFile: file })]; 139 this.pendingPromises = []; 140 }, 141 142 useDirectory(aPath) { 143 var directory = new this.window.Directory(aPath); 144 this.returnData = [this.internalFileData({ domDirectory: directory })]; 145 this.pendingPromises = []; 146 }, 147 148 setFiles(files) { 149 this.returnData = []; 150 this.pendingPromises = []; 151 152 for (let file of files) { 153 if (file instanceof this.window.File) { 154 this.returnData.push(this.internalFileData({ domFile: file })); 155 } else { 156 let promise = this.window.File.createFromNsIFile(file, { 157 existenceCheck: false, 158 }); 159 160 promise.then(domFile => { 161 this.returnData.push( 162 this.internalFileData({ nsIFile: file, domFile }) 163 ); 164 }); 165 this.pendingPromises.push(promise); 166 } 167 } 168 }, 169 170 getNsIFile() { 171 if (this.returnData.length >= 1) { 172 return this.returnData[0].nsIFile; 173 } 174 return null; 175 }, 176}; 177 178function MockFilePickerInstance(window) { 179 this.window = window; 180 this.showCallback = null; 181 this.showCallbackWrapped = null; 182} 183MockFilePickerInstance.prototype = { 184 QueryInterface: ChromeUtils.generateQI([Ci.nsIFilePicker]), 185 init(aParent, aTitle, aMode) { 186 MockFilePicker.mode = aMode; 187 this.filterIndex = MockFilePicker.filterIndex; 188 this.parent = aParent; 189 }, 190 appendFilter(aTitle, aFilter) { 191 if (typeof MockFilePicker.appendFilterCallback == "function") { 192 MockFilePicker.appendFilterCallback(this, aTitle, aFilter); 193 } 194 }, 195 appendFilters(aFilterMask) { 196 if (typeof MockFilePicker.appendFiltersCallback == "function") { 197 MockFilePicker.appendFiltersCallback(this, aFilterMask); 198 } 199 }, 200 defaultString: "", 201 defaultExtension: "", 202 parent: null, 203 filterIndex: 0, 204 displayDirectory: null, 205 displaySpecialDirectory: "", 206 get file() { 207 if (MockFilePicker.returnData.length >= 1) { 208 return MockFilePicker.returnData[0].nsIFile; 209 } 210 211 return null; 212 }, 213 214 // We don't support directories here. 215 get domFileOrDirectory() { 216 if (MockFilePicker.returnData.length < 1) { 217 return null; 218 } 219 220 if (MockFilePicker.returnData[0].domFile) { 221 return MockFilePicker.returnData[0].domFile; 222 } 223 224 if (MockFilePicker.returnData[0].domDirectory) { 225 return MockFilePicker.returnData[0].domDirectory; 226 } 227 228 return null; 229 }, 230 get fileURL() { 231 if ( 232 MockFilePicker.returnData.length >= 1 && 233 MockFilePicker.returnData[0].nsIFile 234 ) { 235 return Services.io.newFileURI(MockFilePicker.returnData[0].nsIFile); 236 } 237 238 return null; 239 }, 240 *getFiles(asDOM) { 241 for (let d of MockFilePicker.returnData) { 242 if (asDOM) { 243 yield d.domFile || d.domDirectory; 244 } else if (d.nsIFile) { 245 yield d.nsIFile; 246 } else { 247 throw Components.Exception("", Cr.NS_ERROR_FAILURE); 248 } 249 } 250 }, 251 get files() { 252 return this.getFiles(false); 253 }, 254 get domFileOrDirectoryEnumerator() { 255 return this.getFiles(true); 256 }, 257 open(aFilePickerShownCallback) { 258 MockFilePicker.showing = true; 259 Services.tm.dispatchToMainThread(() => { 260 // Maybe all the pending promises are already resolved, but we want to be sure. 261 Promise.all(MockFilePicker.pendingPromises) 262 .then( 263 () => { 264 return Ci.nsIFilePicker.returnOK; 265 }, 266 () => { 267 return Ci.nsIFilePicker.returnCancel; 268 } 269 ) 270 .then(result => { 271 // Nothing else has to be done. 272 MockFilePicker.pendingPromises = []; 273 274 if (result == Ci.nsIFilePicker.returnCancel) { 275 return result; 276 } 277 278 MockFilePicker.displayDirectory = this.displayDirectory; 279 MockFilePicker.displaySpecialDirectory = this.displaySpecialDirectory; 280 MockFilePicker.shown = true; 281 if (typeof MockFilePicker.showCallback == "function") { 282 if (MockFilePicker.showCallback != this.showCallback) { 283 this.showCallback = MockFilePicker.showCallback; 284 if (Cu.isXrayWrapper(this.window)) { 285 this.showCallbackWrapped = WrapPrivileged.wrapCallback( 286 MockFilePicker.showCallback, 287 this.window 288 ); 289 } else { 290 this.showCallbackWrapped = this.showCallback; 291 } 292 } 293 try { 294 var returnValue = this.showCallbackWrapped(this); 295 if (typeof returnValue != "undefined") { 296 return returnValue; 297 } 298 } catch (ex) { 299 return Ci.nsIFilePicker.returnCancel; 300 } 301 } 302 303 return MockFilePicker.returnValue; 304 }) 305 .then(result => { 306 // Some additional result file can be set by the callback. Let's 307 // resolve the pending promises again. 308 return Promise.all(MockFilePicker.pendingPromises).then( 309 () => { 310 return result; 311 }, 312 () => { 313 return Ci.nsIFilePicker.returnCancel; 314 } 315 ); 316 }) 317 .then(result => { 318 MockFilePicker.pendingPromises = []; 319 320 if (aFilePickerShownCallback) { 321 aFilePickerShownCallback.done(result); 322 } 323 324 if (typeof MockFilePicker.afterOpenCallback == "function") { 325 Services.tm.dispatchToMainThread(() => { 326 MockFilePicker.afterOpenCallback(this); 327 }); 328 } 329 }); 330 }); 331 }, 332}; 333