Bug 1645920 [wpt PR 24157] - [AspectRatio] Make intrinsic sizes respect aspect-ratio...
[gecko.git] / remote / Sync.jsm
blobe7f1057914d7ca884cedafaff9a607696f61f56f
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var EXPORTED_SYMBOLS = ["EventPromise", "executeSoon", "PollPromise"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11 const { TYPE_REPEATING_SLACK } = Ci.nsITimer;
13 /**
14  * Wait for a single event to be fired on a specific EventListener.
15  *
16  * The returned promise is guaranteed to not be called before the
17  * next event tick after the event listener is called, so that all
18  * other event listeners for the element are executed before the
19  * handler is executed.  For example:
20  *
21  *     const promise = new EventPromise(element, "myEvent");
22  *     // same event tick here
23  *     await promise;
24  *     // next event tick here
25  *
26  * @param {EventListener} listener
27  *     Object which receives a notification (an object that implements
28  *     the Event interface) when an event of the specificed type occurs.
29  * @param {string} type
30  *     Case-sensitive string representing the event type to listen for.
31  * @param {boolean?} [false] options.capture
32  *     Indicates the event will be despatched to this subject,
33  *     before it bubbles down to any EventTarget beneath it in the
34  *     DOM tree.
35  * @param {boolean?} [false] options.wantsUntrusted
36  *     Receive synthetic events despatched by web content.
37  * @param {boolean?} [false] options.mozSystemGroup
38  *     Determines whether to add listener to the system group.
39  *
40  * @return {Promise.<Event>}
41  *
42  * @throws {TypeError}
43  */
44 function EventPromise(
45   listener,
46   type,
47   options = {
48     capture: false,
49     wantsUntrusted: false,
50     mozSystemGroup: false,
51   }
52 ) {
53   if (!listener || !("addEventListener" in listener)) {
54     throw new TypeError();
55   }
56   if (typeof type != "string") {
57     throw new TypeError();
58   }
59   if (
60     ("capture" in options && typeof options.capture != "boolean") ||
61     ("wantsUntrusted" in options &&
62       typeof options.wantsUntrusted != "boolean") ||
63     ("mozSystemGroup" in options && typeof options.mozSystemGroup != "boolean")
64   ) {
65     throw new TypeError();
66   }
68   options.once = true;
70   return new Promise(resolve => {
71     listener.addEventListener(
72       type,
73       event => {
74         executeSoon(() => resolve(event));
75       },
76       options
77     );
78   });
81 /**
82  * Wait for the next tick in the event loop to execute a callback.
83  *
84  * @param {function} fn
85  *     Function to be executed.
86  */
87 function executeSoon(fn) {
88   if (typeof fn != "function") {
89     throw new TypeError();
90   }
92   Services.tm.dispatchToMainThread(fn);
95 /**
96  * Runs a Promise-like function off the main thread until it is resolved
97  * through ``resolve`` or ``rejected`` callbacks.  The function is
98  * guaranteed to be run at least once, irregardless of the timeout.
99  *
100  * The ``func`` is evaluated every ``interval`` for as long as its
101  * runtime duration does not exceed ``interval``.  Evaluations occur
102  * sequentially, meaning that evaluations of ``func`` are queued if
103  * the runtime evaluation duration of ``func`` is greater than ``interval``.
105  * ``func`` is given two arguments, ``resolve`` and ``reject``,
106  * of which one must be called for the evaluation to complete.
107  * Calling ``resolve`` with an argument indicates that the expected
108  * wait condition was met and will return the passed value to the
109  * caller.  Conversely, calling ``reject`` will evaluate ``func``
110  * again until the ``timeout`` duration has elapsed or ``func`` throws.
111  * The passed value to ``reject`` will also be returned to the caller
112  * once the wait has expired.
114  * Usage::
116  *     let els = new PollPromise((resolve, reject) => {
117  *       let res = document.querySelectorAll("p");
118  *       if (res.length > 0) {
119  *         resolve(Array.from(res));
120  *       } else {
121  *         reject([]);
122  *       }
123  *     }, {timeout: 1000});
125  * @param {Condition} func
126  *     Function to run off the main thread.
127  * @param {number=} [timeout] timeout
128  *     Desired timeout if wanted.  If 0 or less than the runtime evaluation
129  *     time of ``func``, ``func`` is guaranteed to run at least once.
130  *     Defaults to using no timeout.
131  * @param {number=} [interval=10] interval
132  *     Duration between each poll of ``func`` in milliseconds.
133  *     Defaults to 10 milliseconds.
135  * @return {Promise.<*>}
136  *     Yields the value passed to ``func``'s
137  *     ``resolve`` or ``reject`` callbacks.
139  * @throws {*}
140  *     If ``func`` throws, its error is propagated.
141  * @throws {TypeError}
142  *     If `timeout` or `interval`` are not numbers.
143  * @throws {RangeError}
144  *     If `timeout` or `interval` are not unsigned integers.
145  */
146 function PollPromise(func, { timeout = null, interval = 10 } = {}) {
147   const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
149   if (typeof func != "function") {
150     throw new TypeError();
151   }
152   if (timeout != null && typeof timeout != "number") {
153     throw new TypeError();
154   }
155   if (typeof interval != "number") {
156     throw new TypeError();
157   }
158   if (
159     (timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
160     !Number.isInteger(interval) ||
161     interval < 0
162   ) {
163     throw new RangeError();
164   }
166   return new Promise((resolve, reject) => {
167     let start, end;
169     if (Number.isInteger(timeout)) {
170       start = new Date().getTime();
171       end = start + timeout;
172     }
174     let evalFn = () => {
175       new Promise(func)
176         .then(resolve, rejected => {
177           if (typeof rejected != "undefined") {
178             throw rejected;
179           }
181           // return if there is a timeout and set to 0,
182           // allowing |func| to be evaluated at least once
183           if (
184             typeof end != "undefined" &&
185             (start == end || new Date().getTime() >= end)
186           ) {
187             resolve(rejected);
188           }
189         })
190         .catch(reject);
191     };
193     // the repeating slack timer waits |interval|
194     // before invoking |evalFn|
195     evalFn();
197     timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
198   }).then(
199     res => {
200       timer.cancel();
201       return res;
202     },
203     err => {
204       timer.cancel();
205       throw err;
206     }
207   );