Bug 1625482 [wpt PR 22496] - [ScrollTimeline] Do not show scrollbar to bypass flakine...
[gecko.git] / toolkit / modules / Promise-backend.js
blob1bcfd68f54915071c1e028d42d974312651b1c81
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
9 /**
10  * This implementation file is imported by the Promise.jsm module, and as a
11  * special case by the debugger server.  To support chrome debugging, the
12  * debugger server needs to have all its code in one global, so it must use
13  * loadSubScript directly.
14  *
15  * In the general case, this script should be used by importing Promise.jsm:
16  *
17  * Components.utils.import("resource://gre/modules/Promise.jsm");
18  *
19  * More documentation can be found in the Promise.jsm module.
20  */
22 // Globals
24 // Obtain an instance of Cu. How this instance is obtained depends on how this
25 // file is loaded.
27 // This file can be loaded in three different ways:
28 // 1. As a CommonJS module, by Loader.jsm, on the main thread.
29 // 2. As a CommonJS module, by worker-loader.js, on a worker thread.
30 // 3. As a subscript, by Promise.jsm, on the main thread.
32 // If require is defined, the file is loaded as a CommonJS module. Components
33 // will not be defined in that case, but we can obtain an instance of Cu from
34 // the chrome module. Otherwise, this file is loaded as a subscript, and we can
35 // obtain an instance of Cu from Components directly.
37 // If the file is loaded as a CommonJS module on a worker thread, the instance
38 // of Cu obtained from the chrome module will be null. The reason for this is
39 // that Components is not defined in worker threads, so no instance of Cu can
40 // be obtained.
42 // As this can be loaded in several ways, allow require and module to be defined.
43 /* global module:false require:false */
44 // This is allowed in workers.
45 /* global setImmediate:false */
47 /* eslint-disable mozilla/no-define-cc-etc */
48 /* eslint-disable mozilla/use-cc-etc */
49 var Cu = this.require ? require("chrome").Cu : Components.utils;
50 var Cc = this.require ? require("chrome").Cc : Components.classes;
51 var Ci = this.require ? require("chrome").Ci : Components.interfaces;
52 /* eslint-enable mozilla/use-cc-etc */
53 /* eslint-enable mozilla/no-define-cc-etc */
54 // If we can access Components, then we use it to capture an async
55 // parent stack trace; see scheduleWalkerLoop.  However, as it might
56 // not be available (see above), users of this must check it first.
57 var Components_ = this.require ? require("chrome").components : Components;
59 // If Cu is defined, use it to lazily define the FinalizationWitnessService.
60 if (Cu) {
61   // If we're in a devtools module environment, ChromeUtils won't exist.
62   /* eslint "mozilla/use-chromeutils-import": ["error", {allowCu: true}] */
63   Cu.import("resource://gre/modules/Services.jsm", this);
64   Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
66   XPCOMUtils.defineLazyServiceGetter(
67     this,
68     "FinalizationWitnessService",
69     "@mozilla.org/toolkit/finalizationwitness;1",
70     "nsIFinalizationWitnessService"
71   );
74 const STATUS_PENDING = 0;
75 const STATUS_RESOLVED = 1;
76 const STATUS_REJECTED = 2;
78 // This N_INTERNALS name allow internal properties of the Promise to be
79 // accessed only by this module, while still being visible on the object
80 // manually when using a debugger.  This doesn't strictly guarantee that the
81 // properties are inaccessible by other code, but provide enough protection to
82 // avoid using them by mistake.
83 const salt = Math.floor(Math.random() * 100);
84 const N_INTERNALS = "{private:internals:" + salt + "}";
86 // We use DOM Promise for scheduling the walker loop.
87 const DOMPromise = Cu ? Promise : null;
89 // Warn-upon-finalization mechanism
91 // One of the difficult problems with promises is locating uncaught
92 // rejections. We adopt the following strategy: if a promise is rejected
93 // at the time of its garbage-collection *and* if the promise is at the
94 // end of a promise chain (i.e. |thatPromise.then| has never been
95 // called), then we print a warning.
97 //  let deferred = Promise.defer();
98 //  let p = deferred.promise.then();
99 //  deferred.reject(new Error("I am un uncaught error"));
100 //  deferred = null;
101 //  p = null;
103 // In this snippet, since |deferred.promise| is not the last in the
104 // chain, no error will be reported for that promise. However, since
105 // |p| is the last promise in the chain, the error will be reported
106 // for |p|.
108 // Note that this may, in some cases, cause an error to be reported more
109 // than once. For instance, consider:
111 //   let deferred = Promise.defer();
112 //   let p1 = deferred.promise.then();
113 //   let p2 = deferred.promise.then();
114 //   deferred.reject(new Error("I am an uncaught error"));
115 //   p1 = p2 = deferred = null;
117 // In this snippet, the error is reported both by p1 and by p2.
120 var PendingErrors = {
121   // An internal counter, used to generate unique id.
122   _counter: 0,
123   // Functions registered to be notified when a pending error
124   // is reported as uncaught.
125   _observers: new Set(),
126   _map: new Map(),
128   /**
129    * Initialize PendingErrors
130    */
131   init() {
132     Services.obs.addObserver(function observe(aSubject, aTopic, aValue) {
133       PendingErrors.report(aValue);
134     }, "promise-finalization-witness");
135   },
137   /**
138    * Register an error as tracked.
139    *
140    * @return The unique identifier of the error.
141    */
142   register(error) {
143     let id = "pending-error-" + this._counter++;
144     //
145     // At this stage, ideally, we would like to store the error itself
146     // and delay any treatment until we are certain that we will need
147     // to report that error. However, in the (unlikely but possible)
148     // case the error holds a reference to the promise itself, doing so
149     // would prevent the promise from being garbabe-collected, which
150     // would both cause a memory leak and ensure that we cannot report
151     // the uncaught error.
152     //
153     // To avoid this situation, we rather extract relevant data from
154     // the error and further normalize it to strings.
155     //
156     let value = {
157       date: new Date(),
158       message: "" + error,
159       fileName: null,
160       stack: null,
161       lineNumber: null,
162     };
163     try {
164       // Defend against non-enumerable values
165       if (error && error instanceof Ci.nsIException) {
166         // nsIException does things a little differently.
167         try {
168           // For starters |.toString()| does not only contain the message, but
169           // also the top stack frame, and we don't really want that.
170           value.message = error.message;
171         } catch (ex) {
172           // Ignore field
173         }
174         try {
175           // All lowercase filename. ;)
176           value.fileName = error.filename;
177         } catch (ex) {
178           // Ignore field
179         }
180         try {
181           value.lineNumber = error.lineNumber;
182         } catch (ex) {
183           // Ignore field
184         }
185       } else if (typeof error == "object" && error) {
186         for (let k of ["fileName", "stack", "lineNumber"]) {
187           try {
188             // Defend against fallible getters and string conversions
189             let v = error[k];
190             value[k] = v ? "" + v : null;
191           } catch (ex) {
192             // Ignore field
193           }
194         }
195       }
197       if (!value.stack) {
198         // |error| is not an Error (or Error-alike). Try to figure out the stack.
199         let stack = null;
200         if (
201           error &&
202           error.location &&
203           error.location instanceof Ci.nsIStackFrame
204         ) {
205           // nsIException has full stack frames in the |.location| member.
206           stack = error.location;
207         } else {
208           // Components.stack to the rescue!
209           stack = Components_.stack;
210           // Remove those top frames that refer to Promise.jsm.
211           while (stack) {
212             if (!stack.filename.endsWith("/Promise.jsm")) {
213               break;
214             }
215             stack = stack.caller;
216           }
217         }
218         if (stack) {
219           let frames = [];
220           while (stack) {
221             frames.push(stack);
222             stack = stack.caller;
223           }
224           value.stack = frames.join("\n");
225         }
226       }
227     } catch (ex) {
228       // Ignore value
229     }
230     this._map.set(id, value);
231     return id;
232   },
234   /**
235    * Notify all observers that a pending error is now uncaught.
236    *
237    * @param id The identifier of the pending error, as returned by
238    * |register|.
239    */
240   report(id) {
241     let value = this._map.get(id);
242     if (!value) {
243       return; // The error has already been reported
244     }
245     this._map.delete(id);
246     for (let obs of this._observers.values()) {
247       obs(value);
248     }
249   },
251   /**
252    * Mark all pending errors are uncaught, notify the observers.
253    */
254   flush() {
255     // Since we are going to modify the map while walking it,
256     // let's copying the keys first.
257     for (let key of Array.from(this._map.keys())) {
258       this.report(key);
259     }
260   },
262   /**
263    * Stop tracking an error, as this error has been caught,
264    * eventually.
265    */
266   unregister(id) {
267     this._map.delete(id);
268   },
270   /**
271    * Add an observer notified when an error is reported as uncaught.
272    *
273    * @param {function} observer A function notified when an error is
274    * reported as uncaught. Its arguments are
275    *   {message, date, fileName, stack, lineNumber}
276    * All arguments are optional.
277    */
278   addObserver(observer) {
279     this._observers.add(observer);
280   },
282   /**
283    * Remove an observer added with addObserver
284    */
285   removeObserver(observer) {
286     this._observers.delete(observer);
287   },
289   /**
290    * Remove all the observers added with addObserver
291    */
292   removeAllObservers() {
293     this._observers.clear();
294   },
297 // Initialize the warn-upon-finalization mechanism if and only if Cu is defined.
298 // Otherwise, FinalizationWitnessService won't be defined (see above).
299 if (Cu) {
300   PendingErrors.init();
303 // Default mechanism for displaying errors
304 PendingErrors.addObserver(function(details) {
305   const generalDescription =
306     "A promise chain failed to handle a rejection." +
307     " Did you forget to '.catch', or did you forget to 'return'?\nSee" +
308     " https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n\n";
310   let error = Cc["@mozilla.org/scripterror;1"].createInstance(
311     Ci.nsIScriptError
312   );
313   if (!error || !Services.console) {
314     // Too late during shutdown to use the nsIConsole
315     dump("*************************\n");
316     dump(generalDescription);
317     dump("On: " + details.date + "\n");
318     dump("Full message: " + details.message + "\n");
319     dump("Full stack: " + (details.stack || "not available") + "\n");
320     dump("*************************\n");
321     return;
322   }
323   let message = details.message;
324   if (details.stack) {
325     message += "\nFull Stack: " + details.stack;
326   }
327   error.init(
328     /* message*/ generalDescription +
329       "Date: " +
330       details.date +
331       "\nFull Message: " +
332       message,
333     /* sourceName*/ details.fileName,
334     /* sourceLine*/ details.lineNumber ? "" + details.lineNumber : 0,
335     /* lineNumber*/ details.lineNumber || 0,
336     /* columnNumber*/ 0,
337     /* flags*/ Ci.nsIScriptError.errorFlag,
338     /* category*/ "chrome javascript"
339   );
340   Services.console.logMessage(error);
343 // Additional warnings for developers
345 // The following error types are considered programmer errors, which should be
346 // reported (possibly redundantly) so as to let programmers fix their code.
347 const ERRORS_TO_REPORT = [
348   "EvalError",
349   "RangeError",
350   "ReferenceError",
351   "TypeError",
354 // Promise
357  * The Promise constructor. Creates a new promise given an executor callback.
358  * The executor callback is called with the resolve and reject handlers.
360  * @param aExecutor
361  *        The callback that will be called with resolve and reject.
362  */
363 this.Promise = function Promise(aExecutor) {
364   if (typeof aExecutor != "function") {
365     throw new TypeError("Promise constructor must be called with an executor.");
366   }
368   /*
369    * Object holding all of our internal values we associate with the promise.
370    */
371   Object.defineProperty(this, N_INTERNALS, {
372     value: {
373       /*
374        * Internal status of the promise.  This can be equal to STATUS_PENDING,
375        * STATUS_RESOLVED, or STATUS_REJECTED.
376        */
377       status: STATUS_PENDING,
379       /*
380        * When the status property is STATUS_RESOLVED, this contains the final
381        * resolution value, that cannot be a promise, because resolving with a
382        * promise will cause its state to be eventually propagated instead.  When the
383        * status property is STATUS_REJECTED, this contains the final rejection
384        * reason, that could be a promise, even if this is uncommon.
385        */
386       value: undefined,
388       /*
389        * Array of Handler objects registered by the "then" method, and not processed
390        * yet.  Handlers are removed when the promise is resolved or rejected.
391        */
392       handlers: [],
394       /**
395        * When the status property is STATUS_REJECTED and until there is
396        * a rejection callback, this contains an array
397        * - {string} id An id for use with |PendingErrors|;
398        * - {FinalizationWitness} witness A witness broadcasting |id| on
399        *   notification "promise-finalization-witness".
400        */
401       witness: undefined,
402     },
403   });
405   Object.seal(this);
407   let resolve = PromiseWalker.completePromise.bind(
408     PromiseWalker,
409     this,
410     STATUS_RESOLVED
411   );
412   let reject = PromiseWalker.completePromise.bind(
413     PromiseWalker,
414     this,
415     STATUS_REJECTED
416   );
418   try {
419     aExecutor(resolve, reject);
420   } catch (ex) {
421     reject(ex);
422   }
426  * Calls one of the provided functions as soon as this promise is either
427  * resolved or rejected.  A new promise is returned, whose state evolves
428  * depending on this promise and the provided callback functions.
430  * The appropriate callback is always invoked after this method returns, even
431  * if this promise is already resolved or rejected.  You can also call the
432  * "then" method multiple times on the same promise, and the callbacks will be
433  * invoked in the same order as they were registered.
435  * @param aOnResolve
436  *        If the promise is resolved, this function is invoked with the
437  *        resolution value of the promise as its only argument, and the
438  *        outcome of the function determines the state of the new promise
439  *        returned by the "then" method.  In case this parameter is not a
440  *        function (usually "null"), the new promise returned by the "then"
441  *        method is resolved with the same value as the original promise.
443  * @param aOnReject
444  *        If the promise is rejected, this function is invoked with the
445  *        rejection reason of the promise as its only argument, and the
446  *        outcome of the function determines the state of the new promise
447  *        returned by the "then" method.  In case this parameter is not a
448  *        function (usually left "undefined"), the new promise returned by the
449  *        "then" method is rejected with the same reason as the original
450  *        promise.
452  * @return A new promise that is initially pending, then assumes a state that
453  *         depends on the outcome of the invoked callback function:
454  *          - If the callback returns a value that is not a promise, including
455  *            "undefined", the new promise is resolved with this resolution
456  *            value, even if the original promise was rejected.
457  *          - If the callback throws an exception, the new promise is rejected
458  *            with the exception as the rejection reason, even if the original
459  *            promise was resolved.
460  *          - If the callback returns a promise, the new promise will
461  *            eventually assume the same state as the returned promise.
463  * @note If the aOnResolve callback throws an exception, the aOnReject
464  *       callback is not invoked.  You can register a rejection callback on
465  *       the returned promise instead, to process any exception occurred in
466  *       either of the callbacks registered on this promise.
467  */
468 Promise.prototype.then = function(aOnResolve, aOnReject) {
469   let handler = new Handler(this, aOnResolve, aOnReject);
470   this[N_INTERNALS].handlers.push(handler);
472   // Ensure the handler is scheduled for processing if this promise is already
473   // resolved or rejected.
474   if (this[N_INTERNALS].status != STATUS_PENDING) {
475     // This promise is not the last in the chain anymore. Remove any watchdog.
476     if (this[N_INTERNALS].witness != null) {
477       let [id, witness] = this[N_INTERNALS].witness;
478       this[N_INTERNALS].witness = null;
479       witness.forget();
480       PendingErrors.unregister(id);
481     }
483     PromiseWalker.schedulePromise(this);
484   }
486   return handler.nextPromise;
490  * Invokes `promise.then` with undefined for the resolve handler and a given
491  * reject handler.
493  * @param aOnReject
494  *        The rejection handler.
496  * @return A new pending promise returned.
498  * @see Promise.prototype.then
499  */
500 Promise.prototype.catch = function(aOnReject) {
501   return this.then(undefined, aOnReject);
505  * Creates a new pending promise and provides methods to resolve or reject it.
507  * @return A new object, containing the new promise in the "promise" property,
508  *         and the methods to change its state in the "resolve" and "reject"
509  *         properties.  See the Deferred documentation for details.
510  */
511 Promise.defer = function() {
512   return new Deferred();
516  * Creates a new promise resolved with the specified value, or propagates the
517  * state of an existing promise.
519  * @param aValue
520  *        If this value is not a promise, including "undefined", it becomes
521  *        the resolution value of the returned promise.  If this value is a
522  *        promise, then the returned promise will eventually assume the same
523  *        state as the provided promise.
525  * @return A promise that can be pending, resolved, or rejected.
526  */
527 Promise.resolve = function(aValue) {
528   if (aValue && typeof aValue == "function" && aValue.isAsyncFunction) {
529     throw new TypeError(
530       "Cannot resolve a promise with an async function. " +
531         "You should either invoke the async function first " +
532         "or use 'Task.spawn' instead of 'Task.async' to start " +
533         "the Task and return its promise."
534     );
535   }
537   if (aValue instanceof Promise) {
538     return aValue;
539   }
541   return new Promise(aResolve => aResolve(aValue));
545  * Creates a new promise rejected with the specified reason.
547  * @param aReason
548  *        The rejection reason for the returned promise.  Although the reason
549  *        can be "undefined", it is generally an Error object, like in
550  *        exception handling.
552  * @return A rejected promise.
554  * @note The aReason argument should not be a promise.  Using a rejected
555  *       promise for the value of aReason would make the rejection reason
556  *       equal to the rejected promise itself, and not its rejection reason.
557  */
558 Promise.reject = function(aReason) {
559   return new Promise((_, aReject) => aReject(aReason));
563  * Returns a promise that is resolved or rejected when all values are
564  * resolved or any is rejected.
566  * @param aValues
567  *        Iterable of promises that may be pending, resolved, or rejected. When
568  *        all are resolved or any is rejected, the returned promise will be
569  *        resolved or rejected as well.
571  * @return A new promise that is fulfilled when all values are resolved or
572  *         that is rejected when any of the values are rejected. Its
573  *         resolution value will be an array of all resolved values in the
574  *         given order, or undefined if aValues is an empty array. The reject
575  *         reason will be forwarded from the first promise in the list of
576  *         given promises to be rejected.
577  */
578 Promise.all = function(aValues) {
579   if (aValues == null || typeof aValues[Symbol.iterator] != "function") {
580     throw new Error("Promise.all() expects an iterable.");
581   }
583   return new Promise((resolve, reject) => {
584     let values = Array.isArray(aValues) ? aValues : [...aValues];
585     let countdown = values.length;
586     let resolutionValues = new Array(countdown);
588     if (!countdown) {
589       resolve(resolutionValues);
590       return;
591     }
593     function checkForCompletion(aValue, aIndex) {
594       resolutionValues[aIndex] = aValue;
595       if (--countdown === 0) {
596         resolve(resolutionValues);
597       }
598     }
600     for (let i = 0; i < values.length; i++) {
601       let index = i;
602       let value = values[i];
603       let resolver = val => checkForCompletion(val, index);
605       if (value && typeof value.then == "function") {
606         value.then(resolver, reject);
607       } else {
608         // Given value is not a promise, forward it as a resolution value.
609         resolver(value);
610       }
611     }
612   });
616  * Returns a promise that is resolved or rejected when the first value is
617  * resolved or rejected, taking on the value or reason of that promise.
619  * @param aValues
620  *        Iterable of values or promises that may be pending, resolved, or
621  *        rejected. When any is resolved or rejected, the returned promise will
622  *        be resolved or rejected as to the given value or reason.
624  * @return A new promise that is fulfilled when any values are resolved or
625  *         rejected. Its resolution value will be forwarded from the resolution
626  *         value or rejection reason.
627  */
628 Promise.race = function(aValues) {
629   if (aValues == null || typeof aValues[Symbol.iterator] != "function") {
630     throw new Error("Promise.race() expects an iterable.");
631   }
633   return new Promise((resolve, reject) => {
634     for (let value of aValues) {
635       Promise.resolve(value).then(resolve, reject);
636     }
637   });
640 Promise.Debugging = {
641   /**
642    * Add an observer notified when an error is reported as uncaught.
643    *
644    * @param {function} observer A function notified when an error is
645    * reported as uncaught. Its arguments are
646    *   {message, date, fileName, stack, lineNumber}
647    * All arguments are optional.
648    */
649   addUncaughtErrorObserver(observer) {
650     PendingErrors.addObserver(observer);
651   },
653   /**
654    * Remove an observer added with addUncaughtErrorObserver
655    *
656    * @param {function} An observer registered with
657    * addUncaughtErrorObserver.
658    */
659   removeUncaughtErrorObserver(observer) {
660     PendingErrors.removeObserver(observer);
661   },
663   /**
664    * Remove all the observers added with addUncaughtErrorObserver
665    */
666   clearUncaughtErrorObservers() {
667     PendingErrors.removeAllObservers();
668   },
670   /**
671    * Force all pending errors to be reported immediately as uncaught.
672    * Note that this may cause some false positives.
673    */
674   flushUncaughtErrors() {
675     PendingErrors.flush();
676   },
678 Object.freeze(Promise.Debugging);
680 Object.freeze(Promise);
682 // If module is defined, this file is loaded as a CommonJS module. Make sure
683 // Promise is exported in that case.
684 if (this.module) {
685   module.exports = Promise;
688 // PromiseWalker
691  * This singleton object invokes the handlers registered on resolved and
692  * rejected promises, ensuring that processing is not recursive and is done in
693  * the same order as registration occurred on each promise.
695  * There is no guarantee on the order of execution of handlers registered on
696  * different promises.
697  */
698 this.PromiseWalker = {
699   /**
700    * Singleton array of all the unprocessed handlers currently registered on
701    * resolved or rejected promises.  Handlers are removed from the array as soon
702    * as they are processed.
703    */
704   handlers: [],
706   /**
707    * Called when a promise needs to change state to be resolved or rejected.
708    *
709    * @param aPromise
710    *        Promise that needs to change state.  If this is already resolved or
711    *        rejected, this method has no effect.
712    * @param aStatus
713    *        New desired status, either STATUS_RESOLVED or STATUS_REJECTED.
714    * @param aValue
715    *        Associated resolution value or rejection reason.
716    */
717   completePromise(aPromise, aStatus, aValue) {
718     // Do nothing if the promise is already resolved or rejected.
719     if (aPromise[N_INTERNALS].status != STATUS_PENDING) {
720       return;
721     }
723     // Resolving with another promise will cause this promise to eventually
724     // assume the state of the provided promise.
725     if (
726       aStatus == STATUS_RESOLVED &&
727       aValue &&
728       typeof aValue.then == "function"
729     ) {
730       aValue.then(
731         this.completePromise.bind(this, aPromise, STATUS_RESOLVED),
732         this.completePromise.bind(this, aPromise, STATUS_REJECTED)
733       );
734       return;
735     }
737     // Change the promise status and schedule our handlers for processing.
738     aPromise[N_INTERNALS].status = aStatus;
739     aPromise[N_INTERNALS].value = aValue;
740     if (aPromise[N_INTERNALS].handlers.length) {
741       this.schedulePromise(aPromise);
742     } else if (Cu && aStatus == STATUS_REJECTED) {
743       // This is a rejection and the promise is the last in the chain.
744       // For the time being we therefore have an uncaught error.
745       let id = PendingErrors.register(aValue);
746       let witness = FinalizationWitnessService.make(
747         "promise-finalization-witness",
748         id
749       );
750       aPromise[N_INTERNALS].witness = [id, witness];
751     }
752   },
754   /**
755    * Sets up the PromiseWalker loop to start on the next tick of the event loop
756    */
757   scheduleWalkerLoop() {
758     this.walkerLoopScheduled = true;
760     // If this file is loaded on a worker thread, DOMPromise will not behave as
761     // expected: because native promises are not aware of nested event loops
762     // created by the debugger, their respective handlers will not be called
763     // until after leaving the nested event loop. The debugger server relies
764     // heavily on the use promises, so this could cause the debugger to hang.
765     //
766     // To work around this problem, any use of native promises in the debugger
767     // server should be avoided when it is running on a worker thread. Because
768     // it is still necessary to be able to schedule runnables on the event
769     // queue, the worker loader defines the function setImmediate as a
770     // per-module global for this purpose.
771     //
772     // If Cu is defined, this file is loaded on the main thread. Otherwise, it
773     // is loaded on the worker thread.
774     if (Cu) {
775       let stack = Components_ ? Components_.stack : null;
776       if (stack) {
777         DOMPromise.resolve().then(() => {
778           Cu.callFunctionWithAsyncStack(
779             this.walkerLoop.bind(this),
780             stack,
781             "Promise"
782           );
783         });
784       } else {
785         DOMPromise.resolve().then(() => this.walkerLoop());
786       }
787     } else {
788       setImmediate(this.walkerLoop);
789     }
790   },
792   /**
793    * Schedules the resolution or rejection handlers registered on the provided
794    * promise for processing.
795    *
796    * @param aPromise
797    *        Resolved or rejected promise whose handlers should be processed.  It
798    *        is expected that this promise has at least one handler to process.
799    */
800   schedulePromise(aPromise) {
801     // Migrate the handlers from the provided promise to the global list.
802     for (let handler of aPromise[N_INTERNALS].handlers) {
803       this.handlers.push(handler);
804     }
805     aPromise[N_INTERNALS].handlers.length = 0;
807     // Schedule the walker loop on the next tick of the event loop.
808     if (!this.walkerLoopScheduled) {
809       this.scheduleWalkerLoop();
810     }
811   },
813   /**
814    * Indicates whether the walker loop is currently scheduled for execution on
815    * the next tick of the event loop.
816    */
817   walkerLoopScheduled: false,
819   /**
820    * Processes all the known handlers during this tick of the event loop.  This
821    * eager processing is done to avoid unnecessarily exiting and re-entering the
822    * JavaScript context for each handler on a resolved or rejected promise.
823    *
824    * This function is called with "this" bound to the PromiseWalker object.
825    */
826   walkerLoop() {
827     // If there is more than one handler waiting, reschedule the walker loop
828     // immediately.  Otherwise, use walkerLoopScheduled to tell schedulePromise()
829     // to reschedule the loop if it adds more handlers to the queue.  This makes
830     // this walker resilient to the case where one handler does not return, but
831     // starts a nested event loop.  In that case, the newly scheduled walker will
832     // take over.  In the common case, the newly scheduled walker will be invoked
833     // after this one has returned, with no actual handler to process.  This
834     // small overhead is required to make nested event loops work correctly, but
835     // occurs at most once per resolution chain, thus having only a minor
836     // impact on overall performance.
837     if (this.handlers.length > 1) {
838       this.scheduleWalkerLoop();
839     } else {
840       this.walkerLoopScheduled = false;
841     }
843     // Process all the known handlers eagerly.
844     while (this.handlers.length) {
845       this.handlers.shift().process();
846     }
847   },
850 // Bind the function to the singleton once.
851 PromiseWalker.walkerLoop = PromiseWalker.walkerLoop.bind(PromiseWalker);
853 // Deferred
856  * Returned by "Promise.defer" to provide a new promise along with methods to
857  * change its state.
858  */
859 function Deferred() {
860   this.promise = new Promise((aResolve, aReject) => {
861     this.resolve = aResolve;
862     this.reject = aReject;
863   });
864   Object.freeze(this);
867 Deferred.prototype = {
868   /**
869    * A newly created promise, initially in the pending state.
870    */
871   promise: null,
873   /**
874    * Resolves the associated promise with the specified value, or propagates the
875    * state of an existing promise.  If the associated promise has already been
876    * resolved or rejected, this method does nothing.
877    *
878    * This function is bound to its associated promise when "Promise.defer" is
879    * called, and can be called with any value of "this".
880    *
881    * @param aValue
882    *        If this value is not a promise, including "undefined", it becomes
883    *        the resolution value of the associated promise.  If this value is a
884    *        promise, then the associated promise will eventually assume the same
885    *        state as the provided promise.
886    *
887    * @note Calling this method with a pending promise as the aValue argument,
888    *       and then calling it again with another value before the promise is
889    *       resolved or rejected, has unspecified behavior and should be avoided.
890    */
891   resolve: null,
893   /**
894    * Rejects the associated promise with the specified reason.  If the promise
895    * has already been resolved or rejected, this method does nothing.
896    *
897    * This function is bound to its associated promise when "Promise.defer" is
898    * called, and can be called with any value of "this".
899    *
900    * @param aReason
901    *        The rejection reason for the associated promise.  Although the
902    *        reason can be "undefined", it is generally an Error object, like in
903    *        exception handling.
904    *
905    * @note The aReason argument should not generally be a promise.  In fact,
906    *       using a rejected promise for the value of aReason would make the
907    *       rejection reason equal to the rejected promise itself, not to the
908    *       rejection reason of the rejected promise.
909    */
910   reject: null,
913 // Handler
916  * Handler registered on a promise by the "then" function.
917  */
918 function Handler(aThisPromise, aOnResolve, aOnReject) {
919   this.thisPromise = aThisPromise;
920   this.onResolve = aOnResolve;
921   this.onReject = aOnReject;
922   this.nextPromise = new Promise(() => {});
925 Handler.prototype = {
926   /**
927    * Promise on which the "then" method was called.
928    */
929   thisPromise: null,
931   /**
932    * Unmodified resolution handler provided to the "then" method.
933    */
934   onResolve: null,
936   /**
937    * Unmodified rejection handler provided to the "then" method.
938    */
939   onReject: null,
941   /**
942    * New promise that will be returned by the "then" method.
943    */
944   nextPromise: null,
946   /**
947    * Called after thisPromise is resolved or rejected, invokes the appropriate
948    * callback and propagates the result to nextPromise.
949    */
950   process() {
951     // The state of this promise is propagated unless a handler is defined.
952     let nextStatus = this.thisPromise[N_INTERNALS].status;
953     let nextValue = this.thisPromise[N_INTERNALS].value;
955     try {
956       // If a handler is defined for either resolution or rejection, invoke it
957       // to determine the state of the next promise, that will be resolved with
958       // the returned value, that can also be another promise.
959       if (nextStatus == STATUS_RESOLVED) {
960         if (typeof this.onResolve == "function") {
961           nextValue = this.onResolve.call(undefined, nextValue);
962         }
963       } else if (typeof this.onReject == "function") {
964         nextValue = this.onReject.call(undefined, nextValue);
965         nextStatus = STATUS_RESOLVED;
966       }
967     } catch (ex) {
968       // An exception has occurred in the handler.
970       if (
971         ex &&
972         typeof ex == "object" &&
973         "name" in ex &&
974         ERRORS_TO_REPORT.includes(ex.name)
975       ) {
976         // We suspect that the exception is a programmer error, so we now
977         // display it using dump().  Note that we do not use Cu.reportError as
978         // we assume that this is a programming error, so we do not want end
979         // users to see it. Also, if the programmer handles errors correctly,
980         // they will either treat the error or log them somewhere.
982         dump("*************************\n");
983         dump(
984           "A coding exception was thrown in a Promise " +
985             (nextStatus == STATUS_RESOLVED ? "resolution" : "rejection") +
986             " callback.\n"
987         );
988         dump(
989           "See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n\n"
990         );
991         dump("Full message: " + ex + "\n");
992         dump(
993           "Full stack: " + ("stack" in ex ? ex.stack : "not available") + "\n"
994         );
995         dump("*************************\n");
996       }
998       // Additionally, reject the next promise.
999       nextStatus = STATUS_REJECTED;
1000       nextValue = ex;
1001     }
1003     // Propagate the newly determined state to the next promise.
1004     PromiseWalker.completePromise(this.nextPromise, nextStatus, nextValue);
1005   },