tor-browser

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

object.js (14258B)


      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 { objectSpec } = require("resource://devtools/shared/specs/object.js");
      8 const {
      9  FrontClassWithSpec,
     10  registerFront,
     11 } = require("resource://devtools/shared/protocol.js");
     12 const {
     13  LongStringFront,
     14 } = require("resource://devtools/client/fronts/string.js");
     15 
     16 const SUPPORT_ENUM_ENTRIES_SET = new Set([
     17  "CustomStateSet",
     18  "FormData",
     19  "Headers",
     20  "HighlightRegistry",
     21  "Map",
     22  "MIDIInputMap",
     23  "MIDIOutputMap",
     24  "Set",
     25  "Storage",
     26  "URLSearchParams",
     27  "WeakMap",
     28  "WeakSet",
     29 ]);
     30 
     31 /**
     32 * A ObjectFront is used as a front end for the ObjectActor that is
     33 * created on the server, hiding implementation details.
     34 */
     35 class ObjectFront extends FrontClassWithSpec(objectSpec) {
     36  constructor(conn = null, targetFront = null, parentFront = null, data) {
     37    if (!parentFront) {
     38      throw new Error("ObjectFront require a parent front");
     39    }
     40 
     41    super(conn, targetFront, parentFront);
     42 
     43    this._grip = data;
     44    this.actorID = this._grip.actor;
     45    this.valid = true;
     46 
     47    parentFront.manage(this);
     48  }
     49 
     50  form(data) {
     51    this.actorID = data.actor;
     52    this._grip = data;
     53  }
     54 
     55  skipDestroy() {
     56    // Object fronts are simple fronts, they don't need to be cleaned up on
     57    // toolbox destroy. `conn` is a DebuggerClient instance, check the
     58    // `isToolboxDestroy` flag to skip the destroy.
     59    return this.conn && this.conn.isToolboxDestroy;
     60  }
     61 
     62  getGrip() {
     63    return this._grip;
     64  }
     65 
     66  get isFrozen() {
     67    return this._grip.frozen;
     68  }
     69 
     70  get isSealed() {
     71    return this._grip.sealed;
     72  }
     73 
     74  get isExtensible() {
     75    return this._grip.extensible;
     76  }
     77 
     78  /**
     79   * Request the prototype and own properties of the object.
     80   */
     81  async getPrototypeAndProperties() {
     82    const result = await super.prototypeAndProperties();
     83 
     84    if (result.prototype) {
     85      result.prototype = getAdHocFrontOrPrimitiveGrip(result.prototype, this);
     86    }
     87 
     88    // The result packet can have multiple properties that hold grips which we may need
     89    // to turn into fronts.
     90    const gripKeys = ["value", "getterValue", "get", "set"];
     91 
     92    if (result.ownProperties) {
     93      Object.entries(result.ownProperties).forEach(([key, descriptor]) => {
     94        if (descriptor) {
     95          for (const gripKey of gripKeys) {
     96            if (descriptor.hasOwnProperty(gripKey)) {
     97              result.ownProperties[key][gripKey] = getAdHocFrontOrPrimitiveGrip(
     98                descriptor[gripKey],
     99                this
    100              );
    101            }
    102          }
    103        }
    104      });
    105    }
    106 
    107    if (result.safeGetterValues) {
    108      Object.entries(result.safeGetterValues).forEach(([key, descriptor]) => {
    109        if (descriptor) {
    110          for (const gripKey of gripKeys) {
    111            if (descriptor.hasOwnProperty(gripKey)) {
    112              result.safeGetterValues[key][gripKey] =
    113                getAdHocFrontOrPrimitiveGrip(descriptor[gripKey], this);
    114            }
    115          }
    116        }
    117      });
    118    }
    119 
    120    if (result.ownSymbols) {
    121      result.ownSymbols.forEach((descriptor, i, arr) => {
    122        if (descriptor) {
    123          for (const gripKey of gripKeys) {
    124            if (descriptor.hasOwnProperty(gripKey)) {
    125              arr[i][gripKey] = getAdHocFrontOrPrimitiveGrip(
    126                descriptor[gripKey],
    127                this
    128              );
    129            }
    130          }
    131        }
    132      });
    133    }
    134 
    135    return result;
    136  }
    137 
    138  /**
    139   * Request a PropertyIteratorFront instance to ease listing
    140   * properties for this object.
    141   *
    142   * @param options Object
    143   *        A dictionary object with various boolean attributes:
    144   *        - ignoreIndexedProperties Boolean
    145   *          If true, filters out Array items.
    146   *          e.g. properties names between `0` and `object.length`.
    147   *        - ignoreNonIndexedProperties Boolean
    148   *          If true, filters out items that aren't array items
    149   *          e.g. properties names that are not a number between `0`
    150   *          and `object.length`.
    151   *        - sort Boolean
    152   *          If true, the iterator will sort the properties by name
    153   *          before dispatching them.
    154   */
    155  enumProperties(options) {
    156    return super.enumProperties(options);
    157  }
    158 
    159  /**
    160   * Request a PropertyIteratorFront instance to enumerate entries in a
    161   * Map/Set-like object.
    162   */
    163  enumEntries() {
    164    if (!SUPPORT_ENUM_ENTRIES_SET.has(this._grip.class)) {
    165      console.error(
    166        `enumEntries can't be called for "${
    167          this._grip.class
    168        }" grips. Supported grips are: ${[...SUPPORT_ENUM_ENTRIES_SET].join(
    169          ", "
    170        )}.`
    171      );
    172      return null;
    173    }
    174    return super.enumEntries();
    175  }
    176 
    177  /**
    178   * Request a SymbolIteratorFront instance to enumerate symbols in an object.
    179   */
    180  enumSymbols() {
    181    if (this._grip.type !== "object") {
    182      console.error("enumSymbols is only valid for objects grips.");
    183      return null;
    184    }
    185    return super.enumSymbols();
    186  }
    187 
    188  /**
    189   * Request the property descriptor of the object's specified property.
    190   *
    191   * @param name string The name of the requested property.
    192   */
    193  getProperty(name) {
    194    return super.property(name);
    195  }
    196 
    197  /**
    198   * Request the value of the object's specified property.
    199   *
    200   * @param name string The name of the requested property.
    201   * @param receiverId string|null The actorId of the receiver to be used for getters.
    202   */
    203  async getPropertyValue(name, receiverId) {
    204    const response = await super.propertyValue(name, receiverId);
    205 
    206    if (response.value) {
    207      const { value } = response;
    208      if (value.return) {
    209        response.value.return = getAdHocFrontOrPrimitiveGrip(
    210          value.return,
    211          this
    212        );
    213      }
    214 
    215      if (value.throw) {
    216        response.value.throw = getAdHocFrontOrPrimitiveGrip(value.throw, this);
    217      }
    218    }
    219    return response;
    220  }
    221 
    222  /**
    223   * Get the body of a custom formatted object.
    224   */
    225  async customFormatterBody() {
    226    const result = await super.customFormatterBody();
    227 
    228    if (!result?.customFormatterBody) {
    229      return result;
    230    }
    231 
    232    const createFrontsInJsonMl = item => {
    233      if (Array.isArray(item)) {
    234        return item.map(i => createFrontsInJsonMl(i));
    235      }
    236      return getAdHocFrontOrPrimitiveGrip(item, this);
    237    };
    238 
    239    result.customFormatterBody = createFrontsInJsonMl(
    240      result.customFormatterBody
    241    );
    242 
    243    return result;
    244  }
    245 
    246  /**
    247   * Request the prototype of the object.
    248   */
    249  async getPrototype() {
    250    const result = await super.prototype();
    251 
    252    if (!result.prototype) {
    253      return result;
    254    }
    255 
    256    result.prototype = getAdHocFrontOrPrimitiveGrip(result.prototype, this);
    257 
    258    return result;
    259  }
    260 
    261  /**
    262   * Request the state of a promise.
    263   */
    264  async getPromiseState() {
    265    if (this._grip.class !== "Promise") {
    266      console.error("getPromiseState is only valid for promise grips.");
    267      return null;
    268    }
    269 
    270    let response, promiseState;
    271    try {
    272      response = await super.promiseState();
    273      promiseState = response.promiseState;
    274    } catch (error) {
    275      // @backward-compat { version 85 } On older server, the promiseState request didn't
    276      // didn't exist (bug 1552648). The promise state was directly included in the grip.
    277      if (error.message.includes("unrecognizedPacketType")) {
    278        promiseState = this._grip.promiseState;
    279        response = { promiseState };
    280      } else {
    281        throw error;
    282      }
    283    }
    284 
    285    const { value, reason } = promiseState;
    286 
    287    if (value) {
    288      promiseState.value = getAdHocFrontOrPrimitiveGrip(value, this);
    289    }
    290 
    291    if (reason) {
    292      promiseState.reason = getAdHocFrontOrPrimitiveGrip(reason, this);
    293    }
    294 
    295    return response;
    296  }
    297 
    298  /**
    299   * Request the target and handler internal slots of a proxy.
    300   */
    301  async getProxySlots() {
    302    if (this._grip.class !== "Proxy") {
    303      console.error("getProxySlots is only valid for proxy grips.");
    304      return null;
    305    }
    306 
    307    const response = await super.proxySlots();
    308    const { proxyHandler, proxyTarget } = response;
    309 
    310    if (proxyHandler) {
    311      response.proxyHandler = getAdHocFrontOrPrimitiveGrip(proxyHandler, this);
    312    }
    313 
    314    if (proxyTarget) {
    315      response.proxyTarget = getAdHocFrontOrPrimitiveGrip(proxyTarget, this);
    316    }
    317 
    318    return response;
    319  }
    320 
    321  get isSyntaxError() {
    322    return this._grip.preview && this._grip.preview.name == "SyntaxError";
    323  }
    324 }
    325 
    326 /**
    327 * When we are asking the server for the value of a given variable, we might get different
    328 * type of objects:
    329 * - a primitive (string, number, null, false, boolean)
    330 * - a long string
    331 * - an "object" (i.e. not primitive nor long string)
    332 *
    333 * Each of those type need a different front, or none:
    334 * - a primitive does not allow further interaction with the server, so we don't need
    335 *   to have a dedicated front.
    336 * - a long string needs a longStringFront to be able to retrieve the full string.
    337 * - an object need an objectFront to retrieve properties, symbols and prototype.
    338 *
    339 * In the case an ObjectFront is created, we also check if the object has properties
    340 * that should be turned into fronts as well.
    341 *
    342 * @param {string | number | object} options: The packet returned by the server.
    343 * @param {Front} parentFront
    344 *
    345 * @returns {number | string | object | LongStringFront | ObjectFront}
    346 */
    347 function getAdHocFrontOrPrimitiveGrip(packet, parentFront) {
    348  // We only want to try to create a front when it makes sense, i.e when it has an
    349  // actorID, unless:
    350  // - it's a Symbol (See Bug 1600299)
    351  // - it's a mapEntry (the preview.key and preview.value properties can hold actors)
    352  // - it's a highlightRegistryEntry (the preview.value properties can hold actors)
    353  // - or it is already a front (happens when we are using the legacy listeners in the ResourceCommand)
    354  const isPacketAnObject = packet && typeof packet === "object";
    355  const isFront = !!packet?.typeName;
    356  if (
    357    !isPacketAnObject ||
    358    packet.type == "symbol" ||
    359    (packet.type !== "mapEntry" &&
    360      packet.type !== "highlightRegistryEntry" &&
    361      !packet.actor) ||
    362    isFront
    363  ) {
    364    return packet;
    365  }
    366 
    367  const { conn } = parentFront;
    368  // If the parent front is a target, consider it as the target to use for all objects
    369  const targetFront = parentFront.isTargetFront
    370    ? parentFront
    371    : parentFront.targetFront;
    372 
    373  // We may have already created a front for this object actor since some actor (e.g. the
    374  // thread actor) cache the object actors they create.
    375  const existingFront = conn.getFrontByID(packet.actor);
    376  if (existingFront) {
    377    // This methods replicates Protocol.js logic when we receive an actor "form" (here `packet`):
    378    // https://searchfox.org/mozilla-central/rev/aecbd5cdd28a09e11872bc829d9e6e4b943e6e49/devtools/shared/protocol/types.js#346
    379    // We notify the Object Front about the new "form" so that it can update itself
    380    // with latest data provided by the server.
    381    // This will help ensure that the object previews get updated.
    382    existingFront.form(packet);
    383 
    384    // The `packet` may contain nested actor forms which should be converted into Fronts.
    385    createChildFronts(existingFront, packet);
    386 
    387    return existingFront;
    388  }
    389 
    390  const { type } = packet;
    391 
    392  if (type === "longString") {
    393    const longStringFront = new LongStringFront(conn, targetFront, parentFront);
    394    longStringFront.form(packet);
    395    parentFront.manage(longStringFront);
    396    return longStringFront;
    397  }
    398 
    399  if (
    400    (type === "mapEntry" || type === "highlightRegistryEntry") &&
    401    packet.preview
    402  ) {
    403    const { key, value } = packet.preview;
    404    packet.preview.key = getAdHocFrontOrPrimitiveGrip(
    405      key,
    406      parentFront,
    407      targetFront
    408    );
    409    packet.preview.value = getAdHocFrontOrPrimitiveGrip(
    410      value,
    411      parentFront,
    412      targetFront
    413    );
    414    return packet;
    415  }
    416 
    417  const objectFront = new ObjectFront(conn, targetFront, parentFront, packet);
    418  createChildFronts(objectFront, packet);
    419  return objectFront;
    420 }
    421 
    422 /**
    423 * Create child fronts of the passed object front given a packet. Those child fronts are
    424 * usually mapping actors of the packet sub-properties (preview items, promise fullfilled
    425 * values, …).
    426 *
    427 * @param {ObjectFront} objectFront
    428 * @param {string | number | object} packet: The packet returned by the server
    429 */
    430 function createChildFronts(objectFront, packet) {
    431  if (packet.preview) {
    432    const { message, entries } = packet.preview;
    433 
    434    // The message could be a longString.
    435    if (packet.preview.message) {
    436      packet.preview.message = getAdHocFrontOrPrimitiveGrip(
    437        message,
    438        objectFront
    439      );
    440    }
    441 
    442    // Handle Map/WeakMap preview entries (the preview might be directly used if has all the
    443    // items needed, i.e. if the Map has less than 10 items).
    444    if (entries && Array.isArray(entries)) {
    445      packet.preview.entries = entries.map(([key, value]) => [
    446        getAdHocFrontOrPrimitiveGrip(key, objectFront),
    447        getAdHocFrontOrPrimitiveGrip(value, objectFront),
    448      ]);
    449    }
    450  }
    451 
    452  if (packet && typeof packet.ownProperties === "object") {
    453    for (const [name, descriptor] of Object.entries(packet.ownProperties)) {
    454      // The descriptor can have multiple properties that hold grips which we may need
    455      // to turn into fronts.
    456      const gripKeys = ["value", "getterValue", "get", "set"];
    457      for (const key of gripKeys) {
    458        if (
    459          descriptor &&
    460          typeof descriptor === "object" &&
    461          descriptor.hasOwnProperty(key)
    462        ) {
    463          packet.ownProperties[name][key] = getAdHocFrontOrPrimitiveGrip(
    464            descriptor[key],
    465            objectFront
    466          );
    467        }
    468      }
    469    }
    470  }
    471 
    472  // Handle custom formatters
    473  if (packet && packet.useCustomFormatter && Array.isArray(packet.header)) {
    474    const createFrontsInJsonMl = item => {
    475      if (Array.isArray(item)) {
    476        return item.map(i => createFrontsInJsonMl(i));
    477      }
    478      return getAdHocFrontOrPrimitiveGrip(item, objectFront);
    479    };
    480 
    481    packet.header = createFrontsInJsonMl(packet.header);
    482  }
    483 }
    484 
    485 registerFront(ObjectFront);
    486 
    487 exports.ObjectFront = ObjectFront;
    488 exports.getAdHocFrontOrPrimitiveGrip = getAdHocFrontOrPrimitiveGrip;