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/. */
7 var EXPORTED_SYMBOLS = ["EventPromise", "executeSoon", "PollPromise"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11 const { TYPE_REPEATING_SLACK } = Ci.nsITimer;
14 * Wait for a single event to be fired on a specific EventListener.
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:
21 * const promise = new EventPromise(element, "myEvent");
22 * // same event tick here
24 * // next event tick here
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
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.
40 * @return {Promise.<Event>}
44 function EventPromise(
49 wantsUntrusted: false,
50 mozSystemGroup: false,
53 if (!listener || !("addEventListener" in listener)) {
54 throw new TypeError();
56 if (typeof type != "string") {
57 throw new TypeError();
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")
65 throw new TypeError();
70 return new Promise(resolve => {
71 listener.addEventListener(
74 executeSoon(() => resolve(event));
82 * Wait for the next tick in the event loop to execute a callback.
84 * @param {function} fn
85 * Function to be executed.
87 function executeSoon(fn) {
88 if (typeof fn != "function") {
89 throw new TypeError();
92 Services.tm.dispatchToMainThread(fn);
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.
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.
116 * let els = new PollPromise((resolve, reject) => {
117 * let res = document.querySelectorAll("p");
118 * if (res.length > 0) {
119 * resolve(Array.from(res));
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.
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.
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();
152 if (timeout != null && typeof timeout != "number") {
153 throw new TypeError();
155 if (typeof interval != "number") {
156 throw new TypeError();
159 (timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
160 !Number.isInteger(interval) ||
163 throw new RangeError();
166 return new Promise((resolve, reject) => {
169 if (Number.isInteger(timeout)) {
170 start = new Date().getTime();
171 end = start + timeout;
176 .then(resolve, rejected => {
177 if (typeof rejected != "undefined") {
181 // return if there is a timeout and set to 0,
182 // allowing |func| to be evaluated at least once
184 typeof end != "undefined" &&
185 (start == end || new Date().getTime() >= end)
193 // the repeating slack timer waits |interval|
194 // before invoking |evalFn|
197 timer.init(evalFn, interval, TYPE_REPEATING_SLACK);