2016-11-22 01:41:20 +03:00
|
|
|
/* 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";
|
|
|
|
|
2018-04-17 11:43:27 +03:00
|
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
2018-06-06 16:40:05 +03:00
|
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
2018-04-17 11:43:27 +03:00
|
|
|
|
2017-08-01 20:28:13 +03:00
|
|
|
const {
|
|
|
|
error,
|
|
|
|
TimeoutError,
|
2018-01-30 02:20:18 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
|
2018-06-06 16:40:05 +03:00
|
|
|
const {Log} = ChromeUtils.import("chrome://marionette/content/log.js", {});
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
|
2016-11-22 01:41:20 +03:00
|
|
|
|
2018-04-17 11:43:27 +03:00
|
|
|
this.EXPORTED_SYMBOLS = [
|
2018-08-05 18:17:36 +03:00
|
|
|
/* exported PollPromise, TimedPromise */
|
2018-04-17 11:43:27 +03:00
|
|
|
"PollPromise",
|
|
|
|
"TimedPromise",
|
2018-08-05 18:17:36 +03:00
|
|
|
|
|
|
|
/* exported MessageManagerDestroyedPromise */
|
|
|
|
"MessageManagerDestroyedPromise",
|
2018-04-17 11:43:27 +03:00
|
|
|
];
|
|
|
|
|
2017-08-01 20:28:13 +03:00
|
|
|
const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
|
|
|
|
|
2017-07-26 15:11:53 +03:00
|
|
|
/**
|
2017-08-01 20:26:09 +03:00
|
|
|
* @callback Condition
|
2017-07-26 15:11:53 +03:00
|
|
|
*
|
|
|
|
* @param {function(*)} resolve
|
|
|
|
* To be called when the condition has been met. Will return the
|
|
|
|
* resolved value.
|
|
|
|
* @param {function} reject
|
|
|
|
* To be called when the condition has not been met. Will cause
|
|
|
|
* the condition to be revaluated or time out.
|
|
|
|
*
|
|
|
|
* @return {*}
|
2018-02-26 14:44:32 +03:00
|
|
|
* The value from calling ``resolve``.
|
2017-07-26 15:11:53 +03:00
|
|
|
*/
|
|
|
|
|
2016-11-22 01:41:20 +03:00
|
|
|
/**
|
2018-02-26 14:44:32 +03:00
|
|
|
* 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.
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2018-02-26 14:44:32 +03:00
|
|
|
* 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``.
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2018-02-26 14:44:32 +03:00
|
|
|
* ``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.
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2018-02-26 14:44:32 +03:00
|
|
|
* Usage::
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2017-10-02 18:50:11 +03:00
|
|
|
* let els = new PollPromise((resolve, reject) => {
|
2016-11-22 01:41:20 +03:00
|
|
|
* let res = document.querySelectorAll("p");
|
|
|
|
* if (res.length > 0) {
|
|
|
|
* resolve(Array.from(res));
|
|
|
|
* } else {
|
|
|
|
* reject([]);
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*
|
2017-08-01 20:26:09 +03:00
|
|
|
* @param {Condition} func
|
2016-11-22 01:41:20 +03:00
|
|
|
* Function to run off the main thread.
|
2017-10-02 18:50:11 +03:00
|
|
|
* @param {number=} [timeout=2000] timeout
|
|
|
|
* Desired timeout. If 0 or less than the runtime evaluation
|
2018-02-26 14:44:32 +03:00
|
|
|
* time of ``func``, ``func`` is guaranteed to run at least once.
|
|
|
|
* The default is 2000 milliseconds.
|
2017-10-02 18:50:11 +03:00
|
|
|
* @param {number=} [interval=10] interval
|
2018-02-26 14:44:32 +03:00
|
|
|
* Duration between each poll of ``func`` in milliseconds.
|
2017-10-02 18:50:11 +03:00
|
|
|
* Defaults to 10 milliseconds.
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @return {Promise.<*>}
|
2018-02-26 14:44:32 +03:00
|
|
|
* Yields the value passed to ``func``'s
|
|
|
|
* ``resolve`` or ``reject`` callbacks.
|
2016-11-22 01:41:20 +03:00
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* @throws {*}
|
2018-02-26 14:44:32 +03:00
|
|
|
* If ``func`` throws, its error is propagated.
|
2016-11-22 01:41:20 +03:00
|
|
|
*/
|
2017-10-02 19:13:57 +03:00
|
|
|
function PollPromise(func, {timeout = 2000, interval = 10} = {}) {
|
2016-11-22 01:41:20 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-06-30 02:40:24 +03:00
|
|
|
// return if timeout is 0, allowing |func| to be evaluated at
|
|
|
|
// least once
|
2016-11-22 01:41:20 +03:00
|
|
|
if (start == end || new Date().getTime() >= end) {
|
|
|
|
resolve(rejected);
|
|
|
|
}
|
|
|
|
}).catch(reject);
|
|
|
|
};
|
|
|
|
|
|
|
|
// the repeating slack timer waits |interval|
|
|
|
|
// before invoking |evalFn|
|
|
|
|
evalFn();
|
|
|
|
|
2017-08-01 20:28:13 +03:00
|
|
|
timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
|
2016-11-22 01:41:20 +03:00
|
|
|
|
|
|
|
}).then(res => {
|
|
|
|
timer.cancel();
|
|
|
|
return res;
|
|
|
|
}, err => {
|
|
|
|
timer.cancel();
|
|
|
|
throw err;
|
|
|
|
});
|
2017-10-02 18:50:11 +03:00
|
|
|
}
|
2017-08-01 20:28:13 +03:00
|
|
|
|
|
|
|
/**
|
2018-02-26 14:44:32 +03:00
|
|
|
* Represents the timed, eventual completion (or failure) of an
|
|
|
|
* asynchronous operation, and its resulting value.
|
2017-08-01 20:28:13 +03:00
|
|
|
*
|
2018-02-26 14:44:32 +03:00
|
|
|
* In contrast to a regular Promise, it times out after ``timeout``.
|
2017-08-01 20:28:13 +03:00
|
|
|
*
|
|
|
|
* @param {Condition} func
|
2018-02-26 14:44:32 +03:00
|
|
|
* Function to run, which will have its ``reject``
|
|
|
|
* callback invoked after the ``timeout`` duration is reached.
|
|
|
|
* It is given two callbacks: ``resolve(value)`` and
|
|
|
|
* ``reject(error)``.
|
2017-08-01 20:28:13 +03:00
|
|
|
* @param {timeout=} [timeout=1500] timeout
|
2018-02-26 14:44:32 +03:00
|
|
|
* ``condition``'s ``reject`` callback will be called
|
2018-08-05 18:17:36 +03:00
|
|
|
* after this timeout.
|
2017-08-01 20:28:13 +03:00
|
|
|
* @param {Error=} [throws=TimeoutError] throws
|
2018-02-26 14:44:32 +03:00
|
|
|
* When the ``timeout`` is hit, this error class will be
|
2017-08-01 20:28:13 +03:00
|
|
|
* thrown. If it is null, no error is thrown and the promise is
|
|
|
|
* instead resolved on timeout.
|
|
|
|
*
|
|
|
|
* @return {Promise.<*>}
|
|
|
|
* Timed promise.
|
|
|
|
*/
|
|
|
|
function TimedPromise(fn, {timeout = 1500, throws = TimeoutError} = {}) {
|
|
|
|
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// Reject only if |throws| is given. Otherwise it is assumed that
|
|
|
|
// the user is OK with the promise timing out.
|
2017-10-03 16:35:47 +03:00
|
|
|
let bail = () => {
|
2017-08-01 20:28:13 +03:00
|
|
|
if (throws !== null) {
|
|
|
|
let err = new throws();
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
timer.initWithCallback({notify: bail}, timeout, TYPE_ONE_SHOT);
|
|
|
|
|
|
|
|
try {
|
|
|
|
fn(resolve, reject);
|
|
|
|
} catch (e) {
|
|
|
|
reject(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
}).then(res => {
|
|
|
|
timer.cancel();
|
|
|
|
return res;
|
|
|
|
}, err => {
|
|
|
|
timer.cancel();
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
}
|
2018-04-17 11:43:27 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Detects when the specified message manager has been destroyed.
|
|
|
|
*
|
|
|
|
* One can observe the removal and detachment of a content browser
|
|
|
|
* (`<xul:browser>`) or a chrome window by its message manager
|
|
|
|
* disconnecting.
|
|
|
|
*
|
|
|
|
* When a browser is associated with a tab, this is safer than only
|
|
|
|
* relying on the event `TabClose` which signalises the _intent to_
|
|
|
|
* remove a tab and consequently would lead to the destruction of
|
|
|
|
* the content browser and its browser message manager.
|
|
|
|
*
|
|
|
|
* When closing a chrome window it is safer than only relying on
|
|
|
|
* the event 'unload' which signalises the _intent to_ close the
|
|
|
|
* chrome window and consequently would lead to the destruction of
|
|
|
|
* the window and its window message manager.
|
|
|
|
*
|
|
|
|
* @param {MessageListenerManager} messageManager
|
|
|
|
* The message manager to observe for its disconnect state.
|
|
|
|
* Use the browser message manager when closing a content browser,
|
|
|
|
* and the window message manager when closing a chrome window.
|
|
|
|
*
|
|
|
|
* @return {Promise}
|
|
|
|
* A promise that resolves when the message manager has been destroyed.
|
|
|
|
*/
|
|
|
|
function MessageManagerDestroyedPromise(messageManager) {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
function observe(subject, topic) {
|
|
|
|
logger.debug(`Received observer notification ${topic}`);
|
|
|
|
|
|
|
|
if (subject == messageManager) {
|
|
|
|
Services.obs.removeObserver(this, "message-manager-disconnect");
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Services.obs.addObserver(observe, "message-manager-disconnect");
|
|
|
|
});
|
|
|
|
}
|