ThreadSafeDevToolsUtils.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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/. */
  4. "use strict";
  5. /**
  6. * General utilities used throughout devtools that can also be used in
  7. * workers.
  8. */
  9. /**
  10. * Immutably reduce the given `...objs` into one object. The reduction is
  11. * applied from left to right, so `immutableUpdate({ a: 1 }, { a: 2 })` will
  12. * result in `{ a: 2 }`. The resulting object is frozen.
  13. *
  14. * Example usage:
  15. *
  16. * const original = { foo: 1, bar: 2, baz: 3 };
  17. * const modified = immutableUpdate(original, { baz: 0, bang: 4 });
  18. *
  19. * // We get the new object that we expect...
  20. * assert(modified.baz === 0);
  21. * assert(modified.bang === 4);
  22. *
  23. * // However, the original is not modified.
  24. * assert(original.baz === 2);
  25. * assert(original.bang === undefined);
  26. *
  27. * @param {...Object} ...objs
  28. * @returns {Object}
  29. */
  30. exports.immutableUpdate = function (...objs) {
  31. return Object.freeze(Object.assign({}, ...objs));
  32. };
  33. /**
  34. * Utility function for updating an object with the properties of
  35. * other objects.
  36. *
  37. * DEPRECATED: Just use Object.assign() instead!
  38. *
  39. * @param aTarget Object
  40. * The object being updated.
  41. * @param aNewAttrs Object
  42. * The rest params are objects to update aTarget with. You
  43. * can pass as many as you like.
  44. */
  45. exports.update = function update(target, ...args) {
  46. for (let attrs of args) {
  47. for (let key in attrs) {
  48. let desc = Object.getOwnPropertyDescriptor(attrs, key);
  49. if (desc) {
  50. Object.defineProperty(target, key, desc);
  51. }
  52. }
  53. }
  54. return target;
  55. };
  56. /**
  57. * Utility function for getting the values from an object as an array
  58. *
  59. * @param object Object
  60. * The object to iterate over
  61. */
  62. exports.values = function values(object) {
  63. return Object.keys(object).map(k => object[k]);
  64. };
  65. /**
  66. * Report that |who| threw an exception, |exception|.
  67. */
  68. exports.reportException = function reportException(who, exception) {
  69. const msg = `${who} threw an exception: ${exports.safeErrorString(exception)}`;
  70. dump(msg + "\n");
  71. if (typeof console !== "undefined" && console && console.error) {
  72. console.error(msg);
  73. }
  74. };
  75. /**
  76. * Given a handler function that may throw, return an infallible handler
  77. * function that calls the fallible handler, and logs any exceptions it
  78. * throws.
  79. *
  80. * @param handler function
  81. * A handler function, which may throw.
  82. * @param aName string
  83. * A name for handler, for use in error messages. If omitted, we use
  84. * handler.name.
  85. *
  86. * (SpiderMonkey does generate good names for anonymous functions, but we
  87. * don't have a way to get at them from JavaScript at the moment.)
  88. */
  89. exports.makeInfallible = function (handler, name = handler.name) {
  90. return function (/* arguments */) {
  91. try {
  92. return handler.apply(this, arguments);
  93. } catch (ex) {
  94. let who = "Handler function";
  95. if (name) {
  96. who += " " + name;
  97. }
  98. exports.reportException(who, ex);
  99. return undefined;
  100. }
  101. };
  102. };
  103. /**
  104. * Turn the |error| into a string, without fail.
  105. *
  106. * @param {Error|any} error
  107. */
  108. exports.safeErrorString = function (error) {
  109. try {
  110. let errorString = error.toString();
  111. if (typeof errorString == "string") {
  112. // Attempt to attach a stack to |errorString|. If it throws an error, or
  113. // isn't a string, don't use it.
  114. try {
  115. if (error.stack) {
  116. let stack = error.stack.toString();
  117. if (typeof stack == "string") {
  118. errorString += "\nStack: " + stack;
  119. }
  120. }
  121. } catch (ee) { }
  122. // Append additional line and column number information to the output,
  123. // since it might not be part of the stringified error.
  124. if (typeof error.lineNumber == "number" && typeof error.columnNumber == "number") {
  125. errorString += "Line: " + error.lineNumber + ", column: " + error.columnNumber;
  126. }
  127. return errorString;
  128. }
  129. } catch (ee) { }
  130. // We failed to find a good error description, so do the next best thing.
  131. return Object.prototype.toString.call(error);
  132. };
  133. /**
  134. * Interleaves two arrays element by element, returning the combined array, like
  135. * a zip. In the case of arrays with different sizes, undefined values will be
  136. * interleaved at the end along with the extra values of the larger array.
  137. *
  138. * @param Array a
  139. * @param Array b
  140. * @returns Array
  141. * The combined array, in the form [a1, b1, a2, b2, ...]
  142. */
  143. exports.zip = function (a, b) {
  144. if (!b) {
  145. return a;
  146. }
  147. if (!a) {
  148. return b;
  149. }
  150. const pairs = [];
  151. for (let i = 0, aLength = a.length, bLength = b.length;
  152. i < aLength || i < bLength;
  153. i++) {
  154. pairs.push([a[i], b[i]]);
  155. }
  156. return pairs;
  157. };
  158. /**
  159. * Converts an object into an array with 2-element arrays as key/value
  160. * pairs of the object. `{ foo: 1, bar: 2}` would become
  161. * `[[foo, 1], [bar 2]]` (order not guaranteed).
  162. *
  163. * @param object obj
  164. * @returns array
  165. */
  166. exports.entries = function entries(obj) {
  167. return Object.keys(obj).map(k => [k, obj[k]]);
  168. };
  169. /*
  170. * Takes an array of 2-element arrays as key/values pairs and
  171. * constructs an object using them.
  172. */
  173. exports.toObject = function (arr) {
  174. const obj = {};
  175. for (let [k, v] of arr) {
  176. obj[k] = v;
  177. }
  178. return obj;
  179. };
  180. /**
  181. * Composes the given functions into a single function, which will
  182. * apply the results of each function right-to-left, starting with
  183. * applying the given arguments to the right-most function.
  184. * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)))`
  185. *
  186. * @param ...function funcs
  187. * @returns function
  188. */
  189. exports.compose = function compose(...funcs) {
  190. return (...args) => {
  191. const initialValue = funcs[funcs.length - 1](...args);
  192. const leftFuncs = funcs.slice(0, -1);
  193. return leftFuncs.reduceRight((composed, f) => f(composed),
  194. initialValue);
  195. };
  196. };
  197. /**
  198. * Return true if `thing` is a generator function, false otherwise.
  199. */
  200. exports.isGenerator = function (fn) {
  201. if (typeof fn !== "function") {
  202. return false;
  203. }
  204. let proto = Object.getPrototypeOf(fn);
  205. if (!proto) {
  206. return false;
  207. }
  208. let ctor = proto.constructor;
  209. if (!ctor) {
  210. return false;
  211. }
  212. return ctor.name == "GeneratorFunction";
  213. };
  214. /**
  215. * Return true if `thing` is a Promise or then-able, false otherwise.
  216. */
  217. exports.isPromise = function (p) {
  218. return p && typeof p.then === "function";
  219. };
  220. /**
  221. * Return true if `thing` is a SavedFrame, false otherwise.
  222. */
  223. exports.isSavedFrame = function (thing) {
  224. return Object.prototype.toString.call(thing) === "[object SavedFrame]";
  225. };
  226. /**
  227. * Return true iff `thing` is a `Set` object (possibly from another global).
  228. */
  229. exports.isSet = function (thing) {
  230. return Object.prototype.toString.call(thing) === "[object Set]";
  231. };
  232. /**
  233. * Given a list of lists, flatten it. Only flattens one level; does not
  234. * recursively flatten all levels.
  235. *
  236. * @param {Array<Array<Any>>} lists
  237. * @return {Array<Any>}
  238. */
  239. exports.flatten = function (lists) {
  240. return Array.prototype.concat.apply([], lists);
  241. };
  242. /**
  243. * Returns a promise that is resolved or rejected when all promises have settled
  244. * (resolved or rejected).
  245. *
  246. * This differs from Promise.all, which will reject immediately after the first
  247. * rejection, instead of waiting for the remaining promises to settle.
  248. *
  249. * @param values
  250. * Iterable of promises that may be pending, resolved, or rejected. When
  251. * when all promises have settled (resolved or rejected), the returned
  252. * promise will be resolved or rejected as well.
  253. *
  254. * @return A new promise that is fulfilled when all values have settled
  255. * (resolved or rejected). Its resolution value will be an array of all
  256. * resolved values in the given order, or undefined if values is an
  257. * empty array. The reject reason will be forwarded from the first
  258. * promise in the list of given promises to be rejected.
  259. */
  260. exports.settleAll = values => {
  261. if (values === null || typeof (values[Symbol.iterator]) != "function") {
  262. throw new Error("settleAll() expects an iterable.");
  263. }
  264. return new Promise((resolve, reject) => {
  265. values = Array.isArray(values) ? values : [...values];
  266. let countdown = values.length;
  267. let resolutionValues = new Array(countdown);
  268. let rejectionValue;
  269. let rejectionOccurred = false;
  270. if (!countdown) {
  271. resolve(resolutionValues);
  272. return deferred.promise;
  273. }
  274. function checkForCompletion() {
  275. if (--countdown > 0) {
  276. return;
  277. }
  278. if (!rejectionOccurred) {
  279. resolve(resolutionValues);
  280. } else {
  281. reject(rejectionValue);
  282. }
  283. }
  284. for (let i = 0; i < values.length; i++) {
  285. let index = i;
  286. let value = values[i];
  287. let resolver = result => {
  288. resolutionValues[index] = result;
  289. checkForCompletion();
  290. };
  291. let rejecter = error => {
  292. if (!rejectionOccurred) {
  293. rejectionValue = error;
  294. rejectionOccurred = true;
  295. }
  296. checkForCompletion();
  297. };
  298. if (value && typeof (value.then) == "function") {
  299. value.then(resolver, rejecter);
  300. } else {
  301. // Given value is not a promise, forward it as a resolution value.
  302. resolver(value);
  303. }
  304. }
  305. });
  306. };