toolkit/components/osfile/modules/osfile_async_front.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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 file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /**
     6  * Asynchronous front-end for OS.File.
     7  *
     8  * This front-end is meant to be imported from the main thread. In turn,
     9  * it spawns one worker (perhaps more in the future) and delegates all
    10  * disk I/O to this worker.
    11  *
    12  * Documentation note: most of the functions and methods in this module
    13  * return promises. For clarity, we denote as follows a promise that may resolve
    14  * with type |A| and some value |value| or reject with type |B| and some
    15  * reason |reason|
    16  * @resolves {A} value
    17  * @rejects {B} reason
    18  */
    20 "use strict";
    22 this.EXPORTED_SYMBOLS = ["OS"];
    24 const Cu = Components.utils;
    25 const Ci = Components.interfaces;
    27 let SharedAll = {};
    28 Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
    29 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    30 Cu.import("resource://gre/modules/Timer.jsm", this);
    32 XPCOMUtils.defineLazyModuleGetter(this, 'Deprecated',
    33   'resource://gre/modules/Deprecated.jsm');
    35 // Boilerplate, to simplify the transition to require()
    36 let LOG = SharedAll.LOG.bind(SharedAll, "Controller");
    37 let isTypedArray = SharedAll.isTypedArray;
    39 // The constructor for file errors.
    40 let SysAll = {};
    41 if (SharedAll.Constants.Win) {
    42   Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
    43 } else if (SharedAll.Constants.libc) {
    44   Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
    45 } else {
    46   throw new Error("I am neither under Windows nor under a Posix system");
    47 }
    48 let OSError = SysAll.Error;
    49 let Type = SysAll.Type;
    51 let Path = {};
    52 Cu.import("resource://gre/modules/osfile/ospath.jsm", Path);
    54 // The library of promises.
    55 Cu.import("resource://gre/modules/Promise.jsm", this);
    56 Cu.import("resource://gre/modules/Task.jsm", this);
    58 // The implementation of communications
    59 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
    61 Cu.import("resource://gre/modules/Services.jsm", this);
    62 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
    63 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
    64 let Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});
    66 /**
    67  * Constructors for decoding standard exceptions
    68  * received from the worker.
    69  */
    70 const EXCEPTION_CONSTRUCTORS = {
    71   EvalError: function(error) {
    72     return new EvalError(error.message, error.fileName, error.lineNumber);
    73   },
    74   InternalError: function(error) {
    75     return new InternalError(error.message, error.fileName, error.lineNumber);
    76   },
    77   RangeError: function(error) {
    78     return new RangeError(error.message, error.fileName, error.lineNumber);
    79   },
    80   ReferenceError: function(error) {
    81     return new ReferenceError(error.message, error.fileName, error.lineNumber);
    82   },
    83   SyntaxError: function(error) {
    84     return new SyntaxError(error.message, error.fileName, error.lineNumber);
    85   },
    86   TypeError: function(error) {
    87     return new TypeError(error.message, error.fileName, error.lineNumber);
    88   },
    89   URIError: function(error) {
    90     return new URIError(error.message, error.fileName, error.lineNumber);
    91   },
    92   OSError: function(error) {
    93     return OS.File.Error.fromMsg(error);
    94   }
    95 };
    97 // It's possible for osfile.jsm to get imported before the profile is
    98 // set up. In this case, some path constants aren't yet available.
    99 // Here, we make them lazy loaders.
   101 function lazyPathGetter(constProp, dirKey) {
   102   return function () {
   103     let path;
   104     try {
   105       path = Services.dirsvc.get(dirKey, Ci.nsIFile).path;
   106       delete SharedAll.Constants.Path[constProp];
   107       SharedAll.Constants.Path[constProp] = path;
   108     } catch (ex) {
   109       // Ignore errors if the value still isn't available. Hopefully
   110       // the next access will return it.
   111     }
   113     return path;
   114   };
   115 }
   117 for (let [constProp, dirKey] of [
   118   ["localProfileDir", "ProfLD"],
   119   ["profileDir", "ProfD"],
   120   ["userApplicationDataDir", "UAppData"],
   121   ["winAppDataDir", "AppData"],
   122   ["winStartMenuProgsDir", "Progs"],
   123   ]) {
   125   if (constProp in SharedAll.Constants.Path) {
   126     continue;
   127   }
   129   LOG("Installing lazy getter for OS.Constants.Path." + constProp +
   130       " because it isn't defined and profile may not be loaded.");
   131   Object.defineProperty(SharedAll.Constants.Path, constProp, {
   132     get: lazyPathGetter(constProp, dirKey),
   133   });
   134 }
   136 /**
   137  * Return a shallow clone of the enumerable properties of an object.
   138  */
   139 let clone = SharedAll.clone;
   141 /**
   142  * Extract a shortened version of an object, fit for logging.
   143  *
   144  * This function returns a copy of the original object in which all
   145  * long strings, Arrays, TypedArrays, ArrayBuffers are removed and
   146  * replaced with placeholders. Use this function to sanitize objects
   147  * if you wish to log them or to keep them in memory.
   148  *
   149  * @param {*} obj The obj to shorten.
   150  * @return {*} array A shorter object, fit for logging.
   151  */
   152 function summarizeObject(obj) {
   153   if (!obj) {
   154     return null;
   155   }
   156   if (typeof obj == "string") {
   157     if (obj.length > 1024) {
   158       return {"Long string": obj.length};
   159     }
   160     return obj;
   161   }
   162   if (typeof obj == "object") {
   163     if (Array.isArray(obj)) {
   164       if (obj.length > 32) {
   165         return {"Long array": obj.length};
   166       }
   167       return [summarizeObject(k) for (k of obj)];
   168     }
   169     if ("byteLength" in obj) {
   170       // Assume TypedArray or ArrayBuffer
   171       return {"Binary Data": obj.byteLength};
   172     }
   173     let result = {};
   174     for (let k of Object.keys(obj)) {
   175       result[k] = summarizeObject(obj[k]);
   176     }
   177     return result;
   178   }
   179   return obj;
   180 }
   182 let Scheduler = {
   184   /**
   185    * |true| once we have sent at least one message to the worker.
   186    * This field is unaffected by resetting the worker.
   187    */
   188   launched: false,
   190   /**
   191    * |true| once shutdown has begun i.e. we should reject any
   192    * message, including resets.
   193    */
   194   shutdown: false,
   196   /**
   197    * A promise resolved once all operations are complete.
   198    *
   199    * This promise is never rejected and the result is always undefined.
   200    */
   201   queue: Promise.resolve(),
   203   /**
   204    * Miscellaneous debugging information
   205    */
   206   Debugging: {
   207     /**
   208      * The latest message sent and still waiting for a reply.
   209      */
   210     latestSent: undefined,
   212     /**
   213      * The latest reply received, or null if we are waiting for a reply.
   214      */
   215     latestReceived: undefined,
   217     /**
   218      * Number of messages sent to the worker. This includes the
   219      * initial SET_DEBUG, if applicable.
   220      */
   221     messagesSent: 0,
   223     /**
   224      * Total number of messages ever queued, including the messages
   225      * sent.
   226      */
   227     messagesQueued: 0,
   229     /**
   230      * Number of messages received from the worker.
   231      */
   232     messagesReceived: 0,
   233   },
   235   /**
   236    * A timer used to automatically shut down the worker after some time.
   237    */
   238   resetTimer: null,
   240   /**
   241    * The worker to which to send requests.
   242    *
   243    * If the worker has never been created or has been reset, this is a
   244    * fresh worker, initialized with osfile_async_worker.js.
   245    *
   246    * @type {PromiseWorker}
   247    */
   248   get worker() {
   249     if (!this._worker) {
   250       // Either the worker has never been created or it has been reset
   251       this._worker = new PromiseWorker(
   252 	"resource://gre/modules/osfile/osfile_async_worker.js", LOG);
   253     }
   254     return this._worker;
   255   },
   257   _worker: null,
   259   /**
   260    * Prepare to kill the OS.File worker after a few seconds.
   261    */
   262   restartTimer: function(arg) {
   263     let delay;
   264     try {
   265       delay = Services.prefs.getIntPref("osfile.reset_worker_delay");
   266     } catch(e) {
   267       // Don't auto-shutdown if we don't have a delay preference set.
   268       return;
   269     }
   271     if (this.resetTimer) {
   272       clearTimeout(this.resetTimer);
   273     }
   274     this.resetTimer = setTimeout(
   275       () => Scheduler.kill({reset: true, shutdown: false}),
   276       delay
   277     );
   278   },
   280   /**
   281    * Shutdown OS.File.
   282    *
   283    * @param {*} options
   284    * - {boolean} shutdown If |true|, reject any further request. Otherwise,
   285    *   further requests will resurrect the worker.
   286    * - {boolean} reset If |true|, instruct the worker to shutdown if this
   287    *   would not cause leaks. Otherwise, assume that the worker will be shutdown
   288    *   through some other mean.
   289    */
   290   kill: function({shutdown, reset}) {
   291     return Task.spawn(function*() {
   293       yield this.queue;
   295       // Enter critical section: no yield in this block
   296       // (we want to make sure that we remain the only
   297       // request in the queue).
   299       if (!this.launched || this.shutdown || !this._worker) {
   300         // Nothing to kill
   301         this.shutdown = this.shutdown || shutdown;
   302         this._worker = null;
   303         return null;
   304       }
   306       // Deactivate the queue, to ensure that no message is sent
   307       // to an obsolete worker (we reactivate it in the |finally|).
   308       let deferred = Promise.defer();
   309       this.queue = deferred.promise;
   312       // Exit critical section
   314       let message = ["Meta_shutdown", [reset]];
   316       try {
   317         Scheduler.latestReceived = [];
   318         Scheduler.latestSent = [Date.now(), ...message];
   319         let promise = this._worker.post(...message);
   321         // Wait for result
   322         let resources;
   323         try {
   324           resources = (yield promise).ok;
   326           Scheduler.latestReceived = [Date.now(), message];
   327         } catch (ex) {
   328           LOG("Could not dispatch Meta_reset", ex);
   329           // It's most likely a programmer error, but we'll assume that
   330           // the worker has been shutdown, as it's less risky than the
   331           // opposite stance.
   332           resources = {openedFiles: [], openedDirectoryIterators: [], killed: true};
   334           Scheduler.latestReceived = [Date.now(), message, ex];
   335         }
   337         let {openedFiles, openedDirectoryIterators, killed} = resources;
   338         if (!reset
   339           && (openedFiles && openedFiles.length
   340             || ( openedDirectoryIterators && openedDirectoryIterators.length))) {
   341           // The worker still holds resources. Report them.
   343           let msg = "";
   344           if (openedFiles.length > 0) {
   345             msg += "The following files are still open:\n" +
   346               openedFiles.join("\n");
   347           }
   348           if (openedDirectoryIterators.length > 0) {
   349             msg += "The following directory iterators are still open:\n" +
   350               openedDirectoryIterators.join("\n");
   351           }
   353           LOG("WARNING: File descriptors leaks detected.\n" + msg);
   354         }
   356         // Make sure that we do not leave an invalid |worker| around.
   357         if (killed || shutdown) {
   358           this._worker = null;
   359         }
   361         this.shutdown = shutdown;
   363         return resources;
   365       } finally {
   366         // Resume accepting messages. If we have set |shutdown| to |true|,
   367         // any pending/future request will be rejected. Otherwise, any
   368         // pending/future request will spawn a new worker if necessary.
   369         deferred.resolve();
   370       }
   372     }.bind(this));
   373   },
   375   /**
   376    * Push a task at the end of the queue.
   377    *
   378    * @param {function} code A function returning a Promise.
   379    * This function will be executed once all the previously
   380    * pushed tasks have completed.
   381    * @return {Promise} A promise with the same behavior as
   382    * the promise returned by |code|.
   383    */
   384   push: function(code) {
   385     let promise = this.queue.then(code);
   386     // By definition, |this.queue| can never reject.
   387     this.queue = promise.then(null, () => undefined);
   388     // Fork |promise| to ensure that uncaught errors are reported
   389     return promise.then(null, null);
   390   },
   392   /**
   393    * Post a message to the worker thread.
   394    *
   395    * @param {string} method The name of the method to call.
   396    * @param {...} args The arguments to pass to the method. These arguments
   397    * must be clonable.
   398    * @return {Promise} A promise conveying the result/error caused by
   399    * calling |method| with arguments |args|.
   400    */
   401   post: function post(method, ...args) {
   402     if (this.shutdown) {
   403       LOG("OS.File is not available anymore. The following request has been rejected.",
   404         method, args);
   405       return Promise.reject(new Error("OS.File has been shut down. Rejecting post to " + method));
   406     }
   407     let firstLaunch = !this.launched;
   408     this.launched = true;
   410     if (firstLaunch && SharedAll.Config.DEBUG) {
   411       // If we have delayed sending SET_DEBUG, do it now.
   412       this.worker.post("SET_DEBUG", [true]);
   413       Scheduler.Debugging.messagesSent++;
   414     }
   416     // By convention, the last argument of any message may be an |options| object.
   417     let options;
   418     let methodArgs = args[0];
   419     if (methodArgs) {
   420       options = methodArgs[methodArgs.length - 1];
   421     }
   422     Scheduler.Debugging.messagesQueued++;
   423     return this.push(Task.async(function*() {
   424       if (this.shutdown) {
   425 	LOG("OS.File is not available anymore. The following request has been rejected.",
   426 	  method, args);
   427 	throw new Error("OS.File has been shut down. Rejecting request to " + method);
   428       }
   430       // Update debugging information. As |args| may be quite
   431       // expensive, we only keep a shortened version of it.
   432       Scheduler.Debugging.latestReceived = null;
   433       Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(methodArgs)];
   435       // Don't kill the worker just yet
   436       Scheduler.restartTimer();
   439       let data;
   440       let reply;
   441       let isError = false;
   442       try {
   443         try {
   444           data = yield this.worker.post(method, ...args);
   445         } finally {
   446           Scheduler.Debugging.messagesReceived++;
   447         }
   448         reply = data;
   449       } catch (error) {
   450         reply = error;
   451         isError = true;
   452         if (error instanceof PromiseWorker.WorkerError) {
   453           throw EXCEPTION_CONSTRUCTORS[error.data.exn || "OSError"](error.data);
   454         }
   455         if (error instanceof ErrorEvent) {
   456           let message = error.message;
   457           if (message == "uncaught exception: [object StopIteration]") {
   458             isError = false;
   459             throw StopIteration;
   460           }
   461           throw new Error(message, error.filename, error.lineno);
   462         }
   463         throw error;
   464       } finally {
   465         Scheduler.Debugging.latestSent = Scheduler.Debugging.latestSent.slice(0, 2);
   466         if (isError) {
   467           Scheduler.Debugging.latestReceived = [Date.now(), reply.message, reply.fileName, reply.lineNumber];
   468         } else {
   469           Scheduler.Debugging.latestReceived = [Date.now(), summarizeObject(reply)];
   470         }
   471         if (firstLaunch) {
   472           Scheduler._updateTelemetry();
   473         }
   475         Scheduler.restartTimer();
   476       }
   478       // Check for duration and return result.
   479       if (!options) {
   480         return data.ok;
   481       }
   482       // Check for options.outExecutionDuration.
   483       if (typeof options !== "object" ||
   484         !("outExecutionDuration" in options)) {
   485         return data.ok;
   486       }
   487       // If data.durationMs is not present, return data.ok (there was an
   488       // exception applying the method).
   489       if (!("durationMs" in data)) {
   490         return data.ok;
   491       }
   492       // Bug 874425 demonstrates that two successive calls to Date.now()
   493       // can actually produce an interval with negative duration.
   494       // We assume that this is due to an operation that is so short
   495       // that Date.now() is not monotonic, so we round this up to 0.
   496       let durationMs = Math.max(0, data.durationMs);
   497       // Accumulate (or initialize) outExecutionDuration
   498       if (typeof options.outExecutionDuration == "number") {
   499         options.outExecutionDuration += durationMs;
   500       } else {
   501         options.outExecutionDuration = durationMs;
   502       }
   503       return data.ok;
   504     }.bind(this)));
   505   },
   507   /**
   508    * Post Telemetry statistics.
   509    *
   510    * This is only useful on first launch.
   511    */
   512   _updateTelemetry: function() {
   513     let worker = this.worker;
   514     let workerTimeStamps = worker.workerTimeStamps;
   515     if (!workerTimeStamps) {
   516       // If the first call to OS.File results in an uncaught errors,
   517       // the timestamps are absent. As this case is a developer error,
   518       // let's not waste time attempting to extract telemetry from it.
   519       return;
   520     }
   521     let HISTOGRAM_LAUNCH = Services.telemetry.getHistogramById("OSFILE_WORKER_LAUNCH_MS");
   522     HISTOGRAM_LAUNCH.add(worker.workerTimeStamps.entered - worker.launchTimeStamp);
   524     let HISTOGRAM_READY = Services.telemetry.getHistogramById("OSFILE_WORKER_READY_MS");
   525     HISTOGRAM_READY.add(worker.workerTimeStamps.loaded - worker.launchTimeStamp);
   526   }
   527 };
   529 const PREF_OSFILE_LOG = "toolkit.osfile.log";
   530 const PREF_OSFILE_LOG_REDIRECT = "toolkit.osfile.log.redirect";
   532 /**
   533  * Safely read a PREF_OSFILE_LOG preference.
   534  * Returns a value read or, in case of an error, oldPref or false.
   535  *
   536  * @param bool oldPref
   537  *        An optional value that the DEBUG flag was set to previously.
   538  */
   539 function readDebugPref(prefName, oldPref = false) {
   540   let pref = oldPref;
   541   try {
   542     pref = Services.prefs.getBoolPref(prefName);
   543   } catch (x) {
   544     // In case of an error when reading a pref keep it as is.
   545   }
   546   // If neither pref nor oldPref were set, default it to false.
   547   return pref;
   548 };
   550 /**
   551  * Listen to PREF_OSFILE_LOG changes and update gShouldLog flag
   552  * appropriately.
   553  */
   554 Services.prefs.addObserver(PREF_OSFILE_LOG,
   555   function prefObserver(aSubject, aTopic, aData) {
   556     SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, SharedAll.Config.DEBUG);
   557     if (Scheduler.launched) {
   558       // Don't start the worker just to set this preference.
   559       Scheduler.post("SET_DEBUG", [SharedAll.Config.DEBUG]);
   560     }
   561   }, false);
   562 SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, false);
   564 Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
   565   function prefObserver(aSubject, aTopic, aData) {
   566     SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
   567   }, false);
   568 SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false);
   571 /**
   572  * If |true|, use the native implementaiton of OS.File methods
   573  * whenever possible. Otherwise, force the use of the JS version.
   574  */
   575 let nativeWheneverAvailable = true;
   576 const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
   577 Services.prefs.addObserver(PREF_OSFILE_NATIVE,
   578   function prefObserver(aSubject, aTopic, aData) {
   579     nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
   580   }, false);
   583 // Update worker's DEBUG flag if it's true.
   584 // Don't start the worker just for this, though.
   585 if (SharedAll.Config.DEBUG && Scheduler.launched) {
   586   Scheduler.post("SET_DEBUG", [true]);
   587 }
   589 // Observer topics used for monitoring shutdown
   590 const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
   592 // Preference used to configure test shutdown observer.
   593 const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
   594   "toolkit.osfile.test.shutdown.observer";
   596 AsyncShutdown.webWorkersShutdown.addBlocker(
   597   "OS.File: flush pending requests, warn about unclosed files, shut down service.",
   598   () => Scheduler.kill({reset: false, shutdown: true})
   599 );
   602 // Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or
   603 // disable the test shutdown event observer.
   604 // Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset.
   605 // Note: This is meant to be used for testing purposes only.
   606 Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
   607   function prefObserver() {
   608     // The temporary phase topic used to trigger the unclosed
   609     // phase warning.
   610     let TOPIC = null;
   611     try {
   612       TOPIC = Services.prefs.getCharPref(
   613         PREF_OSFILE_TEST_SHUTDOWN_OBSERVER);
   614     } catch (x) {
   615     }
   616     if (TOPIC) {
   617       // Generate a phase, add a blocker.
   618       // Note that this can work only if AsyncShutdown itself has been
   619       // configured for testing by the testsuite.
   620       let phase = AsyncShutdown._getPhase(TOPIC);
   621       phase.addBlocker(
   622         "(for testing purposes) OS.File: warn about unclosed files",
   623         () => Scheduler.kill({shutdown: false, reset: false})
   624       );
   625     }
   626   }, false);
   628 /**
   629  * Representation of a file, with asynchronous methods.
   630  *
   631  * @param {*} fdmsg The _message_ representing the platform-specific file
   632  * handle.
   633  *
   634  * @constructor
   635  */
   636 let File = function File(fdmsg) {
   637   // FIXME: At the moment, |File| does not close on finalize
   638   // (see bug 777715)
   639   this._fdmsg = fdmsg;
   640   this._closeResult = null;
   641   this._closed = null;
   642 };
   645 File.prototype = {
   646   /**
   647    * Close a file asynchronously.
   648    *
   649    * This method is idempotent.
   650    *
   651    * @return {promise}
   652    * @resolves {null}
   653    * @rejects {OS.File.Error}
   654    */
   655   close: function close() {
   656     if (this._fdmsg != null) {
   657       let msg = this._fdmsg;
   658       this._fdmsg = null;
   659       return this._closeResult =
   660         Scheduler.post("File_prototype_close", [msg], this);
   661     }
   662     return this._closeResult;
   663   },
   665   /**
   666    * Fetch information about the file.
   667    *
   668    * @return {promise}
   669    * @resolves {OS.File.Info} The latest information about the file.
   670    * @rejects {OS.File.Error}
   671    */
   672   stat: function stat() {
   673     return Scheduler.post("File_prototype_stat", [this._fdmsg], this).then(
   674       File.Info.fromMsg
   675     );
   676   },
   678   /**
   679    * Set the last access and modification date of the file.
   680    * The time stamp resolution is 1 second at best, but might be worse
   681    * depending on the platform.
   682    *
   683    * @return {promise}
   684    * @rejects {TypeError}
   685    * @rejects {OS.File.Error}
   686    */
   687   setDates: function setDates(accessDate, modificationDate) {
   688     return Scheduler.post("File_prototype_setDates",
   689                           [this._fdmsg, accessDate, modificationDate], this);
   690   },
   692   /**
   693    * Read a number of bytes from the file and into a buffer.
   694    *
   695    * @param {Typed array | C pointer} buffer This buffer will be
   696    * modified by another thread. Using this buffer before the |read|
   697    * operation has completed is a BAD IDEA.
   698    * @param {JSON} options
   699    *
   700    * @return {promise}
   701    * @resolves {number} The number of bytes effectively read.
   702    * @rejects {OS.File.Error}
   703    */
   704   readTo: function readTo(buffer, options = {}) {
   705     // If |buffer| is a typed array and there is no |bytes| options, we
   706     // need to extract the |byteLength| now, as it will be lost by
   707     // communication.
   708     // Options might be a nullish value, so better check for that before using
   709     // the |in| operator.
   710     if (isTypedArray(buffer) && !(options && "bytes" in options)) {
   711       // Preserve reference to option |outExecutionDuration|, if it is passed.
   712       options = clone(options, ["outExecutionDuration"]);
   713       options.bytes = buffer.byteLength;
   714     }
   715     // Note: Type.void_t.out_ptr.toMsg ensures that
   716     // - the buffer is effectively shared (not neutered) between both
   717     //   threads;
   718     // - we take care of any |byteOffset|.
   719     return Scheduler.post("File_prototype_readTo",
   720       [this._fdmsg,
   721        Type.void_t.out_ptr.toMsg(buffer),
   722        options],
   723        buffer/*Ensure that |buffer| is not gc-ed*/);
   724   },
   725   /**
   726    * Write bytes from a buffer to this file.
   727    *
   728    * Note that, by default, this function may perform several I/O
   729    * operations to ensure that the buffer is fully written.
   730    *
   731    * @param {Typed array | C pointer} buffer The buffer in which the
   732    * the bytes are stored. The buffer must be large enough to
   733    * accomodate |bytes| bytes. Using the buffer before the operation
   734    * is complete is a BAD IDEA.
   735    * @param {*=} options Optionally, an object that may contain the
   736    * following fields:
   737    * - {number} bytes The number of |bytes| to write from the buffer. If
   738    * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
   739    * if |buffer| is a C pointer.
   740    *
   741    * @return {number} The number of bytes actually written.
   742    */
   743   write: function write(buffer, options = {}) {
   744     // If |buffer| is a typed array and there is no |bytes| options,
   745     // we need to extract the |byteLength| now, as it will be lost
   746     // by communication.
   747     // Options might be a nullish value, so better check for that before using
   748     // the |in| operator.
   749     if (isTypedArray(buffer) && !(options && "bytes" in options)) {
   750       // Preserve reference to option |outExecutionDuration|, if it is passed.
   751       options = clone(options, ["outExecutionDuration"]);
   752       options.bytes = buffer.byteLength;
   753     }
   754     // Note: Type.void_t.out_ptr.toMsg ensures that
   755     // - the buffer is effectively shared (not neutered) between both
   756     //   threads;
   757     // - we take care of any |byteOffset|.
   758     return Scheduler.post("File_prototype_write",
   759       [this._fdmsg,
   760        Type.void_t.in_ptr.toMsg(buffer),
   761        options],
   762        buffer/*Ensure that |buffer| is not gc-ed*/);
   763   },
   765   /**
   766    * Read bytes from this file to a new buffer.
   767    *
   768    * @param {number=} bytes If unspecified, read all the remaining bytes from
   769    * this file. If specified, read |bytes| bytes, or less if the file does not
   770    * contain that many bytes.
   771    * @param {JSON} options
   772    * @return {promise}
   773    * @resolves {Uint8Array} An array containing the bytes read.
   774    */
   775   read: function read(nbytes, options = {}) {
   776     let promise = Scheduler.post("File_prototype_read",
   777       [this._fdmsg,
   778        nbytes, options]);
   779     return promise.then(
   780       function onSuccess(data) {
   781         return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
   782       });
   783   },
   785   /**
   786    * Return the current position in the file, as bytes.
   787    *
   788    * @return {promise}
   789    * @resolves {number} The current position in the file,
   790    * as a number of bytes since the start of the file.
   791    */
   792   getPosition: function getPosition() {
   793     return Scheduler.post("File_prototype_getPosition",
   794       [this._fdmsg]);
   795   },
   797   /**
   798    * Set the current position in the file, as bytes.
   799    *
   800    * @param {number} pos A number of bytes.
   801    * @param {number} whence The reference position in the file,
   802    * which may be either POS_START (from the start of the file),
   803    * POS_END (from the end of the file) or POS_CUR (from the
   804    * current position in the file).
   805    *
   806    * @return {promise}
   807    */
   808   setPosition: function setPosition(pos, whence) {
   809     return Scheduler.post("File_prototype_setPosition",
   810       [this._fdmsg, pos, whence]);
   811   },
   813   /**
   814    * Flushes the file's buffers and causes all buffered data
   815    * to be written.
   816    * Disk flushes are very expensive and therefore should be used carefully,
   817    * sparingly and only in scenarios where it is vital that data survives
   818    * system crashes. Even though the function will be executed off the
   819    * main-thread, it might still affect the overall performance of any running
   820    * application.
   821    *
   822    * @return {promise}
   823    */
   824   flush: function flush() {
   825     return Scheduler.post("File_prototype_flush",
   826       [this._fdmsg]);
   827   },
   829   /**
   830    * Set the file's access permissions.  Without any options, the
   831    * permissions are set to an approximation of what they would have
   832    * been if the file had been created in its current directory in the
   833    * "most typical" fashion for the operating system.  In the current
   834    * implementation, this means that on Unix-like systems (including
   835    * Android, B2G, etc) we set the POSIX file mode to (0666 & ~umask),
   836    * and on Windows, we do nothing.
   837    *
   838    * This operation is likely to fail if applied to a file that was
   839    * not created by the currently running program (more precisely,
   840    * if it was created by a program running under a different OS-level
   841    * user account).  It may also fail, or silently do nothing, if the
   842    * filesystem containing the file does not support access permissions.
   843    *
   844    * @param {*=} options
   845    * - {number} unixMode     If present, the POSIX file mode is set to exactly
   846    *                         this value, unless |unixHonorUmask| is also
   847    *                         present.
   848    * - {bool} unixHonorUmask If true, any |unixMode| value is modified by the
   849    *                         process umask, as open() would have done.
   850    */
   851   setPermissions: function setPermissions(options = {}) {
   852     return Scheduler.post("File_prototype_setPermissions",
   853                           [this._fdmsg, options]);
   854   }
   855 };
   857 /**
   858  * Open a file asynchronously.
   859  *
   860  * @return {promise}
   861  * @resolves {OS.File}
   862  * @rejects {OS.Error}
   863  */
   864 File.open = function open(path, mode, options) {
   865   return Scheduler.post(
   866     "open", [Type.path.toMsg(path), mode, options],
   867     path
   868   ).then(
   869     function onSuccess(msg) {
   870       return new File(msg);
   871     }
   872   );
   873 };
   875 /**
   876  * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
   877  *
   878  * @param {string} path The path to the file.
   879  * @param {*=} options Additional options for file opening. This
   880  * implementation interprets the following fields:
   881  *
   882  * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
   883  *  If |false| use HEX numbers ie: filename-A65BC0.ext
   884  * - {number} maxReadableNumber Used to limit the amount of tries after a failed
   885  *  file creation. Default is 20.
   886  *
   887  * @return {Object} contains A file object{file} and the path{path}.
   888  * @throws {OS.File.Error} If the file could not be opened.
   889  */
   890 File.openUnique = function openUnique(path, options) {
   891   return Scheduler.post(
   892       "openUnique", [Type.path.toMsg(path), options],
   893       path
   894     ).then(
   895     function onSuccess(msg) {
   896       return {
   897         path: msg.path,
   898         file: new File(msg.file)
   899       };
   900     }
   901   );
   902 };
   904 /**
   905  * Get the information on the file.
   906  *
   907  * @return {promise}
   908  * @resolves {OS.File.Info}
   909  * @rejects {OS.Error}
   910  */
   911 File.stat = function stat(path, options) {
   912   return Scheduler.post(
   913     "stat", [Type.path.toMsg(path), options],
   914     path).then(File.Info.fromMsg);
   915 };
   918 /**
   919  * Set the last access and modification date of the file.
   920  * The time stamp resolution is 1 second at best, but might be worse
   921  * depending on the platform.
   922  *
   923  * @return {promise}
   924  * @rejects {TypeError}
   925  * @rejects {OS.File.Error}
   926  */
   927 File.setDates = function setDates(path, accessDate, modificationDate) {
   928   return Scheduler.post("setDates",
   929                         [Type.path.toMsg(path), accessDate, modificationDate],
   930                         this);
   931 };
   933 /**
   934  * Set the file's access permissions.  Without any options, the
   935  * permissions are set to an approximation of what they would have
   936  * been if the file had been created in its current directory in the
   937  * "most typical" fashion for the operating system.  In the current
   938  * implementation, this means that on Unix-like systems (including
   939  * Android, B2G, etc) we set the POSIX file mode to (0666 & ~umask),
   940  * and on Windows, we do nothing.
   941  *
   942  * This operation is likely to fail if applied to a file that was
   943  * not created by the currently running program (more precisely,
   944  * if it was created by a program running under a different OS-level
   945  * user account).  It may also fail, or silently do nothing, if the
   946  * filesystem containing the file does not support access permissions.
   947  *
   948  * @param {string} path   The path to the file.
   949  *
   950  * @param {*=} options
   951  * - {number} unixMode     If present, the POSIX file mode is set to exactly
   952  *                         this value, unless |unixHonorUmask| is also
   953  *                         present.
   954  * - {bool} unixHonorUmask If true, any |unixMode| value is modified by the
   955  *                         process umask, as open() would have done.
   956  */
   957 File.setPermissions = function setPermissions(path, options = {}) {
   958   return Scheduler.post("setPermissions",
   959                         [Type.path.toMsg(path), options]);
   960 };
   962 /**
   963  * Fetch the current directory
   964  *
   965  * @return {promise}
   966  * @resolves {string} The current directory, as a path usable with OS.Path
   967  * @rejects {OS.Error}
   968  */
   969 File.getCurrentDirectory = function getCurrentDirectory() {
   970   return Scheduler.post(
   971     "getCurrentDirectory"
   972   ).then(Type.path.fromMsg);
   973 };
   975 /**
   976  * Change the current directory
   977  *
   978  * @param {string} path The OS-specific path to the current directory.
   979  * You should use the methods of OS.Path and the constants of OS.Constants.Path
   980  * to build OS-specific paths in a portable manner.
   981  *
   982  * @return {promise}
   983  * @resolves {null}
   984  * @rejects {OS.Error}
   985  */
   986 File.setCurrentDirectory = function setCurrentDirectory(path) {
   987   return Scheduler.post(
   988     "setCurrentDirectory", [Type.path.toMsg(path)], path
   989   );
   990 };
   992 /**
   993  * Copy a file to a destination.
   994  *
   995  * @param {string} sourcePath The platform-specific path at which
   996  * the file may currently be found.
   997  * @param {string} destPath The platform-specific path at which the
   998  * file should be copied.
   999  * @param {*=} options An object which may contain the following fields:
  1001  * @option {bool} noOverwrite - If true, this function will fail if
  1002  * a file already exists at |destPath|. Otherwise, if this file exists,
  1003  * it will be erased silently.
  1005  * @rejects {OS.File.Error} In case of any error.
  1007  * General note: The behavior of this function is defined only when
  1008  * it is called on a single file. If it is called on a directory, the
  1009  * behavior is undefined and may not be the same across all platforms.
  1011  * General note: The behavior of this function with respect to metadata
  1012  * is unspecified. Metadata may or may not be copied with the file. The
  1013  * behavior may not be the same across all platforms.
  1014 */
  1015 File.copy = function copy(sourcePath, destPath, options) {
  1016   return Scheduler.post("copy", [Type.path.toMsg(sourcePath),
  1017     Type.path.toMsg(destPath), options], [sourcePath, destPath]);
  1018 };
  1020 /**
  1021  * Move a file to a destination.
  1023  * @param {string} sourcePath The platform-specific path at which
  1024  * the file may currently be found.
  1025  * @param {string} destPath The platform-specific path at which the
  1026  * file should be moved.
  1027  * @param {*=} options An object which may contain the following fields:
  1029  * @option {bool} noOverwrite - If set, this function will fail if
  1030  * a file already exists at |destPath|. Otherwise, if this file exists,
  1031  * it will be erased silently.
  1033  * @returns {Promise}
  1034  * @rejects {OS.File.Error} In case of any error.
  1036  * General note: The behavior of this function is defined only when
  1037  * it is called on a single file. If it is called on a directory, the
  1038  * behavior is undefined and may not be the same across all platforms.
  1040  * General note: The behavior of this function with respect to metadata
  1041  * is unspecified. Metadata may or may not be moved with the file. The
  1042  * behavior may not be the same across all platforms.
  1043  */
  1044 File.move = function move(sourcePath, destPath, options) {
  1045   return Scheduler.post("move", [Type.path.toMsg(sourcePath),
  1046     Type.path.toMsg(destPath), options], [sourcePath, destPath]);
  1047 };
  1049 /**
  1050  * Create a symbolic link to a source.
  1052  * @param {string} sourcePath The platform-specific path to which
  1053  * the symbolic link should point.
  1054  * @param {string} destPath The platform-specific path at which the
  1055  * symbolic link should be created.
  1057  * @returns {Promise}
  1058  * @rejects {OS.File.Error} In case of any error.
  1059  */
  1060 if (!SharedAll.Constants.Win) {
  1061   File.unixSymLink = function unixSymLink(sourcePath, destPath) {
  1062     return Scheduler.post("unixSymLink", [Type.path.toMsg(sourcePath),
  1063       Type.path.toMsg(destPath)], [sourcePath, destPath]);
  1064   };
  1067 /**
  1068  * Gets the number of bytes available on disk to the current user.
  1070  * @param {string} Platform-specific path to a directory on the disk to 
  1071  * query for free available bytes.
  1073  * @return {number} The number of bytes available for the current user.
  1074  * @throws {OS.File.Error} In case of any error.
  1075  */
  1076 File.getAvailableFreeSpace = function getAvailableFreeSpace(sourcePath) {
  1077   return Scheduler.post("getAvailableFreeSpace",
  1078     [Type.path.toMsg(sourcePath)], sourcePath
  1079   ).then(Type.uint64_t.fromMsg);
  1080 };
  1082 /**
  1083  * Remove an empty directory.
  1085  * @param {string} path The name of the directory to remove.
  1086  * @param {*=} options Additional options.
  1087  *   - {bool} ignoreAbsent If |true|, do not fail if the
  1088  *     directory does not exist yet.
  1089  */
  1090 File.removeEmptyDir = function removeEmptyDir(path, options) {
  1091   return Scheduler.post("removeEmptyDir",
  1092     [Type.path.toMsg(path), options], path);
  1093 };
  1095 /**
  1096  * Remove an existing file.
  1098  * @param {string} path The name of the file.
  1099  */
  1100 File.remove = function remove(path) {
  1101   return Scheduler.post("remove",
  1102     [Type.path.toMsg(path)]);
  1103 };
  1107 /**
  1108  * Create a directory and, optionally, its parent directories.
  1110  * @param {string} path The name of the directory.
  1111  * @param {*=} options Additional options.
  1113  * - {string} from If specified, the call to |makeDir| creates all the
  1114  * ancestors of |path| that are descendants of |from|. Note that |path|
  1115  * must be a descendant of |from|, and that |from| and its existing
  1116  * subdirectories present in |path|  must be user-writeable.
  1117  * Example:
  1118  *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
  1119  *  creates directories profileDir/foo, profileDir/foo/bar
  1120  * - {bool} ignoreExisting If |false|, throw an error if the directory
  1121  * already exists. |true| by default. Ignored if |from| is specified.
  1122  * - {number} unixMode Under Unix, if specified, a file creation mode,
  1123  * as per libc function |mkdir|. If unspecified, dirs are
  1124  * created with a default mode of 0700 (dir is private to
  1125  * the user, the user can read, write and execute). Ignored under Windows
  1126  * or if the file system does not support file creation modes.
  1127  * - {C pointer} winSecurity Under Windows, if specified, security
  1128  * attributes as per winapi function |CreateDirectory|. If
  1129  * unspecified, use the default security descriptor, inherited from
  1130  * the parent directory. Ignored under Unix or if the file system
  1131  * does not support security descriptors.
  1132  */
  1133 File.makeDir = function makeDir(path, options) {
  1134   return Scheduler.post("makeDir",
  1135     [Type.path.toMsg(path), options], path);
  1136 };
  1138 /**
  1139  * Return the contents of a file
  1141  * @param {string} path The path to the file.
  1142  * @param {number=} bytes Optionally, an upper bound to the number of bytes
  1143  * to read. DEPRECATED - please use options.bytes instead.
  1144  * @param {JSON} options Additional options.
  1145  * - {boolean} sequential A flag that triggers a population of the page cache
  1146  * with data from a file so that subsequent reads from that file would not
  1147  * block on disk I/O. If |true| or unspecified, inform the system that the
  1148  * contents of the file will be read in order. Otherwise, make no such
  1149  * assumption. |true| by default.
  1150  * - {number} bytes An upper bound to the number of bytes to read.
  1151  * - {string} compression If "lz4" and if the file is compressed using the lz4
  1152  * compression algorithm, decompress the file contents on the fly.
  1154  * @resolves {Uint8Array} A buffer holding the bytes
  1155  * read from the file.
  1156  */
  1157 File.read = function read(path, bytes, options = {}) {
  1158   if (typeof bytes == "object") {
  1159     // Passing |bytes| as an argument is deprecated.
  1160     // We should now be passing it as a field of |options|.
  1161     options = bytes || {};
  1162   } else {
  1163     options = clone(options, ["outExecutionDuration"]);
  1164     if (typeof bytes != "undefined") {
  1165       options.bytes = bytes;
  1169   if (options.compression || !nativeWheneverAvailable) {
  1170     // We need to use the JS implementation.
  1171     let promise = Scheduler.post("read",
  1172       [Type.path.toMsg(path), bytes, options], path);
  1173     return promise.then(
  1174       function onSuccess(data) {
  1175         if (typeof data == "string") {
  1176           return data;
  1178         return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
  1179       });
  1182   // Otherwise, use the native implementation.
  1183   return Scheduler.push(() => Native.read(path, options));
  1184 };
  1186 /**
  1187  * Find outs if a file exists.
  1189  * @param {string} path The path to the file.
  1191  * @return {bool} true if the file exists, false otherwise.
  1192  */
  1193 File.exists = function exists(path) {
  1194   return Scheduler.post("exists",
  1195     [Type.path.toMsg(path)], path);
  1196 };
  1198 /**
  1199  * Write a file, atomically.
  1201  * By opposition to a regular |write|, this operation ensures that,
  1202  * until the contents are fully written, the destination file is
  1203  * not modified.
  1205  * Limitation: In a few extreme cases (hardware failure during the
  1206  * write, user unplugging disk during the write, etc.), data may be
  1207  * corrupted. If your data is user-critical (e.g. preferences,
  1208  * application data, etc.), you may wish to consider adding options
  1209  * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
  1210  * detailed below. Note that no combination of options can be
  1211  * guaranteed to totally eliminate the risk of corruption.
  1213  * @param {string} path The path of the file to modify.
  1214  * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
  1215  * @param {*=} options Optionally, an object determining the behavior
  1216  * of this function. This object may contain the following fields:
  1217  * - {number} bytes The number of bytes to write. If unspecified,
  1218  * |buffer.byteLength|. Required if |buffer| is a C pointer.
  1219  * - {string} tmpPath If |null| or unspecified, write all data directly
  1220  * to |path|. If specified, write all data to a temporary file called
  1221  * |tmpPath| and, once this write is complete, rename the file to
  1222  * replace |path|. Performing this additional operation is a little
  1223  * slower but also a little safer.
  1224  * - {bool} noOverwrite - If set, this function will fail if a file already
  1225  * exists at |path|.
  1226  * - {bool} flush - If |false| or unspecified, return immediately once the
  1227  * write is complete. If |true|, before writing, force the operating system
  1228  * to write its internal disk buffers to the disk. This is considerably slower
  1229  * (not just for the application but for the whole system) but also safer:
  1230  * if the system shuts down improperly (typically due to a kernel freeze
  1231  * or a power failure) or if the device is disconnected before the buffer
  1232  * is flushed, the file has more chances of not being corrupted.
  1233  * - {string} backupTo - If specified, backup the destination file as |backupTo|.
  1234  * Note that this function renames the destination file before overwriting it.
  1235  * If the process or the operating system freezes or crashes
  1236  * during the short window between these operations,
  1237  * the destination file will have been moved to its backup.
  1239  * @return {promise}
  1240  * @resolves {number} The number of bytes actually written.
  1241  */
  1242 File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
  1243   // Copy |options| to avoid modifying the original object but preserve the
  1244   // reference to |outExecutionDuration| option if it is passed.
  1245   options = clone(options, ["outExecutionDuration"]);
  1246   // As options.tmpPath is a path, we need to encode it as |Type.path| message
  1247   if ("tmpPath" in options) {
  1248     options.tmpPath = Type.path.toMsg(options.tmpPath);
  1249   };
  1250   if (isTypedArray(buffer) && (!("bytes" in options))) {
  1251     options.bytes = buffer.byteLength;
  1252   };
  1253   // Note: Type.void_t.out_ptr.toMsg ensures that
  1254   // - the buffer is effectively shared (not neutered) between both
  1255   //   threads;
  1256   // - we take care of any |byteOffset|.
  1257   let refObj = {};
  1258   TelemetryStopwatch.start("OSFILE_WRITEATOMIC_JANK_MS", refObj);
  1259   let promise = Scheduler.post("writeAtomic",
  1260     [Type.path.toMsg(path),
  1261      Type.void_t.in_ptr.toMsg(buffer),
  1262      options], [options, buffer]);
  1263   TelemetryStopwatch.finish("OSFILE_WRITEATOMIC_JANK_MS", refObj);
  1264   return promise;
  1265 };
  1267 File.removeDir = function(path, options = {}) {
  1268   return Scheduler.post("removeDir",
  1269     [Type.path.toMsg(path), options], path);
  1270 };
  1272 /**
  1273  * Information on a file, as returned by OS.File.stat or
  1274  * OS.File.prototype.stat
  1276  * @constructor
  1277  */
  1278 File.Info = function Info(value) {
  1279   // Note that we can't just do this[k] = value[k] because our
  1280   // prototype defines getters for all of these fields.
  1281   for (let k in value) {
  1282     if (k != "creationDate") {
  1283       Object.defineProperty(this, k, {value: value[k]});
  1286   Object.defineProperty(this, "_deprecatedCreationDate", {value: value["creationDate"]});
  1287 };
  1288 File.Info.prototype = SysAll.AbstractInfo.prototype;
  1290 // Deprecated
  1291 Object.defineProperty(File.Info.prototype, "creationDate", {
  1292   get: function creationDate() {
  1293     Deprecated.warning("Field 'creationDate' is deprecated.", "https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File.Info#Cross-platform_Attributes");
  1294     return this._deprecatedCreationDate;
  1296 });
  1298 File.Info.fromMsg = function fromMsg(value) {
  1299   return new File.Info(value);
  1300 };
  1302 /**
  1303  * Get worker's current DEBUG flag.
  1304  * Note: This is used for testing purposes.
  1305  */
  1306 File.GET_DEBUG = function GET_DEBUG() {
  1307   return Scheduler.post("GET_DEBUG");
  1308 };
  1310 /**
  1311  * Iterate asynchronously through a directory
  1313  * @constructor
  1314  */
  1315 let DirectoryIterator = function DirectoryIterator(path, options) {
  1316   /**
  1317    * Open the iterator on the worker thread
  1319    * @type {Promise}
  1320    * @resolves {*} A message accepted by the methods of DirectoryIterator
  1321    * in the worker thread
  1322    * @rejects {StopIteration} If all entries have already been visited
  1323    * or the iterator has been closed.
  1324    */
  1325   this.__itmsg = Scheduler.post(
  1326     "new_DirectoryIterator", [Type.path.toMsg(path), options],
  1327     path
  1328   );
  1329   this._isClosed = false;
  1330 };
  1331 DirectoryIterator.prototype = {
  1332   iterator: function () this,
  1333   __iterator__: function () this,
  1335   // Once close() is called, _itmsg should reject with a
  1336   // StopIteration. However, we don't want to create the promise until
  1337   // it's needed because it might never be used. In that case, we
  1338   // would get a warning on the console.
  1339   get _itmsg() {
  1340     if (!this.__itmsg) {
  1341       this.__itmsg = Promise.reject(StopIteration);
  1343     return this.__itmsg;
  1344   },
  1346   /**
  1347    * Determine whether the directory exists.
  1349    * @resolves {boolean}
  1350    */
  1351   exists: function exists() {
  1352     return this._itmsg.then(
  1353       function onSuccess(iterator) {
  1354         return Scheduler.post("DirectoryIterator_prototype_exists", [iterator]);
  1356     );
  1357   },
  1358   /**
  1359    * Get the next entry in the directory.
  1361    * @return {Promise}
  1362    * @resolves {OS.File.Entry}
  1363    * @rejects {StopIteration} If all entries have already been visited.
  1364    */
  1365   next: function next() {
  1366     let self = this;
  1367     let promise = this._itmsg;
  1369     // Get the iterator, call _next
  1370     promise = promise.then(
  1371       function withIterator(iterator) {
  1372         return self._next(iterator);
  1373       });
  1375     return promise;
  1376   },
  1377   /**
  1378    * Get several entries at once.
  1380    * @param {number=} length If specified, the number of entries
  1381    * to return. If unspecified, return all remaining entries.
  1382    * @return {Promise}
  1383    * @resolves {Array} An array containing the |length| next entries.
  1384    */
  1385   nextBatch: function nextBatch(size) {
  1386     if (this._isClosed) {
  1387       return Promise.resolve([]);
  1389     let promise = this._itmsg;
  1390     promise = promise.then(
  1391       function withIterator(iterator) {
  1392         return Scheduler.post("DirectoryIterator_prototype_nextBatch", [iterator, size]);
  1393       });
  1394     promise = promise.then(
  1395       function withEntries(array) {
  1396         return array.map(DirectoryIterator.Entry.fromMsg);
  1397       });
  1398     return promise;
  1399   },
  1400   /**
  1401    * Apply a function to all elements of the directory sequentially.
  1403    * @param {Function} cb This function will be applied to all entries
  1404    * of the directory. It receives as arguments
  1405    *  - the OS.File.Entry corresponding to the entry;
  1406    *  - the index of the entry in the enumeration;
  1407    *  - the iterator itself - return |iterator.close()| to stop the loop.
  1409    * If the callback returns a promise, iteration waits until the
  1410    * promise is resolved before proceeding.
  1412    * @return {Promise} A promise resolved once the loop has reached
  1413    * its end.
  1414    */
  1415   forEach: function forEach(cb, options) {
  1416     if (this._isClosed) {
  1417       return Promise.resolve();
  1420     let self = this;
  1421     let position = 0;
  1422     let iterator;
  1424     // Grab iterator
  1425     let promise = this._itmsg.then(
  1426       function(aIterator) {
  1427         iterator = aIterator;
  1429     );
  1431     // Then iterate
  1432     let loop = function loop() {
  1433       if (self._isClosed) {
  1434         return Promise.resolve();
  1436       return self._next(iterator).then(
  1437         function onSuccess(value) {
  1438           return Promise.resolve(cb(value, position++, self)).then(loop);
  1439         },
  1440         function onFailure(reason) {
  1441           if (reason == StopIteration) {
  1442             return;
  1444           throw reason;
  1446       );
  1447     };
  1449     return promise.then(loop);
  1450   },
  1451   /**
  1452    * Auxiliary method: fetch the next item
  1454    * @rejects {StopIteration} If all entries have already been visited
  1455    * or the iterator has been closed.
  1456    */
  1457   _next: function _next(iterator) {
  1458     if (this._isClosed) {
  1459       return this._itmsg;
  1461     let self = this;
  1462     let promise = Scheduler.post("DirectoryIterator_prototype_next", [iterator]);
  1463     promise = promise.then(
  1464       DirectoryIterator.Entry.fromMsg,
  1465       function onReject(reason) {
  1466         if (reason == StopIteration) {
  1467           self.close();
  1468           throw StopIteration;
  1470         throw reason;
  1471       });
  1472     return promise;
  1473   },
  1474   /**
  1475    * Close the iterator
  1476    */
  1477   close: function close() {
  1478     if (this._isClosed) {
  1479       return Promise.resolve();
  1481     this._isClosed = true;
  1482     let self = this;
  1483     return this._itmsg.then(
  1484       function withIterator(iterator) {
  1485         // Set __itmsg to null so that the _itmsg getter returns a
  1486         // rejected StopIteration promise if it's ever used.
  1487         self.__itmsg = null;
  1488         return Scheduler.post("DirectoryIterator_prototype_close", [iterator]);
  1490     );
  1492 };
  1494 DirectoryIterator.Entry = function Entry(value) {
  1495   return value;
  1496 };
  1497 DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
  1499 DirectoryIterator.Entry.fromMsg = function fromMsg(value) {
  1500   return new DirectoryIterator.Entry(value);
  1501 };
  1503 File.resetWorker = function() {
  1504   return Task.spawn(function*() {
  1505     let resources = yield Scheduler.kill({shutdown: false, reset: true});
  1506     if (resources && !resources.killed) {
  1507         throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
  1509   });
  1510 };
  1512 // Constants
  1513 File.POS_START = SysAll.POS_START;
  1514 File.POS_CURRENT = SysAll.POS_CURRENT;
  1515 File.POS_END = SysAll.POS_END;
  1517 // Exports
  1518 File.Error = OSError;
  1519 File.DirectoryIterator = DirectoryIterator;
  1521 this.OS = {};
  1522 this.OS.File = File;
  1523 this.OS.Constants = SharedAll.Constants;
  1524 this.OS.Shared = {
  1525   LOG: SharedAll.LOG,
  1526   Type: SysAll.Type,
  1527   get DEBUG() {
  1528     return SharedAll.Config.DEBUG;
  1529   },
  1530   set DEBUG(x) {
  1531     return SharedAll.Config.DEBUG = x;
  1533 };
  1534 Object.freeze(this.OS.Shared);
  1535 this.OS.Path = Path;
  1537 // Returns a resolved promise when all the queued operation have been completed.
  1538 Object.defineProperty(OS.File, "queue", {
  1539   get: function() {
  1540     return Scheduler.queue;
  1542 });
  1544 // Auto-flush OS.File during profile-before-change. This ensures that any I/O
  1545 // that has been queued *before* profile-before-change is properly completed.
  1546 // To ensure that I/O queued *during* profile-before-change is completed,
  1547 // clients should register using AsyncShutdown.addBlocker.
  1548 AsyncShutdown.profileBeforeChange.addBlocker(
  1549   "OS.File: flush I/O queued before profile-before-change",
  1550   // Wait until the latest currently enqueued promise is satisfied/rejected
  1551   function() {
  1552     let DEBUG = false;
  1553     try {
  1554       DEBUG = Services.prefs.getBoolPref("toolkit.osfile.debug.failshutdown");
  1555     } catch (ex) {
  1556       // Ignore
  1558     if (DEBUG) {
  1559       // Return a promise that will never be satisfied
  1560       return Promise.defer().promise;
  1561     } else {
  1562       return Scheduler.queue;
  1564   },
  1565   function getDetails() {
  1566     let result = {
  1567       launched: Scheduler.launched,
  1568       shutdown: Scheduler.shutdown,
  1569       worker: !!Scheduler._worker,
  1570       pendingReset: !!Scheduler.resetTimer,
  1571       latestSent: Scheduler.Debugging.latestSent,
  1572       latestReceived: Scheduler.Debugging.latestReceived,
  1573       messagesSent: Scheduler.Debugging.messagesSent,
  1574       messagesReceived: Scheduler.Debugging.messagesReceived,
  1575       messagesQueued: Scheduler.Debugging.messagesQueued,
  1576       DEBUG: SharedAll.Config.DEBUG
  1577     };
  1578     // Convert dates to strings for better readability
  1579     for (let key of ["latestSent", "latestReceived"]) {
  1580       if (result[key] && typeof result[key][0] == "number") {
  1581         result[key][0] = Date(result[key][0]);
  1584     return result;
  1586 );

mercurial