FilePickerDelegate.sys.mjs (7136B)
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 5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 6 import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs"; 7 8 const lazy = {}; 9 10 ChromeUtils.defineESModuleGetters(lazy, { 11 FileUtils: "resource://gre/modules/FileUtils.sys.mjs", 12 GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs", 13 NetUtil: "resource://gre/modules/NetUtil.sys.mjs", 14 }); 15 16 const { debug, warn } = GeckoViewUtils.initLogging("FilePickerDelegate"); 17 18 export class FilePickerDelegate { 19 _filesInWebKitDirectory = []; 20 21 /* ---------- nsIFilePicker ---------- */ 22 init(aBrowsingContext, aTitle, aMode) { 23 let mode; 24 switch (aMode) { 25 case Ci.nsIFilePicker.modeOpen: 26 mode = "single"; 27 break; 28 case Ci.nsIFilePicker.modeGetFolder: 29 mode = "folder"; 30 break; 31 case Ci.nsIFilePicker.modeOpenMultiple: 32 mode = "multiple"; 33 break; 34 default: 35 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 36 } 37 this._browsingContext = aBrowsingContext; 38 this._prompt = new lazy.GeckoViewPrompter(aBrowsingContext); 39 this._msg = { 40 type: "file", 41 title: aTitle, 42 mode, 43 }; 44 this._mode = aMode; 45 this._mimeTypes = []; 46 this._capture = 0; 47 } 48 49 get mode() { 50 return this._mode; 51 } 52 53 appendRawFilter(aFilter) { 54 this._mimeTypes.push(aFilter); 55 } 56 57 open(aFilePickerShownCallback) { 58 if (!this._browsingContext.canOpenModalPicker) { 59 // File pickers are not allowed to open, so we respond to the callback 60 // with returnCancel. 61 aFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); 62 return; 63 } 64 65 this._msg.mimeTypes = this._mimeTypes; 66 this._msg.capture = this._capture; 67 this._prompt.asyncShowPrompt(this._msg, result => { 68 // OK: result 69 // Cancel: !result 70 if (!result || !result.files || !result.files.length) { 71 aFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); 72 } else { 73 this._resolveFilesInWebKitDirectory(result.filesInWebKitDirectory).then( 74 () => { 75 this._resolveFiles(result.files, aFilePickerShownCallback); 76 } 77 ); 78 } 79 }); 80 } 81 82 async _resolveFiles(aFiles, aCallback) { 83 const fileData = []; 84 85 try { 86 for (const file of aFiles) { 87 const domFileOrDir = await this._getDOMFileOrDir(file); 88 fileData.push({ 89 file, 90 domFileOrDir, 91 }); 92 } 93 } catch (ex) { 94 warn`Error resolving files from file picker: ${ex}`; 95 aCallback.done(Ci.nsIFilePicker.returnCancel); 96 return; 97 } 98 99 this._fileData = fileData; 100 aCallback.done(Ci.nsIFilePicker.returnOK); 101 } 102 103 async _resolveFilesInWebKitDirectory(files) { 104 if (!files) { 105 return; 106 } 107 108 const filesInWebKitDirectory = []; 109 110 for (const info of files) { 111 const { filePath, uri, relativePath, name, type, lastModified } = info; 112 if (filePath) { 113 const file = await (() => { 114 if (this._prompt.domWin) { 115 return this._prompt.domWin.File.createFromFileName(filePath, { 116 type, 117 lastModified, 118 }); 119 } 120 return File.createFromFileName(filePath, { 121 type, 122 lastModified, 123 }); 124 })(); 125 126 file.setMozRelativePath(relativePath); 127 filesInWebKitDirectory.push(file); 128 continue; 129 } 130 131 // File path cannot be resolved, but we know content URI. 132 const input = Cc[ 133 "@mozilla.org/network/android-content-input-stream;1" 134 ].createInstance(Ci.nsIAndroidContentInputStream); 135 input.init(Services.io.newURI(uri)); 136 const buffer = lazy.NetUtil.readInputStream(input); 137 input.close(); 138 139 const file = (() => { 140 if (this._prompt.domWin) { 141 return new this._prompt.domWin.File([buffer], name, { 142 type, 143 lastModified, 144 }); 145 } 146 return new File([buffer], name, { type, lastModified }); 147 })(); 148 file.setMozRelativePath(relativePath); 149 filesInWebKitDirectory.push(file); 150 } 151 152 this._filesInWebKitDirectory = filesInWebKitDirectory; 153 } 154 155 get file() { 156 if (!this._fileData) { 157 throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); 158 } 159 const fileData = this._fileData[0]; 160 if (!fileData) { 161 return null; 162 } 163 return new lazy.FileUtils.File(fileData.file); 164 } 165 166 get fileURL() { 167 return Services.io.newFileURI(this.file); 168 } 169 170 *_getEnumerator(aDOMFile) { 171 if (!this._fileData) { 172 throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); 173 } 174 175 for (const fileData of this._fileData) { 176 if (aDOMFile) { 177 yield fileData.domFileOrDir; 178 } 179 yield new lazy.FileUtils.File(fileData.file); 180 } 181 } 182 183 get files() { 184 return this._getEnumerator(/* aDOMFile */ false); 185 } 186 187 _getDOMFileOrDir(aPath) { 188 if (this.mode == Ci.nsIFilePicker.modeGetFolder) { 189 return this._getDOMDir(aPath); 190 } 191 return this._getDOMFile(aPath); 192 } 193 194 _getDOMDir(aPath) { 195 if (this._prompt.domWin) { 196 return new this._prompt.domWin.Directory(aPath); 197 } 198 return new Directory(aPath); 199 } 200 201 _getDOMFile(aPath) { 202 if (this._prompt.domWin) { 203 return this._prompt.domWin.File.createFromFileName(aPath); 204 } 205 return File.createFromFileName(aPath); 206 } 207 208 get domFileOrDirectory() { 209 if (!this._fileData) { 210 throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); 211 } 212 return this._fileData[0]?.domFileOrDir; 213 } 214 215 get domFileOrDirectoryEnumerator() { 216 return this._getEnumerator(/* aDOMFile */ true); 217 } 218 219 get defaultString() { 220 return ""; 221 } 222 223 set defaultString(aValue) {} 224 225 get defaultExtension() { 226 return ""; 227 } 228 229 set defaultExtension(aValue) {} 230 231 get filterIndex() { 232 return 0; 233 } 234 235 set filterIndex(aValue) {} 236 237 get displayDirectory() { 238 return null; 239 } 240 241 set displayDirectory(aValue) {} 242 243 get displaySpecialDirectory() { 244 return ""; 245 } 246 247 set displaySpecialDirectory(aValue) {} 248 249 get addToRecentDocs() { 250 return false; 251 } 252 253 set addToRecentDocs(aValue) {} 254 255 get okButtonLabel() { 256 return ""; 257 } 258 259 set okButtonLabel(aValue) {} 260 261 get capture() { 262 return this._capture; 263 } 264 265 set capture(aValue) { 266 this._capture = aValue; 267 } 268 269 *_getDOMFilesInWebKitDirectory() { 270 if ( 271 this._mode != Ci.nsIFilePicker.modeGetFolder || 272 AppConstants.platform != "android" 273 ) { 274 throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); 275 } 276 277 for (const file of this._filesInWebKitDirectory) { 278 yield file; 279 } 280 } 281 282 get domFilesInWebKitDirectory() { 283 return this._getDOMFilesInWebKitDirectory(); 284 } 285 } 286 287 FilePickerDelegate.prototype.classID = Components.ID( 288 "{e4565e36-f101-4bf5-950b-4be0887785a9}" 289 ); 290 FilePickerDelegate.prototype.QueryInterface = ChromeUtils.generateQI([ 291 "nsIFilePicker", 292 ]);