tor-browser

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

utils.js (14636B)


      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 const {
      8  DevToolsServer,
      9 } = require("resource://devtools/server/devtools-server.js");
     10 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     11 const { assert } = DevToolsUtils;
     12 
     13 loader.lazyRequireGetter(
     14  this,
     15  "LongStringActor",
     16  "resource://devtools/server/actors/string.js",
     17  true
     18 );
     19 
     20 loader.lazyRequireGetter(
     21  this,
     22  "symbolGrip",
     23  "resource://devtools/server/actors/object/symbol.js",
     24  true
     25 );
     26 
     27 /**
     28 * Get thisDebugger.Object referent's `promiseState`.
     29 *
     30 * @returns Object
     31 *          An object of one of the following forms:
     32 *          - { state: "pending" }
     33 *          - { state: "fulfilled", value }
     34 *          - { state: "rejected", reason }
     35 */
     36 function getPromiseState(obj) {
     37  if (obj.class != "Promise") {
     38    throw new Error(
     39      "Can't call `getPromiseState` on `Debugger.Object`s that don't " +
     40        "refer to Promise objects."
     41    );
     42  }
     43 
     44  const state = { state: obj.promiseState };
     45  if (state.state === "fulfilled") {
     46    state.value = obj.promiseValue;
     47  } else if (state.state === "rejected") {
     48    state.reason = obj.promiseReason;
     49  }
     50  return state;
     51 }
     52 
     53 /**
     54 * Returns true if value is an object or function.
     55 *
     56 * @param value
     57 * @returns {boolean}
     58 */
     59 
     60 function isObjectOrFunction(value) {
     61  // Handle null, whose typeof is object
     62  if (!value) {
     63    return false;
     64  }
     65 
     66  const type = typeof value;
     67  return type == "object" || type == "function";
     68 }
     69 
     70 /**
     71 * Make a debuggee value for the given object, if needed. Primitive values
     72 * are left the same.
     73 *
     74 * Use case: you have a raw JS object (after unsafe dereference) and you want to
     75 * send it to the client. In that case you need to use an ObjectActor which
     76 * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
     77 * method works only for JS objects and functions.
     78 *
     79 * @param Debugger.Object obj
     80 * @param any value
     81 * @return object
     82 */
     83 function makeDebuggeeValueIfNeeded(obj, value) {
     84  if (isObjectOrFunction(value)) {
     85    return obj.makeDebuggeeValue(value);
     86  }
     87  return value;
     88 }
     89 
     90 /**
     91 * Convert a debuggee value into the underlying raw object, if needed.
     92 */
     93 function unwrapDebuggeeValue(value) {
     94  if (value && typeof value == "object") {
     95    return value.unsafeDereference();
     96  }
     97  return value;
     98 }
     99 
    100 /**
    101 * Create a grip for the given debuggee value. If the value is an object or a long string,
    102 * it will create an actor and add it to the pool
    103 *
    104 * @param {ThreadActor} threadActor 
    105 *        The related Thread Actor.
    106 * @param {any} value
    107 *        The debuggee value.
    108 * @param {Pool} pool
    109 *        The pool where the created actor will be added to.
    110 * @param {number} [depth]
    111 *        The current depth within the chain of nested object actor being previewed.
    112 * @param {object} [objectActorAttributes]
    113 *        An optional object whose properties will be assigned to the ObjectActor if one
    114 *        is created.
    115 */
    116 function createValueGrip(threadActor, value, pool, depth = 0, objectActorAttributes = {}) {
    117  switch (typeof value) {
    118    case "boolean":
    119      return value;
    120 
    121    case "string":
    122      return createStringGrip(pool, value);
    123 
    124    case "number":
    125      if (value === Infinity) {
    126        return { type: "Infinity" };
    127      } else if (value === -Infinity) {
    128        return { type: "-Infinity" };
    129      } else if (Number.isNaN(value)) {
    130        return { type: "NaN" };
    131      } else if (!value && 1 / value === -Infinity) {
    132        return { type: "-0" };
    133      }
    134      return value;
    135 
    136    case "bigint":
    137      return createBigIntValueGrip(value);
    138 
    139    // TODO(bug 1772157)
    140    // Record/tuple grips aren't fully implemented yet.
    141    case "record":
    142      return {
    143        class: "Record",
    144      };
    145    case "tuple":
    146      return {
    147        class: "Tuple",
    148      };
    149    case "undefined":
    150      return { type: "undefined" };
    151 
    152    case "object":
    153      if (value === null) {
    154        return { type: "null" };
    155      } else if (
    156        value.optimizedOut ||
    157        value.uninitialized ||
    158        value.missingArguments
    159      ) {
    160        // The slot is optimized out, an uninitialized binding, or
    161        // arguments on a dead scope
    162        return {
    163          type: "null",
    164          optimizedOut: value.optimizedOut,
    165          uninitialized: value.uninitialized,
    166          missingArguments: value.missingArguments,
    167        };
    168      }
    169      return pool.createObjectGrip(
    170        value,
    171        depth,
    172        objectActorAttributes,
    173      );
    174 
    175    case "symbol":
    176      return symbolGrip(threadActor, value, pool);
    177 
    178    default:
    179      assert(false, "Failed to provide a grip for: " + value);
    180      return null;
    181  }
    182 }
    183 
    184 /**
    185 * Returns a grip for the passed BigInt
    186 *
    187 * @param {bigint} value
    188 * @returns {object}
    189 */
    190 function createBigIntValueGrip(value) {
    191  return {
    192    type: "BigInt",
    193    text: value.toString(),
    194  };
    195 }
    196 
    197 /**
    198 * of passing the value directly over the protocol.
    199 *
    200 * @param str String
    201 *        The string we are checking the length of.
    202 */
    203 function stringIsLong(str) {
    204  return str.length >= DevToolsServer.LONG_STRING_LENGTH;
    205 }
    206 
    207 const TYPED_ARRAY_CLASSES = [
    208  "Uint8Array",
    209  "Uint8ClampedArray",
    210  "Uint16Array",
    211  "Uint32Array",
    212  "Int8Array",
    213  "Int16Array",
    214  "Int32Array",
    215  "Float32Array",
    216  "Float64Array",
    217  "BigInt64Array",
    218  "BigUint64Array",
    219 ];
    220 
    221 /**
    222 * Returns true if a debuggee object is a typed array.
    223 *
    224 * @param obj Debugger.Object
    225 *        The debuggee object to test.
    226 * @return Boolean
    227 */
    228 function isTypedArray(object) {
    229  return TYPED_ARRAY_CLASSES.includes(object.class);
    230 }
    231 
    232 /**
    233 * Returns true if a debuggee object is an array, including a typed array.
    234 *
    235 * @param obj Debugger.Object
    236 *        The debuggee object to test.
    237 * @return Boolean
    238 */
    239 function isArray(object) {
    240  return isTypedArray(object) || object.class === "Array";
    241 }
    242 
    243 /**
    244 * Returns the length of an array (or typed array).
    245 *
    246 * @param object Debugger.Object
    247 *        The debuggee object of the array.
    248 * @return Number
    249 * @throws if the object is not an array.
    250 */
    251 function getArrayLength(object) {
    252  if (!isArray(object)) {
    253    throw new Error("Expected an array, got a " + object.class);
    254  }
    255 
    256  // Real arrays have a reliable `length` own property.
    257  if (object.class === "Array") {
    258    return DevToolsUtils.getProperty(object, "length");
    259  }
    260 
    261  // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
    262  // getter could be shadowed by an own property, and `getOwnPropertyNames` is
    263  // unnecessarily slow. Obtain the `length` getter safely and call it manually.
    264  const typedProto = Object.getPrototypeOf(Uint8Array.prototype);
    265  const getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
    266  return getter.call(object.unsafeDereference());
    267 }
    268 
    269 /**
    270 * Returns true if the parameter is suitable to be an array index.
    271 *
    272 * @param str String
    273 * @return Boolean
    274 */
    275 function isArrayIndex(str) {
    276  // Transform the parameter to a 32-bit unsigned integer.
    277  const num = str >>> 0;
    278  // Check that the parameter is a canonical Uint32 index.
    279  return (
    280    num + "" === str &&
    281    // Array indices cannot attain the maximum Uint32 value.
    282    num != -1 >>> 0
    283  );
    284 }
    285 
    286 /**
    287 * Returns true if a debuggee object is a local or sessionStorage object.
    288 *
    289 * @param object Debugger.Object
    290 *        The debuggee object to test.
    291 * @return Boolean
    292 */
    293 function isStorage(object) {
    294  return object.class === "Storage";
    295 }
    296 
    297 /**
    298 * Returns the length of a local or sessionStorage object.
    299 *
    300 * @param object Debugger.Object
    301 *        The debuggee object of the array.
    302 * @return Number
    303 * @throws if the object is not a local or sessionStorage object.
    304 */
    305 function getStorageLength(object) {
    306  if (!isStorage(object)) {
    307    throw new Error("Expected a storage object, got a " + object.class);
    308  }
    309  return DevToolsUtils.getProperty(object, "length");
    310 }
    311 
    312 /**
    313 * Returns an array of properties based on event class name.
    314 *
    315 * @param className
    316 * @returns {Array}
    317 */
    318 function getPropsForEvent(className) {
    319  const positionProps = ["buttons", "clientX", "clientY", "layerX", "layerY"];
    320  const eventToPropsMap = {
    321    MouseEvent: positionProps,
    322    DragEvent: positionProps,
    323    PointerEvent: positionProps,
    324    SimpleGestureEvent: positionProps,
    325    WheelEvent: positionProps,
    326    KeyboardEvent: ["key", "charCode", "keyCode"],
    327    TransitionEvent: ["propertyName", "pseudoElement"],
    328    AnimationEvent: ["animationName", "pseudoElement"],
    329    ClipboardEvent: ["clipboardData"],
    330  };
    331 
    332  if (className in eventToPropsMap) {
    333    return eventToPropsMap[className];
    334  }
    335 
    336  return [];
    337 }
    338 
    339 /**
    340 * Returns an array of of all properties of an object
    341 *
    342 * @param obj
    343 * @param rawObj
    344 * @returns {Array|Iterable} If rawObj is localStorage/sessionStorage, we don't return an
    345 *          array but an iterable object (with the proper `length` property) to avoid
    346 *          performance issues.
    347 */
    348 function getPropNamesFromObject(obj, rawObj) {
    349  try {
    350    if (isStorage(obj)) {
    351      // local and session storage cannot be iterated over using
    352      // Object.getOwnPropertyNames() because it skips keys that are duplicated
    353      // on the prototype e.g. "key", "getKeys" so we need to gather the real
    354      // keys using the storage.key() function.
    355      // As the method is pretty slow, we return an iterator here, so we don't consume
    356      // more than we need, especially since we're calling this from previewers in which
    357      // we only need the first 10 entries for the preview (See Bug 1741804).
    358 
    359      // Still return the proper number of entries.
    360      const length = rawObj.length;
    361      const iterable = { length };
    362      iterable[Symbol.iterator] = function*() {
    363        for (let j = 0; j < length; j++) {
    364          yield rawObj.key(j);
    365        }
    366      };
    367      return iterable;
    368    }
    369 
    370    return obj.getOwnPropertyNames();
    371  } catch (ex) {
    372    // Calling getOwnPropertyNames() on some wrapped native prototypes is not
    373    // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
    374  }
    375 
    376  return [];
    377 }
    378 
    379 /**
    380 * Returns an array of private properties of an object
    381 *
    382 * @param obj
    383 * @returns {Array}
    384 */
    385 function getSafePrivatePropertiesSymbols(obj) {
    386  try {
    387    return obj.getOwnPrivateProperties();
    388  } catch (ex) {
    389    return [];
    390  }
    391 }
    392 
    393 /**
    394 * Returns an array of all symbol properties of an object
    395 *
    396 * @param obj
    397 * @returns {Array}
    398 */
    399 function getSafeOwnPropertySymbols(obj) {
    400  try {
    401    return obj.getOwnPropertySymbols();
    402  } catch (ex) {
    403    return [];
    404  }
    405 }
    406 
    407 /**
    408 * Returns an array modifiers based on keys
    409 *
    410 * @param rawObj
    411 * @returns {Array}
    412 */
    413 function getModifiersForEvent(rawObj) {
    414  const modifiers = [];
    415  const keysToModifiersMap = {
    416    altKey: "Alt",
    417    ctrlKey: "Control",
    418    metaKey: "Meta",
    419    shiftKey: "Shift",
    420  };
    421 
    422  for (const key in keysToModifiersMap) {
    423    if (keysToModifiersMap.hasOwnProperty(key) && rawObj[key]) {
    424      modifiers.push(keysToModifiersMap[key]);
    425    }
    426  }
    427 
    428  return modifiers;
    429 }
    430 
    431 /**
    432 * Make a debuggee value for the given value.
    433 *
    434 * @param TargetActor targetActor
    435 *        The Target Actor from which this object originates.
    436 * @param mixed value
    437 *        The value you want to get a debuggee value for.
    438 * @return object
    439 *         Debuggee value for |value|.
    440 */
    441 function makeDebuggeeValue(targetActor, value) {
    442  // Primitive types are debuggee values and Debugger.Object.makeDebuggeeValue
    443  // would return them unchanged. So avoid the expense of:
    444  // getGlobalForObject+makeGlobalObjectReference+makeDebugeeValue for them.
    445  //
    446  // It is actually easier to identify non primitive which can only be object or function.
    447  if (!isObjectOrFunction(value)) {
    448    return value;
    449  }
    450 
    451  // `value` may come from various globals.
    452  // And Debugger.Object.makeDebuggeeValue only works for objects
    453  // related to the same global. So fetch the global first,
    454  // in order to instantiate a Debugger.Object for it.
    455  //
    456  // In the worker thread, we don't have access to Cu,
    457  // but at the same time, there is only one global, the worker one.
    458  const valueGlobal = isWorker ? targetActor.targetGlobal : Cu.getGlobalForObject(value);
    459  let dbgGlobal;
    460  try {
    461    dbgGlobal = targetActor.dbg.makeGlobalObjectReference(
    462      valueGlobal
    463    );
    464  } catch(e) {
    465    // makeGlobalObjectReference will throw if the global is invisible to Debugger,
    466    // in this case instantiate a Debugger.Object for the top level global
    467    // of the target. Even if value will come from another global, it will "work",
    468    // but the Debugger.Object created via dbgGlobal.makeDebuggeeValue will throw
    469    // on most methods as the object will also be invisible to Debuggee...
    470    if (e.message.includes("object in compartment marked as invisible to Debugger")) {
    471      dbgGlobal = targetActor.dbg.makeGlobalObjectReference(
    472        targetActor.window 
    473      );
    474 
    475    } else {
    476      throw e;
    477    }
    478  }
    479 
    480  return dbgGlobal.makeDebuggeeValue(value);
    481 }
    482 
    483 /**
    484 * Create a grip for the given string.
    485 *
    486 * @param TargetActor targetActor
    487 *        The Target Actor from which this object originates.
    488 */
    489 function createStringGrip(targetActor, string) {
    490  if (string && stringIsLong(string)) {
    491    const actor = new LongStringActor(targetActor.conn, string);
    492    targetActor.manage(actor);
    493    return actor.form();
    494  }
    495  return string;
    496 }
    497 
    498 /**
    499 * Create a grip for the given value.
    500 *
    501 * @param TargetActor targetActor
    502 *        The Target Actor from which this object originates.
    503 * @param mixed value
    504 *        The value you want to get a debuggee value for.
    505 * @param Number depth
    506 *        Depth of the object compared to the top level object,
    507 *        when we are inspecting nested attributes.
    508 * @param Object [objectActorAttributes]
    509 *        An optional object whose properties will be assigned to the ObjectActor if one
    510 *        is created.
    511 * @return object
    512 */
    513 function createValueGripForTarget(
    514  targetActor,
    515  value,
    516  depth = 0,
    517  objectActorAttributes = {}
    518 ) {
    519  return createValueGrip(targetActor.threadActor, value, targetActor.objectsPool, depth, objectActorAttributes);
    520 }
    521 
    522 module.exports = {
    523  getPromiseState,
    524  makeDebuggeeValueIfNeeded,
    525  unwrapDebuggeeValue,
    526  createBigIntValueGrip,
    527  createValueGrip,
    528  stringIsLong,
    529  isTypedArray,
    530  isArray,
    531  isStorage,
    532  getArrayLength,
    533  getStorageLength,
    534  isArrayIndex,
    535  getPropsForEvent,
    536  getPropNamesFromObject,
    537  getSafeOwnPropertySymbols,
    538  getSafePrivatePropertiesSymbols,
    539  getModifiersForEvent,
    540  isObjectOrFunction,
    541  createStringGrip,
    542  makeDebuggeeValue,
    543  createValueGripForTarget,
    544 };