/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FileUtils: "resource://gre/modules/FileUtils.sys.mjs", GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs", NetUtil: "resource://gre/modules/NetUtil.sys.mjs", }); const { debug, warn } = GeckoViewUtils.initLogging("FilePickerDelegate"); export class FilePickerDelegate { _filesInWebKitDirectory = []; /* ---------- nsIFilePicker ---------- */ init(aBrowsingContext, aTitle, aMode) { let mode; switch (aMode) { case Ci.nsIFilePicker.modeOpen: mode = "single"; break; case Ci.nsIFilePicker.modeGetFolder: mode = "folder"; break; case Ci.nsIFilePicker.modeOpenMultiple: mode = "multiple"; break; default: throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); } this._browsingContext = aBrowsingContext; this._prompt = new lazy.GeckoViewPrompter(aBrowsingContext); this._msg = { type: "file", title: aTitle, mode, }; this._mode = aMode; this._mimeTypes = []; this._capture = 0; } get mode() { return this._mode; } appendRawFilter(aFilter) { this._mimeTypes.push(aFilter); } open(aFilePickerShownCallback) { if (!this._browsingContext.canOpenModalPicker) { // File pickers are not allowed to open, so we respond to the callback // with returnCancel. aFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); return; } this._msg.mimeTypes = this._mimeTypes; this._msg.capture = this._capture; this._prompt.asyncShowPrompt(this._msg, result => { // OK: result // Cancel: !result if (!result || !result.files || !result.files.length) { aFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel); } else { this._resolveFilesInWebKitDirectory(result.filesInWebKitDirectory).then( () => { this._resolveFiles(result.files, aFilePickerShownCallback); } ); } }); } async _resolveFiles(aFiles, aCallback) { const fileData = []; try { for (const file of aFiles) { const domFileOrDir = await this._getDOMFileOrDir(file); fileData.push({ file, domFileOrDir, }); } } catch (ex) { warn`Error resolving files from file picker: ${ex}`; aCallback.done(Ci.nsIFilePicker.returnCancel); return; } this._fileData = fileData; aCallback.done(Ci.nsIFilePicker.returnOK); } async _resolveFilesInWebKitDirectory(files) { if (!files) { return; } const filesInWebKitDirectory = []; for (const info of files) { const { filePath, uri, name, type, lastModified } = info; if (filePath) { const file = (() => { if (this._prompt.domWin) { return this._prompt.domWin.File.createFromFileName(filePath, { type, lastModified, }); } return File.createFromFileName(filePath, { type, lastModified, }); })(); filesInWebKitDirectory.push(await file); continue; } // File path cannot be resolved, but we know content URI. const input = Cc[ "@mozilla.org/network/android-content-input-stream;1" ].createInstance(Ci.nsIAndroidContentInputStream); input.init(Services.io.newURI(uri)); const buffer = lazy.NetUtil.readInputStream(input); const file = (() => { if (this._prompt.domWin) { return new this._prompt.domWin.File([buffer], name, { type, lastModified, }); } return new File([buffer], name, { type, lastModified }); })(); filesInWebKitDirectory.push(file); } this._filesInWebKitDirectory = filesInWebKitDirectory; } get file() { if (!this._fileData) { throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); } const fileData = this._fileData[0]; if (!fileData) { return null; } return new lazy.FileUtils.File(fileData.file); } get fileURL() { return Services.io.newFileURI(this.file); } *_getEnumerator(aDOMFile) { if (!this._fileData) { throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); } for (const fileData of this._fileData) { if (aDOMFile) { yield fileData.domFileOrDir; } yield new lazy.FileUtils.File(fileData.file); } } get files() { return this._getEnumerator(/* aDOMFile */ false); } _getDOMFileOrDir(aPath) { if (this.mode == Ci.nsIFilePicker.modeGetFolder) { return this._getDOMDir(aPath); } return this._getDOMFile(aPath); } _getDOMDir(aPath) { if (this._prompt.domWin) { return new this._prompt.domWin.Directory(aPath); } return new Directory(aPath); } _getDOMFile(aPath) { if (this._prompt.domWin) { return this._prompt.domWin.File.createFromFileName(aPath); } return File.createFromFileName(aPath); } get domFileOrDirectory() { if (!this._fileData) { throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); } return this._fileData[0]?.domFileOrDir; } get domFileOrDirectoryEnumerator() { return this._getEnumerator(/* aDOMFile */ true); } get defaultString() { return ""; } set defaultString(aValue) {} get defaultExtension() { return ""; } set defaultExtension(aValue) {} get filterIndex() { return 0; } set filterIndex(aValue) {} get displayDirectory() { return null; } set displayDirectory(aValue) {} get displaySpecialDirectory() { return ""; } set displaySpecialDirectory(aValue) {} get addToRecentDocs() { return false; } set addToRecentDocs(aValue) {} get okButtonLabel() { return ""; } set okButtonLabel(aValue) {} get capture() { return this._capture; } set capture(aValue) { this._capture = aValue; } *_getDOMFilesInWebKitDirectory() { if ( this._mode != Ci.nsIFilePicker.modeGetFolder || AppConstants.platform != "android" ) { throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE); } for (const file of this._filesInWebKitDirectory) { yield file; } } get domFilesInWebKitDirectory() { return this._getDOMFilesInWebKitDirectory(); } } FilePickerDelegate.prototype.classID = Components.ID( "{e4565e36-f101-4bf5-950b-4be0887785a9}" ); FilePickerDelegate.prototype.QueryInterface = ChromeUtils.generateQI([ "nsIFilePicker", ]);