gecko-dev/testing/marionette/wait.js
Andreas Tolfsen 645c6db1b0 Bug 1319237 - Generalise wait condition utility; r=automatedtester,maja_zf
This makes the `implicitWaitFor` utility from
testing/marionette/element.js generally available in Marionette.

It improves on the design of the old wait utility by providing
promise-like resolve and reject options to the evaluated function.  These
can be used to indicate success or failure of waiting.  If resolved, the
provided value is returned immediately.  When rejected, the function is
evaluated over again until the timeout is reached or an error is thrown.

It is useful to indicate success and failure state because it saves the
calling code from guessing based on the return value.  Guessing from
the return value can be problematic since there are certain types and
values in JavaScript that are ambigeous or misleading, such as the fact
that empty arrays are evaluated as a truthy value.

MozReview-Commit-ID: G8F99tdbiNb

--HG--
extra : rebase_source : 88647b1c7115f15649d5029391ff21567f9d527c
2016-11-21 23:41:20 +01:00

97 lines
3.1 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("chrome://marionette/content/error.js");
this.EXPORTED_SYMBOLS = ["wait"];
/** Implicit wait utilities. */
this.wait = {};
/**
* Runs a promise-like function off the main thread until it is resolved
* through |resolve| or |rejected| callbacks. The function is guaranteed
* to be run at least once, irregardless of the timeout.
*
* The |func| is evaluated every |interval| for as long as its runtime
* duration does not exceed |interval|. Evaluations occur sequentially,
* meaning that evaluations of |func| are queued if the runtime evaluation
* duration of |func| is greater than |interval|.
*
* |func| is given two arguments, |resolve| and |reject|, of which one
* must be called for the evaluation to complete. Calling |resolve| with
* an argument indicates that the expected wait condition was met and
* will return the passed value to the caller. Conversely, calling
* |reject| will evaluate |func| again until the |timeout| duration has
* elapsed or |func| throws. The passed value to |reject| will also be
* returned to the caller once the wait has expired.
*
* Usage:
*
* let els = wait.until((resolve, reject) => {
* let res = document.querySelectorAll("p");
* if (res.length > 0) {
* resolve(Array.from(res));
* } else {
* reject([]);
* }
* });
*
* @param {function(resolve: function(?), reject: function(?)): ?} func
* Function to run off the main thread.
* @param {number=} timeout
* Desired timeout. If 0 or less than the runtime evaluation time
* of |func|, |func| is guaranteed to run at least once. The default
* is 2000 milliseconds.
* @param {number=} interval
* Duration between each poll of |func| in milliseconds. Defaults to
* 10 milliseconds.
*
* @return {Promise: ?}
* Yields the value passed to |func|'s |resolve| or |reject|
* callbacks.
*
* @throws {?}
* If |func| throws, its error is propagated.
*/
wait.until = function (func, timeout = 2000, interval = 10) {
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
return new Promise((resolve, reject) => {
const start = new Date().getTime();
const end = start + timeout;
let evalFn = () => {
new Promise(func).then(resolve, rejected => {
if (error.isError(rejected)) {
throw rejected;
}
// return if timeout is 0, allowing |func| to be evaluated at least once
if (start == end || new Date().getTime() >= end) {
resolve(rejected);
}
}).catch(reject);
};
// the repeating slack timer waits |interval|
// before invoking |evalFn|
evalFn();
timer.init(evalFn, interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
// cancel timer and propagate result
}).then(res => {
timer.cancel();
return res;
}, err => {
timer.cancel();
throw err;
});
};