tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MockFilePicker.sys.mjs (10212B)


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