tor-browser

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

object.js (23962B)


      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 { Actor } = require("resource://devtools/shared/protocol/Actor.js");
      8 const { objectSpec } = require("resource://devtools/shared/specs/object.js");
      9 
     10 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     11 const { assert } = DevToolsUtils;
     12 
     13 loader.lazyRequireGetter(
     14  this,
     15  "propertyDescriptor",
     16  "resource://devtools/server/actors/object/property-descriptor.js",
     17  true
     18 );
     19 loader.lazyRequireGetter(
     20  this,
     21  "PropertyIteratorActor",
     22  "resource://devtools/server/actors/object/property-iterator.js",
     23  true
     24 );
     25 loader.lazyRequireGetter(
     26  this,
     27  "SymbolIteratorActor",
     28  "resource://devtools/server/actors/object/symbol-iterator.js",
     29  true
     30 );
     31 loader.lazyRequireGetter(
     32  this,
     33  "PrivatePropertiesIteratorActor",
     34  "resource://devtools/server/actors/object/private-properties-iterator.js",
     35  true
     36 );
     37 loader.lazyRequireGetter(
     38  this,
     39  "previewers",
     40  "resource://devtools/server/actors/object/previewers.js"
     41 );
     42 
     43 loader.lazyRequireGetter(
     44  this,
     45  ["customFormatterHeader", "customFormatterBody"],
     46  "resource://devtools/server/actors/utils/custom-formatters.js",
     47  true
     48 );
     49 
     50 // This is going to be used by findSafeGetters, where we want to avoid calling getters for
     51 // deprecated properties (otherwise a warning message is displayed in the console).
     52 // We could do something like EagerEvaluation, where we create a new Sandbox which is then
     53 // used to compare functions, but, we'd need to make new classes available in
     54 // the Sandbox, and possibly do it again when a new property gets deprecated.
     55 // Since this is only to be able to automatically call getters, we can simply check against
     56 // a list of unsafe getters that we generate from webidls.
     57 loader.lazyRequireGetter(
     58  this,
     59  "unsafeGettersNames",
     60  "resource://devtools/server/actors/webconsole/webidl-unsafe-getters-names.js"
     61 );
     62 
     63 // ContentDOMReference requires ChromeUtils, which isn't available in worker context.
     64 const lazy = {};
     65 if (!isWorker) {
     66  loader.lazyGetter(
     67    lazy,
     68    "ContentDOMReference",
     69    () =>
     70      ChromeUtils.importESModule(
     71        "resource://gre/modules/ContentDOMReference.sys.mjs",
     72        // ContentDOMReference needs to be retrieved from the shared global
     73        // since it is a shared singleton.
     74        { global: "shared" }
     75      ).ContentDOMReference
     76  );
     77 }
     78 
     79 const {
     80  getArrayLength,
     81  getPromiseState,
     82  getStorageLength,
     83  isArray,
     84  isStorage,
     85  isTypedArray,
     86  createValueGrip,
     87 } = require("resource://devtools/server/actors/object/utils.js");
     88 
     89 class ObjectActor extends Actor {
     90  /**
     91   * Creates an actor for the specified object.
     92   *
     93   * @param ThreadActor threadActor
     94   *        The current thread actor from where this object is running from.
     95   * @param Debugger.Object obj
     96   *        The debuggee object.
     97   * @param Object
     98   *        A collection of abstract methods that are implemented by the caller.
     99   *        ObjectActor requires the following functions to be implemented by
    100   *        the caller:
    101   *        - {Number} customFormatterObjectTagDepth: See `processObjectTag`
    102   *        - {Debugger.Object} customFormatterConfigDbgObj
    103   *        - {bool} allowSideEffect: allow side effectful operations while
    104   *                                  constructing a preview
    105   */
    106  constructor(
    107    threadActor,
    108    obj,
    109    {
    110      customFormatterObjectTagDepth,
    111      customFormatterConfigDbgObj,
    112      allowSideEffect = true,
    113    }
    114  ) {
    115    super(threadActor.conn, objectSpec);
    116 
    117    assert(
    118      !obj.optimizedOut,
    119      "Should not create object actors for optimized out values!"
    120    );
    121 
    122    this.obj = obj;
    123    this.targetActor = threadActor.targetActor;
    124    this.threadActor = threadActor;
    125    this.rawObj = obj.unsafeDereference();
    126    this.safeRawObj = this.#getSafeRawObject();
    127    this.allowSideEffect = allowSideEffect;
    128 
    129    // Cache obj.class as it can be costly when queried from previewers if this is in a hot path
    130    // (e.g. logging objects within a for loops).
    131    this.className = this.obj.class;
    132 
    133    this.hooks = {
    134      customFormatterObjectTagDepth,
    135      customFormatterConfigDbgObj,
    136    };
    137  }
    138 
    139  addWatchpoint(property, label, watchpointType) {
    140    this.threadActor.addWatchpoint(this, { property, label, watchpointType });
    141  }
    142 
    143  removeWatchpoint(property) {
    144    this.threadActor.removeWatchpoint(this, property);
    145  }
    146 
    147  removeWatchpoints() {
    148    this.threadActor.removeWatchpoint(this);
    149  }
    150 
    151  createValueGrip(value, depth) {
    152    if (typeof depth != "number") {
    153      throw new Error("Missing 'depth' argument to createValeuGrip()");
    154    }
    155    return createValueGrip(
    156      this.threadActor,
    157      value,
    158      // Register any nested object actor in the same pool as their parent object actor
    159      this.getParent(),
    160      depth,
    161      this.hooks
    162    );
    163  }
    164 
    165  /**
    166   * Returns a grip for this actor for returning in a protocol message.
    167   *
    168   * @param {number} depth
    169   *                 Current depth in the generated preview object sent to the client.
    170   */
    171  form({ depth = 0 } = {}) {
    172    const g = {
    173      type: "object",
    174      actor: this.actorID,
    175    };
    176 
    177    const unwrapped = DevToolsUtils.unwrap(this.obj);
    178    if (unwrapped === undefined) {
    179      // Objects belonging to an invisible-to-debugger compartment might be proxies,
    180      // so just in case they shouldn't be accessed.
    181      g.class = "InvisibleToDebugger: " + this.className;
    182      return g;
    183    }
    184 
    185    // Only process custom formatters if the feature is enabled.
    186    if (this.threadActor?.targetActor?.customFormatters) {
    187      const result = customFormatterHeader(this);
    188      if (result) {
    189        const { formatter, ...header } = result;
    190        this._customFormatterItem = formatter;
    191 
    192        return {
    193          ...g,
    194          ...header,
    195        };
    196      }
    197    }
    198 
    199    if (unwrapped?.isProxy) {
    200      // Proxy objects can run traps when accessed, so just create a preview with
    201      // the target and the handler.
    202      g.class = "Proxy";
    203      previewers.Proxy[0](this, g, depth + 1);
    204      return g;
    205    }
    206 
    207    const ownPropertyLength = this._getOwnPropertyLength();
    208 
    209    Object.assign(g, {
    210      // If the debuggee does not subsume the object's compartment, most properties won't
    211      // be accessible. Cross-orgin Window and Location objects might expose some, though.
    212      // Change the displayed class, but when creating the preview use the original one.
    213      class: unwrapped === null ? "Restricted" : this.className,
    214      ownPropertyLength: Number.isFinite(ownPropertyLength)
    215        ? ownPropertyLength
    216        : undefined,
    217      extensible: this.obj.isExtensible(),
    218      frozen: this.obj.isFrozen(),
    219      sealed: this.obj.isSealed(),
    220      isError: this.obj.isError,
    221    });
    222 
    223    if (g.class == "Function") {
    224      g.isClassConstructor = this.obj.isClassConstructor;
    225    }
    226 
    227    this._populateGripPreview(g, depth + 1);
    228 
    229    if (
    230      this.safeRawObj &&
    231      Node.isInstance(this.safeRawObj) &&
    232      lazy.ContentDOMReference
    233    ) {
    234      // ContentDOMReference.get takes a DOM element and returns an object with
    235      // its browsing context id, as well as a unique identifier. We are putting it in
    236      // the grip here in order to be able to retrieve the node later, potentially from a
    237      // different DevToolsServer running in the same process.
    238      // If ContentDOMReference.get throws, we simply don't add the property to the grip.
    239      try {
    240        g.contentDomReference = lazy.ContentDOMReference.get(this.safeRawObj);
    241      } catch (e) {}
    242    }
    243 
    244    return g;
    245  }
    246 
    247  customFormatterBody() {
    248    return customFormatterBody(this, this._customFormatterItem);
    249  }
    250 
    251  _getOwnPropertyLength() {
    252    if (isTypedArray(this.obj)) {
    253      // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays
    254      return getArrayLength(this.obj);
    255    }
    256 
    257    if (isStorage(this.obj)) {
    258      return getStorageLength(this.obj);
    259    }
    260 
    261    try {
    262      return this.obj.getOwnPropertyNamesLength();
    263    } catch (err) {
    264      // The above can throw when the debuggee does not subsume the object's
    265      // compartment, or for some WrappedNatives like Cu.Sandbox.
    266    }
    267 
    268    return null;
    269  }
    270 
    271  #getSafeRawObject() {
    272    let raw = this.rawObj;
    273 
    274    // If Cu is not defined, we are running on a worker thread, where xrays
    275    // don't exist.
    276    if (raw && Cu) {
    277      raw = Cu.unwaiveXrays(raw);
    278    }
    279 
    280    if (raw && !DevToolsUtils.isSafeJSObject(raw)) {
    281      raw = null;
    282    }
    283 
    284    return raw;
    285  }
    286 
    287  /**
    288   * Populate the `preview` property on `grip` given its type.
    289   *
    290   * @param {object} grip
    291   *                 Object onto which preview data attribute should be added.
    292   * @param {number} depth
    293   *                 Current depth in the generated preview object sent to the client.
    294   */
    295  _populateGripPreview(grip, depth) {
    296    for (const previewer of previewers[this.className] || previewers.Object) {
    297      try {
    298        const previewerResult = previewer(this, grip, depth);
    299        if (previewerResult) {
    300          return;
    301        }
    302      } catch (e) {
    303        const msg =
    304          "ObjectActor.prototype._populateGripPreview previewer function";
    305        DevToolsUtils.reportException(msg, e);
    306      }
    307    }
    308  }
    309 
    310  /**
    311   * Returns an object exposing the internal Promise state.
    312   */
    313  promiseState() {
    314    const { state, value, reason } = getPromiseState(this.obj);
    315    const promiseState = { state };
    316 
    317    if (state == "fulfilled") {
    318      promiseState.value = this.createValueGrip(value, 0);
    319    } else if (state == "rejected") {
    320      promiseState.reason = this.createValueGrip(reason, 0);
    321    }
    322 
    323    promiseState.creationTimestamp = Date.now() - this.obj.promiseLifetime;
    324 
    325    // Only add the timeToSettle property if the Promise isn't pending.
    326    if (state !== "pending") {
    327      promiseState.timeToSettle = this.obj.promiseTimeToResolution;
    328    }
    329 
    330    return { promiseState };
    331  }
    332 
    333  /**
    334   * Creates an actor to iterate over an object property names and values.
    335   * See PropertyIteratorActor constructor for more info about options param.
    336   *
    337   * @param options object
    338   */
    339  enumProperties(options) {
    340    return new PropertyIteratorActor(this, options, this.conn);
    341  }
    342 
    343  /**
    344   * Creates an actor to iterate over entries of a Map/Set-like object.
    345   */
    346  enumEntries() {
    347    return new PropertyIteratorActor(this, { enumEntries: true }, this.conn);
    348  }
    349 
    350  /**
    351   * Creates an actor to iterate over an object symbols properties.
    352   */
    353  enumSymbols() {
    354    return new SymbolIteratorActor(this, this.conn);
    355  }
    356 
    357  /**
    358   * Creates an actor to iterate over an object private properties.
    359   */
    360  enumPrivateProperties() {
    361    return new PrivatePropertiesIteratorActor(this, this.conn);
    362  }
    363 
    364  /**
    365   * Handle a protocol request to provide the prototype and own properties of
    366   * the object.
    367   *
    368   * @returns {object} An object containing the data of this.obj, of the following form:
    369   *          - {Object} prototype: The descriptor of this.obj's prototype.
    370   *          - {Object} ownProperties: an object where the keys are the names of the
    371   *                     this.obj's ownProperties, and the values the descriptors of
    372   *                     the properties.
    373   *          - {Array} ownSymbols: An array containing all descriptors of this.obj's
    374   *                    ownSymbols. Here we have an array, and not an object like for
    375   *                    ownProperties, because we can have multiple symbols with the same
    376   *                    name in this.obj, e.g. `{[Symbol()]: "a", [Symbol()]: "b"}`.
    377   *          - {Object} safeGetterValues: an object that maps this.obj's property names
    378   *                     with safe getters descriptors.
    379   */
    380  prototypeAndProperties() {
    381    let objProto = null;
    382    let names = [];
    383    let symbols = [];
    384    if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
    385      try {
    386        objProto = this.obj.proto;
    387        names = this.obj.getOwnPropertyNames();
    388        symbols = this.obj.getOwnPropertySymbols();
    389      } catch (err) {
    390        // The above can throw when the debuggee does not subsume the object's
    391        // compartment, or for some WrappedNatives like Cu.Sandbox.
    392      }
    393    }
    394 
    395    const ownProperties = Object.create(null);
    396    const ownSymbols = [];
    397 
    398    for (const name of names) {
    399      ownProperties[name] = propertyDescriptor(this, name, 0);
    400    }
    401 
    402    for (const sym of symbols) {
    403      ownSymbols.push({
    404        name: sym.toString(),
    405        descriptor: propertyDescriptor(this, sym, 0),
    406      });
    407    }
    408 
    409    return {
    410      prototype: this.createValueGrip(objProto, 0),
    411      ownProperties,
    412      ownSymbols,
    413      safeGetterValues: this._findSafeGetterValues(names, 0),
    414    };
    415  }
    416 
    417  /**
    418   * Find the safe getter values for the current Debugger.Object, |this.obj|.
    419   *
    420   * @private
    421   * @param array ownProperties
    422   *        The array that holds the list of known ownProperties names for
    423   *        |this.obj|.
    424   * @param {number} depth
    425   *                 Current depth in the generated preview object sent to the client.
    426   * @param number [limit=Infinity]
    427   *        Optional limit of getter values to find.
    428   * @return object
    429   *         An object that maps property names to safe getter descriptors as
    430   *         defined by the remote debugging protocol.
    431   */
    432  _findSafeGetterValues(ownProperties, depth, limit = Infinity) {
    433    const safeGetterValues = Object.create(null);
    434    let obj = this.obj;
    435    let level = 0,
    436      currentGetterValuesCount = 0;
    437 
    438    // Do not search safe getters in unsafe objects.
    439    if (!DevToolsUtils.isSafeDebuggerObject(obj)) {
    440      return safeGetterValues;
    441    }
    442 
    443    // Most objects don't have any safe getters but inherit some from their
    444    // prototype. Avoid calling getOwnPropertyNames on objects that may have
    445    // many properties like Array, strings or js objects. That to avoid
    446    // freezing firefox when doing so.
    447    if (
    448      this.className == "Object" ||
    449      this.className == "String" ||
    450      isArray(this.obj)
    451    ) {
    452      obj = obj.proto;
    453      level++;
    454    }
    455 
    456    while (obj && DevToolsUtils.isSafeDebuggerObject(obj)) {
    457      for (const name of this._findSafeGetters(obj)) {
    458        // Avoid overwriting properties from prototypes closer to this.obj. Also
    459        // avoid providing safeGetterValues from prototypes if property |name|
    460        // is already defined as an own property.
    461        if (
    462          name in safeGetterValues ||
    463          (obj != this.obj && ownProperties.includes(name))
    464        ) {
    465          continue;
    466        }
    467 
    468        // Ignore __proto__ on Object.prototye.
    469        if (!obj.proto && name == "__proto__") {
    470          continue;
    471        }
    472 
    473        const desc = safeGetOwnPropertyDescriptor(obj, name);
    474        if (!desc?.get) {
    475          // If no getter matches the name, the cache is stale and should be cleaned up.
    476          obj._safeGetters = null;
    477          continue;
    478        }
    479 
    480        const getterValue = this._evaluateGetter(desc.get);
    481        if (getterValue === this._evaluateGetterNoResult) {
    482          continue;
    483        }
    484 
    485        // Treat an already-rejected Promise as we would a thrown exception
    486        // by not including it as a safe getter value (see Bug 1477765).
    487        if (isRejectedPromise(getterValue)) {
    488          // Until we have a good way to handle Promise rejections through the
    489          // debugger API (Bug 1478076), call `catch` when it's safe to do so.
    490          const raw = getterValue.unsafeDereference();
    491          if (DevToolsUtils.isSafeJSObject(raw)) {
    492            raw.catch(e => e);
    493          }
    494          continue;
    495        }
    496 
    497        // WebIDL attributes specified with the LenientThis extended attribute
    498        // return undefined and should be ignored.
    499        safeGetterValues[name] = {
    500          getterValue: this.createValueGrip(getterValue, depth),
    501          getterPrototypeLevel: level,
    502          enumerable: desc.enumerable,
    503          writable: level == 0 ? desc.writable : true,
    504        };
    505 
    506        ++currentGetterValuesCount;
    507        if (currentGetterValuesCount == limit) {
    508          return safeGetterValues;
    509        }
    510      }
    511 
    512      obj = obj.proto;
    513      level++;
    514    }
    515 
    516    return safeGetterValues;
    517  }
    518 
    519  _evaluateGetterNoResult = Symbol();
    520 
    521  /**
    522   * Evaluate the getter function |desc.get|.
    523   *
    524   * @param {object} getter
    525   */
    526  _evaluateGetter(getter) {
    527    const result = getter.call(this.obj);
    528    if (!result || "throw" in result) {
    529      return this._evaluateGetterNoResult;
    530    }
    531 
    532    let getterValue = this._evaluateGetterNoResult;
    533    if ("return" in result) {
    534      getterValue = result.return;
    535    } else if ("yield" in result) {
    536      getterValue = result.yield;
    537    }
    538 
    539    return getterValue;
    540  }
    541 
    542  /**
    543   * Find the safe getters for a given Debugger.Object. Safe getters are native
    544   * getters which are safe to execute.
    545   *
    546   * @private
    547   * @param Debugger.Object object
    548   *        The Debugger.Object where you want to find safe getters.
    549   * @return Set
    550   *         A Set of names of safe getters. This result is cached for each
    551   *         Debugger.Object.
    552   */
    553  _findSafeGetters(object) {
    554    if (object._safeGetters) {
    555      return object._safeGetters;
    556    }
    557 
    558    const getters = new Set();
    559 
    560    if (!DevToolsUtils.isSafeDebuggerObject(object)) {
    561      object._safeGetters = getters;
    562      return getters;
    563    }
    564 
    565    let names = [];
    566    try {
    567      names = object.getOwnPropertyNames();
    568    } catch (ex) {
    569      // Calling getOwnPropertyNames() on some wrapped native prototypes is not
    570      // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
    571    }
    572 
    573    for (const name of names) {
    574      let desc = null;
    575      try {
    576        desc = object.getOwnPropertyDescriptor(name);
    577      } catch (e) {
    578        // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
    579        // allowed (bug 560072).
    580      }
    581      if (!desc || desc.value !== undefined || !("get" in desc)) {
    582        continue;
    583      }
    584 
    585      if (
    586        DevToolsUtils.hasSafeGetter(desc) &&
    587        !unsafeGettersNames.includes(name)
    588      ) {
    589        getters.add(name);
    590      }
    591    }
    592 
    593    object._safeGetters = getters;
    594    return getters;
    595  }
    596 
    597  /**
    598   * Handle a protocol request to provide the prototype of the object.
    599   */
    600  prototype() {
    601    let objProto = null;
    602    if (DevToolsUtils.isSafeDebuggerObject(this.obj)) {
    603      objProto = this.obj.proto;
    604    }
    605    return { prototype: this.createValueGrip(objProto, 0) };
    606  }
    607 
    608  /**
    609   * Handle a protocol request to provide the property descriptor of the
    610   * object's specified property.
    611   *
    612   * @param name string
    613   *        The property we want the description of.
    614   */
    615  property(name) {
    616    if (!name) {
    617      return this.throwError(
    618        "missingParameter",
    619        "no property name was specified"
    620      );
    621    }
    622 
    623    return { descriptor: propertyDescriptor(this, name, 0) };
    624  }
    625 
    626  /**
    627   * Handle a protocol request to provide the value of the object's
    628   * specified property.
    629   *
    630   * Note: Since this will evaluate getters, it can trigger execution of
    631   * content code and may cause side effects. This endpoint should only be used
    632   * when you are confident that the side-effects will be safe, or the user
    633   * is expecting the effects.
    634   *
    635   * @param {string} name
    636   *        The property we want the value of.
    637   * @param {string|null} receiverId
    638   *        The actorId of the receiver to be used if the property is a getter.
    639   *        If null or invalid, the receiver will be the referent.
    640   */
    641  propertyValue(name, receiverId) {
    642    if (!name) {
    643      return this.throwError(
    644        "missingParameter",
    645        "no property name was specified"
    646      );
    647    }
    648 
    649    let receiver;
    650    if (receiverId) {
    651      const receiverActor = this.conn.getActor(receiverId);
    652      if (receiverActor) {
    653        receiver = receiverActor.obj;
    654      }
    655    }
    656 
    657    const value = receiver
    658      ? this.obj.getProperty(name, receiver)
    659      : this.obj.getProperty(name);
    660 
    661    return { value: this._buildCompletion(value) };
    662  }
    663 
    664  /**
    665   * Handle a protocol request to evaluate a function and provide the value of
    666   * the result.
    667   *
    668   * Note: Since this will evaluate the function, it can trigger execution of
    669   * content code and may cause side effects. This endpoint should only be used
    670   * when you are confident that the side-effects will be safe, or the user
    671   * is expecting the effects.
    672   *
    673   * @param {any} context
    674   *        The 'this' value to call the function with.
    675   * @param {Array<any>} args
    676   *        The array of un-decoded actor objects, or primitives.
    677   */
    678  apply(context, args) {
    679    if (!this.obj.callable) {
    680      return this.throwError("notCallable", "debugee object is not callable");
    681    }
    682 
    683    const debugeeContext = this._getValueFromGrip(context);
    684    const debugeeArgs = args && args.map(this._getValueFromGrip, this);
    685 
    686    const value = this.obj.apply(debugeeContext, debugeeArgs);
    687 
    688    return { value: this._buildCompletion(value) };
    689  }
    690 
    691  _getValueFromGrip(grip) {
    692    if (typeof grip !== "object" || !grip) {
    693      return grip;
    694    }
    695 
    696    if (typeof grip.actor !== "string") {
    697      return this.throwError(
    698        "invalidGrip",
    699        "grip argument did not include actor ID"
    700      );
    701    }
    702 
    703    const actor = this.conn.getActor(grip.actor);
    704 
    705    if (!actor) {
    706      return this.throwError(
    707        "unknownActor",
    708        "grip actor did not match a known object"
    709      );
    710    }
    711 
    712    return actor.obj;
    713  }
    714 
    715  /**
    716   * Converts a Debugger API completion value record into an equivalent
    717   * object grip for use by the API.
    718   *
    719   * See https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/
    720   * for more specifics on the expected behavior.
    721   */
    722  _buildCompletion(value) {
    723    let completionGrip = null;
    724 
    725    // .apply result will be falsy if the script being executed is terminated
    726    // via the "slow script" dialog.
    727    if (value) {
    728      completionGrip = {};
    729      if ("return" in value) {
    730        completionGrip.return = this.createValueGrip(value.return, 0);
    731      }
    732      if ("throw" in value) {
    733        completionGrip.throw = this.createValueGrip(value.throw, 0);
    734      }
    735    }
    736 
    737    return completionGrip;
    738  }
    739 
    740  /**
    741   * Handle a protocol request to get the target and handler internal slots of a proxy.
    742   */
    743  proxySlots() {
    744    // There could be transparent security wrappers, unwrap to check if it's a proxy.
    745    // However, retrieve proxyTarget and proxyHandler from `this.obj` to avoid exposing
    746    // the unwrapped target and handler.
    747    const unwrapped = DevToolsUtils.unwrap(this.obj);
    748    if (!unwrapped || !unwrapped.isProxy) {
    749      return this.throwError(
    750        "objectNotProxy",
    751        "'proxySlots' request is only valid for grips with a 'Proxy' class."
    752      );
    753    }
    754    return {
    755      proxyTarget: this.createValueGrip(this.obj.proxyTarget, 0),
    756      proxyHandler: this.createValueGrip(this.obj.proxyHandler, 0),
    757    };
    758  }
    759 
    760  /**
    761   * Release the actor, when it isn't needed anymore.
    762   * Protocol.js uses this release method to call the destroy method.
    763   */
    764  release() {
    765    if (this.hooks) {
    766      this.hooks.customFormatterConfigDbgObj = null;
    767    }
    768    this._customFormatterItem = null;
    769    this.obj = null;
    770    this.rawObj = null;
    771    this.safeRawObj = null;
    772    this.threadActor = null;
    773  }
    774 }
    775 
    776 exports.ObjectActor = ObjectActor;
    777 
    778 function safeGetOwnPropertyDescriptor(obj, name) {
    779  let desc = null;
    780  try {
    781    desc = obj.getOwnPropertyDescriptor(name);
    782  } catch (ex) {
    783    // The above can throw if the cache becomes stale.
    784  }
    785  return desc;
    786 }
    787 
    788 /**
    789 * Check if the value is rejected promise
    790 *
    791 * @param {object} getterValue
    792 * @returns {boolean} true if the value is rejected promise, false otherwise.
    793 */
    794 function isRejectedPromise(getterValue) {
    795  return (
    796    getterValue &&
    797    getterValue.class == "Promise" &&
    798    getterValue.promiseState == "rejected"
    799  );
    800 }