tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ThreadSafeDevToolsUtils.js (9812B)


      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 
      5 "use strict";
      6 
      7 /**
      8 * General utilities used throughout devtools that can also be used in
      9 * workers.
     10 */
     11 
     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.
     16 *
     17 * Example usage:
     18 *
     19 *     const original = { foo: 1, bar: 2, baz: 3 };
     20 *     const modified = immutableUpdate(original, { baz: 0, bang: 4 });
     21 *
     22 *     // We get the new object that we expect...
     23 *     assert(modified.baz === 0);
     24 *     assert(modified.bang === 4);
     25 *
     26 *     // However, the original is not modified.
     27 *     assert(original.baz === 2);
     28 *     assert(original.bang === undefined);
     29 *
     30 * @param {...object} ...objs
     31 * @returns {object}
     32 */
     33 exports.immutableUpdate = function (...objs) {
     34  return Object.freeze(Object.assign({}, ...objs));
     35 };
     36 
     37 /**
     38 * Utility function for updating an object with the properties of
     39 * other objects.
     40 *
     41 * DEPRECATED: Just use Object.assign() instead!
     42 *
     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.
     48 */
     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);
     53 
     54      if (desc) {
     55        Object.defineProperty(target, key, desc);
     56      }
     57    }
     58  }
     59  return target;
     60 };
     61 
     62 /**
     63 * Utility function for getting the values from an object as an array
     64 *
     65 * @param object Object
     66 *        The object to iterate over
     67 */
     68 exports.values = function values(object) {
     69  return Object.keys(object).map(k => object[k]);
     70 };
     71 
     72 /**
     73 * Report that |who| threw an exception, |exception|.
     74 */
     75 exports.reportException = function reportException(who, exception) {
     76  const msg = `${who} threw an exception: ${exports.safeErrorString(
     77    exception
     78  )}`;
     79  dump(msg + "\n");
     80 
     81  if (typeof console !== "undefined" && console && console.error) {
     82    console.error(exception);
     83  }
     84 };
     85 
     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.
     90 *
     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.
     96 *
     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.)
     99 */
    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;
    108      }
    109      exports.reportException(who, ex);
    110      return undefined;
    111    }
    112  };
    113 };
    114 
    115 /**
    116 * Turn the |error| into a string, without fail.
    117 *
    118 * @param {Error|any} error
    119 */
    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;
    131          }
    132        }
    133      } catch (ee) {
    134        // Ignore.
    135      }
    136 
    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"
    142      ) {
    143        errorString +=
    144          "Line: " + error.lineNumber + ", column: " + error.columnNumber;
    145      }
    146 
    147      return errorString;
    148    }
    149  } catch (ee) {
    150    // Ignore.
    151  }
    152 
    153  // We failed to find a good error description, so do the next best thing.
    154  return Object.prototype.toString.call(error);
    155 };
    156 
    157 /**
    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.
    161 *
    162 * @param Array a
    163 * @param Array b
    164 * @returns Array
    165 *          The combined array, in the form [a1, b1, a2, b2, ...]
    166 */
    167 exports.zip = function (a, b) {
    168  if (!b) {
    169    return a;
    170  }
    171  if (!a) {
    172    return b;
    173  }
    174  const pairs = [];
    175  for (
    176    let i = 0, aLength = a.length, bLength = b.length;
    177    i < aLength || i < bLength;
    178    i++
    179  ) {
    180    pairs.push([a[i], b[i]]);
    181  }
    182  return pairs;
    183 };
    184 
    185 /**
    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).
    189 *
    190 * @param object obj
    191 * @returns array
    192 */
    193 exports.entries = function entries(obj) {
    194  return Object.keys(obj).map(k => [k, obj[k]]);
    195 };
    196 
    197 /*
    198 * Takes an array of 2-element arrays as key/values pairs and
    199 * constructs an object using them.
    200 */
    201 exports.toObject = function (arr) {
    202  const obj = {};
    203  for (const [k, v] of arr) {
    204    obj[k] = v;
    205  }
    206  return obj;
    207 };
    208 
    209 /**
    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)))`
    214 *
    215 * @param ...function funcs
    216 * @returns function
    217 */
    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);
    223  };
    224 };
    225 
    226 /**
    227 * Return true if `thing` is a generator function, false otherwise.
    228 */
    229 exports.isGenerator = function (fn) {
    230  if (typeof fn !== "function") {
    231    return false;
    232  }
    233  const proto = Object.getPrototypeOf(fn);
    234  if (!proto) {
    235    return false;
    236  }
    237  const ctor = proto.constructor;
    238  if (!ctor) {
    239    return false;
    240  }
    241  return ctor.name == "GeneratorFunction";
    242 };
    243 
    244 /**
    245 * Return true if `thing` is an async function, false otherwise.
    246 */
    247 exports.isAsyncFunction = function (fn) {
    248  if (typeof fn !== "function") {
    249    return false;
    250  }
    251  const proto = Object.getPrototypeOf(fn);
    252  if (!proto) {
    253    return false;
    254  }
    255  const ctor = proto.constructor;
    256  if (!ctor) {
    257    return false;
    258  }
    259  return ctor.name == "AsyncFunction";
    260 };
    261 
    262 /**
    263 * Return true if `thing` is a Promise or then-able, false otherwise.
    264 */
    265 exports.isPromise = function (p) {
    266  return p && typeof p.then === "function";
    267 };
    268 
    269 /**
    270 * Return true if `thing` is a SavedFrame, false otherwise.
    271 */
    272 exports.isSavedFrame = function (thing) {
    273  return Object.prototype.toString.call(thing) === "[object SavedFrame]";
    274 };
    275 
    276 /**
    277 * Return true iff `thing` is a `Set` object (possibly from another global).
    278 */
    279 exports.isSet = function (thing) {
    280  return Object.prototype.toString.call(thing) === "[object Set]";
    281 };
    282 
    283 /**
    284 * Given a list of lists, flatten it. Only flattens one level; does not
    285 * recursively flatten all levels.
    286 *
    287 * @param {Array<Array<Any>>} lists
    288 * @return {Array<Any>}
    289 */
    290 exports.flatten = function (lists) {
    291  return Array.prototype.concat.apply([], lists);
    292 };
    293 
    294 /**
    295 * Returns a promise that is resolved or rejected when all promises have settled
    296 * (resolved or rejected).
    297 *
    298 * This differs from Promise.all, which will reject immediately after the first
    299 * rejection, instead of waiting for the remaining promises to settle.
    300 *
    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.
    305 *
    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.
    311 */
    312 exports.settleAll = values => {
    313  if (values === null || typeof values[Symbol.iterator] != "function") {
    314    throw new Error("settleAll() expects an iterable.");
    315  }
    316 
    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;
    323 
    324    if (!countdown) {
    325      resolve(resolutionValues);
    326      return;
    327    }
    328 
    329    function checkForCompletion() {
    330      if (--countdown > 0) {
    331        return;
    332      }
    333      if (!rejectionOccurred) {
    334        resolve(resolutionValues);
    335      } else {
    336        reject(rejectionValue);
    337      }
    338    }
    339 
    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();
    346      };
    347      const rejecter = error => {
    348        if (!rejectionOccurred) {
    349          rejectionValue = error;
    350          rejectionOccurred = true;
    351        }
    352        checkForCompletion();
    353      };
    354 
    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);
    360      }
    361    }
    362  });
    363 };