Sat, 03 Jan 2015 20:18:00 +0100
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:
1000 *
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.
1004 *
1005 * @rejects {OS.File.Error} In case of any error.
1006 *
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.
1010 *
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.
1022 *
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:
1028 *
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.
1032 *
1033 * @returns {Promise}
1034 * @rejects {OS.File.Error} In case of any error.
1035 *
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.
1039 *
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.
1051 *
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.
1056 *
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 };
1065 }
1067 /**
1068 * Gets the number of bytes available on disk to the current user.
1069 *
1070 * @param {string} Platform-specific path to a directory on the disk to
1071 * query for free available bytes.
1072 *
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.
1084 *
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.
1097 *
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.
1109 *
1110 * @param {string} path The name of the directory.
1111 * @param {*=} options Additional options.
1112 *
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
1140 *
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.
1153 *
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;
1166 }
1167 }
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;
1177 }
1178 return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1179 });
1180 }
1182 // Otherwise, use the native implementation.
1183 return Scheduler.push(() => Native.read(path, options));
1184 };
1186 /**
1187 * Find outs if a file exists.
1188 *
1189 * @param {string} path The path to the file.
1190 *
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.
1200 *
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.
1204 *
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.
1212 *
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.
1238 *
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
1275 *
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]});
1284 }
1285 }
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;
1295 }
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
1312 *
1313 * @constructor
1314 */
1315 let DirectoryIterator = function DirectoryIterator(path, options) {
1316 /**
1317 * Open the iterator on the worker thread
1318 *
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);
1342 }
1343 return this.__itmsg;
1344 },
1346 /**
1347 * Determine whether the directory exists.
1348 *
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]);
1355 }
1356 );
1357 },
1358 /**
1359 * Get the next entry in the directory.
1360 *
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.
1379 *
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([]);
1388 }
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.
1402 *
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.
1408 *
1409 * If the callback returns a promise, iteration waits until the
1410 * promise is resolved before proceeding.
1411 *
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();
1418 }
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;
1428 }
1429 );
1431 // Then iterate
1432 let loop = function loop() {
1433 if (self._isClosed) {
1434 return Promise.resolve();
1435 }
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;
1443 }
1444 throw reason;
1445 }
1446 );
1447 };
1449 return promise.then(loop);
1450 },
1451 /**
1452 * Auxiliary method: fetch the next item
1453 *
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;
1460 }
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;
1469 }
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();
1480 }
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]);
1489 }
1490 );
1491 }
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));
1508 }
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;
1532 }
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;
1541 }
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
1557 }
1558 if (DEBUG) {
1559 // Return a promise that will never be satisfied
1560 return Promise.defer().promise;
1561 } else {
1562 return Scheduler.queue;
1563 }
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]);
1582 }
1583 }
1584 return result;
1585 }
1586 );