Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / devtools / shared / ThreadSafeDevToolsUtils.js
blobfba93a9dcc6ee028c0b0294aa121a4d08c137736
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 /**
8 * General utilities used throughout devtools that can also be used in
9 * workers.
12 /**
13 * Immutably reduce the given `...objs` into one object. The reduction is
14 * applied from left to right, so `immutableUpdate({ a: 1 }, { a: 2 })` will
15 * result in `{ a: 2 }`. The resulting object is frozen.
17 * Example usage:
19 * const original = { foo: 1, bar: 2, baz: 3 };
20 * const modified = immutableUpdate(original, { baz: 0, bang: 4 });
22 * // We get the new object that we expect...
23 * assert(modified.baz === 0);
24 * assert(modified.bang === 4);
26 * // However, the original is not modified.
27 * assert(original.baz === 2);
28 * assert(original.bang === undefined);
30 * @param {...Object} ...objs
31 * @returns {Object}
33 exports.immutableUpdate = function (...objs) {
34 return Object.freeze(Object.assign({}, ...objs));
37 /**
38 * Utility function for updating an object with the properties of
39 * other objects.
41 * DEPRECATED: Just use Object.assign() instead!
43 * @param aTarget Object
44 * The object being updated.
45 * @param aNewAttrs Object
46 * The rest params are objects to update aTarget with. You
47 * can pass as many as you like.
49 exports.update = function update(target, ...args) {
50 for (const attrs of args) {
51 for (const key in attrs) {
52 const desc = Object.getOwnPropertyDescriptor(attrs, key);
54 if (desc) {
55 Object.defineProperty(target, key, desc);
59 return target;
62 /**
63 * Utility function for getting the values from an object as an array
65 * @param object Object
66 * The object to iterate over
68 exports.values = function values(object) {
69 return Object.keys(object).map(k => object[k]);
72 /**
73 * Report that |who| threw an exception, |exception|.
75 exports.reportException = function reportException(who, exception) {
76 const msg = `${who} threw an exception: ${exports.safeErrorString(
77 exception
78 )}`;
79 dump(msg + "\n");
81 if (typeof console !== "undefined" && console && console.error) {
82 console.error(exception);
86 /**
87 * Given a handler function that may throw, return an infallible handler
88 * function that calls the fallible handler, and logs any exceptions it
89 * throws.
91 * @param handler function
92 * A handler function, which may throw.
93 * @param aName string
94 * A name for handler, for use in error messages. If omitted, we use
95 * handler.name.
97 * (SpiderMonkey does generate good names for anonymous functions, but we
98 * don't have a way to get at them from JavaScript at the moment.)
100 exports.makeInfallible = function (handler, name = handler.name) {
101 return function () {
102 try {
103 return handler.apply(this, arguments);
104 } catch (ex) {
105 let who = "Handler function";
106 if (name) {
107 who += " " + name;
109 exports.reportException(who, ex);
110 return undefined;
116 * Turn the |error| into a string, without fail.
118 * @param {Error|any} error
120 exports.safeErrorString = function (error) {
121 try {
122 let errorString = error.toString();
123 if (typeof errorString == "string") {
124 // Attempt to attach a stack to |errorString|. If it throws an error, or
125 // isn't a string, don't use it.
126 try {
127 if (error.stack) {
128 const stack = error.stack.toString();
129 if (typeof stack == "string") {
130 errorString += "\nStack: " + stack;
133 } catch (ee) {
134 // Ignore.
137 // Append additional line and column number information to the output,
138 // since it might not be part of the stringified error.
139 if (
140 typeof error.lineNumber == "number" &&
141 typeof error.columnNumber == "number"
143 errorString +=
144 "Line: " + error.lineNumber + ", column: " + error.columnNumber;
147 return errorString;
149 } catch (ee) {
150 // Ignore.
153 // We failed to find a good error description, so do the next best thing.
154 return Object.prototype.toString.call(error);
158 * Interleaves two arrays element by element, returning the combined array, like
159 * a zip. In the case of arrays with different sizes, undefined values will be
160 * interleaved at the end along with the extra values of the larger array.
162 * @param Array a
163 * @param Array b
164 * @returns Array
165 * The combined array, in the form [a1, b1, a2, b2, ...]
167 exports.zip = function (a, b) {
168 if (!b) {
169 return a;
171 if (!a) {
172 return b;
174 const pairs = [];
175 for (
176 let i = 0, aLength = a.length, bLength = b.length;
177 i < aLength || i < bLength;
180 pairs.push([a[i], b[i]]);
182 return pairs;
186 * Converts an object into an array with 2-element arrays as key/value
187 * pairs of the object. `{ foo: 1, bar: 2}` would become
188 * `[[foo, 1], [bar 2]]` (order not guaranteed).
190 * @param object obj
191 * @returns array
193 exports.entries = function entries(obj) {
194 return Object.keys(obj).map(k => [k, obj[k]]);
198 * Takes an array of 2-element arrays as key/values pairs and
199 * constructs an object using them.
201 exports.toObject = function (arr) {
202 const obj = {};
203 for (const [k, v] of arr) {
204 obj[k] = v;
206 return obj;
210 * Composes the given functions into a single function, which will
211 * apply the results of each function right-to-left, starting with
212 * applying the given arguments to the right-most function.
213 * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)))`
215 * @param ...function funcs
216 * @returns function
218 exports.compose = function compose(...funcs) {
219 return (...args) => {
220 const initialValue = funcs[funcs.length - 1](...args);
221 const leftFuncs = funcs.slice(0, -1);
222 return leftFuncs.reduceRight((composed, f) => f(composed), initialValue);
227 * Return true if `thing` is a generator function, false otherwise.
229 exports.isGenerator = function (fn) {
230 if (typeof fn !== "function") {
231 return false;
233 const proto = Object.getPrototypeOf(fn);
234 if (!proto) {
235 return false;
237 const ctor = proto.constructor;
238 if (!ctor) {
239 return false;
241 return ctor.name == "GeneratorFunction";
245 * Return true if `thing` is an async function, false otherwise.
247 exports.isAsyncFunction = function (fn) {
248 if (typeof fn !== "function") {
249 return false;
251 const proto = Object.getPrototypeOf(fn);
252 if (!proto) {
253 return false;
255 const ctor = proto.constructor;
256 if (!ctor) {
257 return false;
259 return ctor.name == "AsyncFunction";
263 * Return true if `thing` is a Promise or then-able, false otherwise.
265 exports.isPromise = function (p) {
266 return p && typeof p.then === "function";
270 * Return true if `thing` is a SavedFrame, false otherwise.
272 exports.isSavedFrame = function (thing) {
273 return Object.prototype.toString.call(thing) === "[object SavedFrame]";
277 * Return true iff `thing` is a `Set` object (possibly from another global).
279 exports.isSet = function (thing) {
280 return Object.prototype.toString.call(thing) === "[object Set]";
284 * Given a list of lists, flatten it. Only flattens one level; does not
285 * recursively flatten all levels.
287 * @param {Array<Array<Any>>} lists
288 * @return {Array<Any>}
290 exports.flatten = function (lists) {
291 return Array.prototype.concat.apply([], lists);
295 * Returns a promise that is resolved or rejected when all promises have settled
296 * (resolved or rejected).
298 * This differs from Promise.all, which will reject immediately after the first
299 * rejection, instead of waiting for the remaining promises to settle.
301 * @param values
302 * Iterable of promises that may be pending, resolved, or rejected. When
303 * when all promises have settled (resolved or rejected), the returned
304 * promise will be resolved or rejected as well.
306 * @return A new promise that is fulfilled when all values have settled
307 * (resolved or rejected). Its resolution value will be an array of all
308 * resolved values in the given order, or undefined if values is an
309 * empty array. The reject reason will be forwarded from the first
310 * promise in the list of given promises to be rejected.
312 exports.settleAll = values => {
313 if (values === null || typeof values[Symbol.iterator] != "function") {
314 throw new Error("settleAll() expects an iterable.");
317 return new Promise((resolve, reject) => {
318 values = Array.isArray(values) ? values : [...values];
319 let countdown = values.length;
320 const resolutionValues = new Array(countdown);
321 let rejectionValue;
322 let rejectionOccurred = false;
324 if (!countdown) {
325 resolve(resolutionValues);
326 return;
329 function checkForCompletion() {
330 if (--countdown > 0) {
331 return;
333 if (!rejectionOccurred) {
334 resolve(resolutionValues);
335 } else {
336 reject(rejectionValue);
340 for (let i = 0; i < values.length; i++) {
341 const index = i;
342 const value = values[i];
343 const resolver = result => {
344 resolutionValues[index] = result;
345 checkForCompletion();
347 const rejecter = error => {
348 if (!rejectionOccurred) {
349 rejectionValue = error;
350 rejectionOccurred = true;
352 checkForCompletion();
355 if (value && typeof value.then == "function") {
356 value.then(resolver, rejecter);
357 } else {
358 // Given value is not a promise, forward it as a resolution value.
359 resolver(value);