tor-browser

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

previewers.js (36450B)


      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 /* global Temporal, TrustedHTML, TrustedScript, TrustedScriptURL */
      8 
      9 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     10 loader.lazyRequireGetter(
     11  this,
     12  "ObjectUtils",
     13  "resource://devtools/server/actors/object/utils.js"
     14 );
     15 loader.lazyRequireGetter(
     16  this,
     17  "PropertyIterators",
     18  "resource://devtools/server/actors/object/property-iterator.js"
     19 );
     20 loader.lazyRequireGetter(
     21  this,
     22  "propertyDescriptor",
     23  "resource://devtools/server/actors/object/property-descriptor.js",
     24  true
     25 );
     26 
     27 // Number of items to preview in objects, arrays, maps, sets, lists,
     28 // collections, etc.
     29 const OBJECT_PREVIEW_MAX_ITEMS = 10;
     30 
     31 const ERROR_CLASSNAMES = new Set([
     32  "Error",
     33  "EvalError",
     34  "RangeError",
     35  "ReferenceError",
     36  "SyntaxError",
     37  "TypeError",
     38  "URIError",
     39  "InternalError",
     40  "AggregateError",
     41  "CompileError",
     42  "DebuggeeWouldRun",
     43  "LinkError",
     44  "RuntimeError",
     45  "Exception", // This related to Components.Exception()
     46  "SuppressedError",
     47 ]);
     48 const ARRAY_LIKE_CLASSNAMES = new Set([
     49  "DOMStringList",
     50  "DOMTokenList",
     51  "CSSRuleList",
     52  "MediaList",
     53  "StyleSheetList",
     54  "NamedNodeMap",
     55  "FileList",
     56  "NodeList",
     57 ]);
     58 const OBJECT_WITH_URL_CLASSNAMES = new Set([
     59  "CSSImportRule",
     60  "CSSStyleSheet",
     61  "Location",
     62  "TrustedScriptURL"
     63 ]);
     64 
     65 /**
     66 * Functions for adding information to ObjectActor grips for the purpose of
     67 * having customized output. This object holds arrays mapped by
     68 * Debugger.Object.prototype.class.
     69 *
     70 * In each array you can add functions that take three
     71 * arguments:
     72 *   - the ObjectActor instance and its hooks to make a preview for,
     73 *   - the grip object being prepared for the client,
     74 *   - the depth of the object compared to the top level object,
     75 *     when we are inspecting nested attributes.
     76 *
     77 * Functions must return false if they cannot provide preview
     78 * information for the debugger object, or true otherwise.
     79 */
     80 const previewers = {
     81  String: [
     82    function(objectActor, grip, depth) {
     83      return wrappedPrimitivePreviewer(
     84        String,
     85        objectActor,
     86        grip,
     87        depth
     88      );
     89    },
     90  ],
     91 
     92  Boolean: [
     93    function(objectActor, grip, depth) {
     94      return wrappedPrimitivePreviewer(
     95        Boolean,
     96        objectActor,
     97        grip,
     98        depth
     99      );
    100    },
    101  ],
    102 
    103  Number: [
    104    function(objectActor, grip, depth) {
    105      return wrappedPrimitivePreviewer(
    106        Number,
    107        objectActor,
    108        grip,
    109        depth
    110      );
    111    },
    112  ],
    113 
    114  Symbol: [
    115    function(objectActor, grip, depth) {
    116      return wrappedPrimitivePreviewer(
    117        Symbol,
    118        objectActor,
    119        grip,
    120        depth
    121      );
    122    },
    123  ],
    124 
    125  Function: [
    126    function(objectActor, grip, depth) {
    127      const { obj } = objectActor;
    128      if (obj.name) {
    129        grip.name = obj.name;
    130      }
    131 
    132      if (obj.displayName) {
    133        grip.displayName = obj.displayName.substr(0, 500);
    134      }
    135 
    136      if (obj.parameterNames) {
    137        grip.parameterNames = obj.parameterNames;
    138      }
    139 
    140      // Check if the developer has added a de-facto standard displayName
    141      // property for us to use.
    142      let userDisplayName;
    143      try {
    144        userDisplayName = obj.getOwnPropertyDescriptor("displayName");
    145      } catch (e) {
    146        // The above can throw "permission denied" errors when the debuggee
    147        // does not subsume the function's compartment.
    148      }
    149 
    150      if (
    151        userDisplayName &&
    152        typeof userDisplayName.value == "string" &&
    153        userDisplayName.value
    154      ) {
    155        grip.userDisplayName = objectActor.createValueGrip(userDisplayName.value, depth);
    156      }
    157 
    158      grip.isAsync = obj.isAsyncFunction;
    159      grip.isGenerator = obj.isGeneratorFunction;
    160 
    161      if (obj.script) {
    162        // NOTE: Debugger.Script.prototype.startColumn is 1-based.
    163        //       Convert to 0-based, while keeping the wasm's column (1) as is.
    164        //       (bug 1863878)
    165        const columnBase = obj.script.format === "wasm" ? 0 : 1;
    166        grip.location = {
    167          url: obj.script.url,
    168          line: obj.script.startLine,
    169          column: obj.script.startColumn - columnBase,
    170        };
    171      }
    172 
    173      return true;
    174    },
    175  ],
    176 
    177  RegExp: [
    178    function(objectActor, grip, depth) {
    179      let str;
    180      if (isWorker) {
    181        // For some reason, the following incantation on the worker thread returns "/undefined/undefined"
    182        // str = RegExp.prototype.toString.call(objectActor.obj.unsafeDereference());
    183        //
    184        // The following method will throw in case of method being overloaded by the page,
    185        // and a more generic previewer will render the object.
    186        try {
    187          str = DevToolsUtils.callPropertyOnObject(objectActor.obj, "toString");
    188        } catch(e) {
    189          // Ensure displaying something in case of error.
    190          // Otherwise this would render an object with an empty label
    191          grip.displayString = "RegExp with overloaded toString";
    192        }
    193      } else {
    194        const { RegExp } = objectActor.targetActor.targetGlobal;
    195        str = RegExp.prototype.toString.call(objectActor.safeRawObj);
    196      }
    197 
    198      if (typeof str != "string") {
    199        return false;
    200      }
    201 
    202      grip.displayString = objectActor.createValueGrip(str, depth);
    203      return true;
    204    },
    205  ],
    206 
    207  Date: [
    208    function(objectActor, grip, depth) {
    209      let time;
    210      if (isWorker) {
    211        // Also, targetGlobal is an opaque wrapper, from which we can't access its Date object,
    212        // so fallback to the privileged one
    213        //
    214        // In worker objectActor.safeRawObj is considered unsafe and is null,
    215        // so retrieve the objectActor.rawObj object directly from Debugger.Object.unsafeDereference
    216        time = Date.prototype.getTime.call(objectActor.rawObj);
    217      } else {
    218        const { Date } = objectActor.targetActor.targetGlobal;
    219        time = Date.prototype.getTime.call(objectActor.safeRawObj);
    220      }
    221      if (typeof time != "number") {
    222        return false;
    223      }
    224 
    225      grip.preview = {
    226        timestamp: objectActor.createValueGrip(time, depth),
    227      };
    228      return true;
    229    },
    230  ],
    231 
    232  "Temporal.Instant": [
    233    function(objectActor, grip, _depth) {
    234      temporalPreviewer(Temporal.Instant, objectActor, grip);
    235      return true;
    236    },
    237  ],
    238 
    239  "Temporal.PlainDate": [
    240    function(objectActor, grip, _depth) {
    241      temporalPreviewer(Temporal.PlainDate, objectActor, grip);
    242      return true;
    243    },
    244  ],
    245 
    246  "Temporal.PlainDateTime": [
    247    function(objectActor, grip, _depth) {
    248      temporalPreviewer(Temporal.PlainDateTime, objectActor, grip);
    249      return true;
    250    },
    251  ],
    252 
    253  "Temporal.PlainMonthDay": [
    254    function(objectActor, grip, _depth) {
    255      temporalPreviewer(Temporal.PlainMonthDay, objectActor, grip);
    256      return true;
    257    },
    258  ],
    259 
    260  "Temporal.PlainTime": [
    261    function(objectActor, grip, _depth) {
    262      temporalPreviewer(Temporal.PlainTime, objectActor, grip);
    263      return true;
    264    },
    265  ],
    266 
    267  "Temporal.PlainYearMonth": [
    268    function(objectActor, grip, _depth) {
    269      temporalPreviewer(Temporal.PlainYearMonth, objectActor, grip);
    270      return true;
    271    },
    272  ],
    273 
    274  "Temporal.ZonedDateTime": [
    275    function(objectActor, grip, _depth) {
    276      temporalPreviewer(Temporal.ZonedDateTime, objectActor, grip);
    277      return true;
    278    },
    279  ],
    280 
    281  "Temporal.Duration": [
    282    function(objectActor, grip, _depth) {
    283      temporalPreviewer(Temporal.Duration, objectActor, grip);
    284      return true;
    285    },
    286  ],
    287 
    288  TrustedHTML: [
    289    function(objectActor, grip, depth) {
    290      const text = TrustedHTML.prototype.toString.call(
    291        // In worker objectActor.safeRawObj is considered unsafe and is null
    292        objectActor.safeRawObj || objectActor.rawObj
    293      );
    294 
    295      grip.preview = {
    296        kind: "ObjectWithText",
    297        text: objectActor.createValueGrip(text, depth)
    298      };
    299      return true;
    300    },
    301  ],
    302 
    303  TrustedScript: [
    304    function(objectActor, grip, depth) {
    305      const text = TrustedScript.prototype.toString.call(
    306        // In worker objectActor.safeRawObj is considered unsafe and is null
    307        objectActor.safeRawObj || objectActor.rawObj
    308      );
    309 
    310      grip.preview = {
    311        kind: "ObjectWithText",
    312        text: objectActor.createValueGrip(text, depth)
    313      };
    314      return true;
    315    },
    316  ],
    317 
    318  TrustedScriptURL: [
    319    function(objectActor, grip, depth) {
    320      const url = TrustedScriptURL.prototype.toString.call(
    321        // In worker objectActor.safeRawObj is considered unsafe and is null
    322        objectActor.safeRawObj || objectActor.rawObj
    323      );
    324 
    325      grip.preview = {
    326        kind: "ObjectWithURL",
    327        url: objectActor.createValueGrip(url, depth)
    328      };
    329      return true;
    330    },
    331  ],
    332 
    333  Array: [
    334    function(objectActor, grip, depth) {
    335      const length = ObjectUtils.getArrayLength(objectActor.obj);
    336 
    337      grip.preview = {
    338        kind: "ArrayLike",
    339        length,
    340      };
    341 
    342      if (depth > 1) {
    343        return true;
    344      }
    345 
    346      const { obj, rawObj } = objectActor;
    347      const items = (grip.preview.items = []);
    348 
    349      for (let i = 0; i < length; ++i) {
    350        if (rawObj && !isWorker) {
    351          // Array Xrays filter out various possibly-unsafe properties (like
    352          // functions, and claim that the value is undefined instead. This
    353          // is generally the right thing for privileged code accessing untrusted
    354          // objects, but quite confusing for Object previews. So we manually
    355          // override this protection by waiving Xrays on the array, and re-applying
    356          // Xrays on any indexed value props that we pull off of it.
    357          const desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(rawObj), i);
    358          if (desc && !desc.get && !desc.set) {
    359            let value = Cu.unwaiveXrays(desc.value);
    360            value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value);
    361            items.push(objectActor.createValueGrip(value, depth));
    362          } else if (!desc) {
    363            items.push(null);
    364          } else {
    365            const item = {};
    366            if (desc.get) {
    367              let getter = Cu.unwaiveXrays(desc.get);
    368              getter = ObjectUtils.makeDebuggeeValueIfNeeded(obj, getter);
    369              item.get = objectActor.createValueGrip(getter, depth);
    370            }
    371            if (desc.set) {
    372              let setter = Cu.unwaiveXrays(desc.set);
    373              setter = ObjectUtils.makeDebuggeeValueIfNeeded(obj, setter);
    374              item.set = objectActor.createValueGrip(setter, depth);
    375            }
    376            items.push(item);
    377          }
    378        } else if (rawObj && !obj.getOwnPropertyDescriptor(i)) {
    379          items.push(null);
    380        } else {
    381          // Workers do not have access to Cu.
    382          const value = DevToolsUtils.getProperty(obj, i);
    383          items.push(objectActor.createValueGrip(value, depth));
    384        }
    385 
    386        if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
    387          break;
    388        }
    389      }
    390 
    391      return true;
    392    },
    393  ],
    394 
    395  Set: [
    396    function(objectActor, grip, depth) {
    397      const size = DevToolsUtils.getProperty(objectActor.obj, "size");
    398      if (typeof size != "number") {
    399        return false;
    400      }
    401 
    402      grip.preview = {
    403        kind: "ArrayLike",
    404        length: size,
    405      };
    406 
    407      // Avoid recursive object grips.
    408      if (depth > 1) {
    409        return true;
    410      }
    411 
    412      const items = (grip.preview.items = []);
    413      for (const item of PropertyIterators.enumSetEntries(objectActor, depth)) {
    414        items.push(item);
    415        if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
    416          break;
    417        }
    418      }
    419 
    420      return true;
    421    },
    422  ],
    423 
    424  WeakSet: [
    425    function(objectActor, grip, depth) {
    426      const enumEntries = PropertyIterators.enumWeakSetEntries(objectActor, depth);
    427 
    428      grip.preview = {
    429        kind: "ArrayLike",
    430        length: enumEntries.size,
    431      };
    432 
    433      // Avoid recursive object grips.
    434      if (depth > 1) {
    435        return true;
    436      }
    437 
    438      const items = (grip.preview.items = []);
    439      for (const item of enumEntries) {
    440        items.push(item);
    441        if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
    442          break;
    443        }
    444      }
    445 
    446      return true;
    447    },
    448  ],
    449 
    450  Map: [
    451    function(objectActor, grip, depth) {
    452      const size = DevToolsUtils.getProperty(objectActor.obj, "size");
    453      if (typeof size != "number") {
    454        return false;
    455      }
    456 
    457      grip.preview = {
    458        kind: "MapLike",
    459        size,
    460      };
    461 
    462      if (depth > 1) {
    463        return true;
    464      }
    465 
    466      const entries = (grip.preview.entries = []);
    467      for (const entry of PropertyIterators.enumMapEntries(objectActor, depth)) {
    468        entries.push(entry);
    469        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    470          break;
    471        }
    472      }
    473 
    474      return true;
    475    },
    476  ],
    477 
    478  WeakMap: [
    479    function(objectActor, grip, depth) {
    480      const enumEntries = PropertyIterators.enumWeakMapEntries(objectActor, depth);
    481 
    482      grip.preview = {
    483        kind: "MapLike",
    484        size: enumEntries.size,
    485      };
    486 
    487      if (depth > 1) {
    488        return true;
    489      }
    490 
    491      const entries = (grip.preview.entries = []);
    492      for (const entry of enumEntries) {
    493        entries.push(entry);
    494        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    495          break;
    496        }
    497      }
    498 
    499      return true;
    500    },
    501  ],
    502 
    503  URLSearchParams: [
    504    function(objectActor, grip, depth) {
    505      const enumEntries = PropertyIterators.enumURLSearchParamsEntries(objectActor, depth);
    506 
    507      grip.preview = {
    508        kind: "MapLike",
    509        size: enumEntries.size,
    510      };
    511 
    512      if (depth > 1) {
    513        return true;
    514      }
    515 
    516      const entries = (grip.preview.entries = []);
    517      for (const entry of enumEntries) {
    518        entries.push(entry);
    519        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    520          break;
    521        }
    522      }
    523 
    524      return true;
    525    },
    526  ],
    527 
    528  FormData: [
    529    function(objectActor, grip, depth) {
    530      const enumEntries = PropertyIterators.enumFormDataEntries(objectActor, depth);
    531 
    532      grip.preview = {
    533        kind: "MapLike",
    534        size: enumEntries.size,
    535      };
    536 
    537      if (depth > 1) {
    538        return true;
    539      }
    540 
    541      const entries = (grip.preview.entries = []);
    542      for (const entry of enumEntries) {
    543        entries.push(entry);
    544        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    545          break;
    546        }
    547      }
    548 
    549      return true;
    550    },
    551  ],
    552 
    553  Headers: [
    554    function(objectActor, grip, depth) {
    555      // Bug 1863776: Headers can't be yet previewed from workers
    556      if (isWorker) {
    557        return false;
    558      }
    559      const enumEntries = PropertyIterators.enumHeadersEntries(objectActor, depth);
    560 
    561      grip.preview = {
    562        kind: "MapLike",
    563        size: enumEntries.size,
    564      };
    565 
    566      if (depth > 1) {
    567        return true;
    568      }
    569 
    570      const entries = (grip.preview.entries = []);
    571      for (const entry of enumEntries) {
    572        entries.push(entry);
    573        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    574          break;
    575        }
    576      }
    577 
    578      return true;
    579    },
    580  ],
    581 
    582 
    583  HighlightRegistry: [
    584    function(objectActor, grip, depth) {
    585      const enumEntries = PropertyIterators.enumHighlightRegistryEntries(objectActor, depth);
    586 
    587      grip.preview = {
    588        kind: "MapLike",
    589        size: enumEntries.size,
    590      };
    591 
    592      if (depth > 1) {
    593        return true;
    594      }
    595 
    596      const entries = (grip.preview.entries = []);
    597      for (const entry of enumEntries) {
    598        entries.push(entry);
    599        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    600          break;
    601        }
    602      }
    603 
    604      return true;
    605    },
    606  ],
    607 
    608  MIDIInputMap: [
    609    function(objectActor, grip, depth) {
    610      const enumEntries = PropertyIterators.enumMidiInputMapEntries(
    611        objectActor,
    612        depth
    613      );
    614 
    615      grip.preview = {
    616        kind: "MapLike",
    617        size: enumEntries.size,
    618      };
    619 
    620      if (depth > 1) {
    621        return true;
    622      }
    623 
    624      const entries = (grip.preview.entries = []);
    625      for (const entry of enumEntries) {
    626        entries.push(entry);
    627        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    628          break;
    629        }
    630      }
    631 
    632      return true;
    633    },
    634  ],
    635 
    636  MIDIOutputMap: [
    637    function(objectActor, grip, depth) {
    638      const enumEntries = PropertyIterators.enumMidiOutputMapEntries(
    639        objectActor,
    640        depth
    641      );
    642 
    643      grip.preview = {
    644        kind: "MapLike",
    645        size: enumEntries.size,
    646      };
    647 
    648      if (depth > 1) {
    649        return true;
    650      }
    651 
    652      const entries = (grip.preview.entries = []);
    653      for (const entry of enumEntries) {
    654        entries.push(entry);
    655        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    656          break;
    657        }
    658      }
    659 
    660      return true;
    661    },
    662  ],
    663 
    664  DOMStringMap: [
    665    function(objectActor, grip, depth) {
    666      const { obj, safeRawObj } = objectActor;
    667      if (!safeRawObj) {
    668        return false;
    669      }
    670 
    671      const keys = obj.getOwnPropertyNames();
    672      grip.preview = {
    673        kind: "MapLike",
    674        size: keys.length,
    675      };
    676 
    677      if (depth > 1) {
    678        return true;
    679      }
    680 
    681      const entries = (grip.preview.entries = []);
    682      for (const key of keys) {
    683        const value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, safeRawObj[key]);
    684        entries.push([key, objectActor.createValueGrip(value, depth)]);
    685        if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
    686          break;
    687        }
    688      }
    689 
    690      return true;
    691    },
    692  ],
    693 
    694  Promise: [
    695    function(objectActor, grip, depth) {
    696      const { state, value, reason } = ObjectUtils.getPromiseState(objectActor.obj);
    697      const ownProperties = Object.create(null);
    698      ownProperties["<state>"] = { value: state };
    699      let ownPropertiesLength = 1;
    700 
    701      // Only expose <value> or <reason> in top-level promises, to avoid recursion.
    702      // <state> is not problematic because it's a string.
    703      if (depth === 1) {
    704        if (state == "fulfilled") {
    705          ownProperties["<value>"] = { value: objectActor.createValueGrip(value, depth) };
    706          ++ownPropertiesLength;
    707        } else if (state == "rejected") {
    708          ownProperties["<reason>"] = { value: objectActor.createValueGrip(reason, depth) };
    709          ++ownPropertiesLength;
    710        }
    711      }
    712 
    713      grip.preview = {
    714        kind: "Object",
    715        ownProperties,
    716        ownPropertiesLength,
    717      };
    718 
    719      return true;
    720    },
    721  ],
    722 
    723  Proxy: [
    724    function(objectActor, grip, depth) {
    725      // Only preview top-level proxies, avoiding recursion. Otherwise, since both the
    726      // target and handler can also be proxies, we could get an exponential behavior.
    727      if (depth > 1) {
    728        return true;
    729      }
    730 
    731      const { obj } = objectActor;
    732 
    733      // The `isProxy` getter of the debuggee object only detects proxies without
    734      // security wrappers. If false, the target and handler are not available.
    735      const hasTargetAndHandler = obj.isProxy;
    736 
    737      grip.preview = {
    738        kind: "Object",
    739        ownProperties: Object.create(null),
    740        ownPropertiesLength: 2 * hasTargetAndHandler,
    741      };
    742 
    743      if (hasTargetAndHandler) {
    744        Object.assign(grip.preview.ownProperties, {
    745          "<target>": { value: objectActor.createValueGrip(obj.proxyTarget, depth) },
    746          "<handler>": { value: objectActor.createValueGrip(obj.proxyHandler, depth) },
    747        });
    748      }
    749 
    750      return true;
    751    },
    752  ],
    753 
    754  CustomStateSet: [
    755    function(objectActor, grip, depth) {
    756      const size = DevToolsUtils.getProperty(objectActor.obj, "size");
    757      if (typeof size != "number") {
    758        return false;
    759      }
    760 
    761      grip.preview = {
    762        kind: "ArrayLike",
    763        length: size,
    764      };
    765 
    766      const items = (grip.preview.items = []);
    767      for (const item of PropertyIterators.enumCustomStateSetEntries(objectActor, depth)) {
    768        items.push(item);
    769        if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
    770          break;
    771        }
    772      }
    773 
    774      return true;
    775    },
    776  ],
    777 };
    778 
    779 /**
    780 * Generic previewer for classes wrapping primitives, like String,
    781 * Number and Boolean.
    782 *
    783 * @param object classObj
    784 *        The class to expect, eg. String. The valueOf() method of the class is
    785 *        invoked on the given object.
    786 * @param ObjectActor objectActor
    787 *        The object actor
    788 * @param Object grip
    789 *        The result grip to fill in
    790 * @param Number depth
    791 *        Depth of the object compared to the top level object,
    792 *        when we are inspecting nested attributes.
    793 * @return Booolean true if the object was handled, false otherwise
    794 */
    795 function wrappedPrimitivePreviewer(
    796  classObj,
    797  objectActor,
    798  grip,
    799  depth
    800 ) {
    801  const { safeRawObj } = objectActor;
    802  let v = null;
    803  try {
    804    v = classObj.prototype.valueOf.call(safeRawObj);
    805  } catch (ex) {
    806    // valueOf() can throw if the raw JS object is "misbehaved".
    807    return false;
    808  }
    809 
    810  if (v === null) {
    811    return false;
    812  }
    813 
    814  const canHandle = GenericObject(objectActor, grip, depth);
    815  if (!canHandle) {
    816    return false;
    817  }
    818 
    819  grip.preview.wrappedValue = objectActor.createValueGrip(
    820    ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, v),
    821    depth
    822  );
    823  return true;
    824 }
    825 
    826 /**
    827 * Previewer for Temporal objects
    828 *
    829 * @param cls
    830 *        The class of the object we're previewing (e.g. `Temporal.Instant`)
    831 * @param ObjectActor objectActor
    832 *        The object actor
    833 * @param Object grip
    834 *        The result grip to fill in
    835 */
    836 function temporalPreviewer(cls, objectActor, grip) {
    837  grip.preview = {
    838    kind: "ObjectWithText",
    839    text: cls.prototype.toString.call(
    840      // In worker objectActor.safeRawObj is considered unsafe and is null
    841      objectActor.safeRawObj || objectActor.rawObj
    842    )
    843  }
    844 }
    845 
    846 /**
    847 * @param {ObjectActor} objectActor
    848 * @param {object} grip: The grip built by the objectActor, for which we need to populate
    849 *                       the `preview` property.
    850 * @param {number} depth
    851 *        Depth of the object compared to the top level object,
    852 *        when we are inspecting nested attributes.
    853 * @returns
    854 */
    855 // eslint-disable-next-line complexity
    856 function GenericObject(objectActor, grip, depth) {
    857  const { obj, safeRawObj } = objectActor;
    858  if (grip.preview || grip.displayString || depth > 1) {
    859    return false;
    860  }
    861 
    862  const preview = (grip.preview = {
    863    kind: "Object",
    864    ownProperties: Object.create(null),
    865  });
    866 
    867  const names = ObjectUtils.getPropNamesFromObject(obj, safeRawObj);
    868  preview.ownPropertiesLength = names.length;
    869 
    870  let length,
    871    i = 0;
    872  let specialStringBehavior = objectActor.className === "String";
    873  if (specialStringBehavior) {
    874    length = DevToolsUtils.getProperty(obj, "length");
    875    if (typeof length != "number") {
    876      specialStringBehavior = false;
    877    }
    878  }
    879 
    880  for (const name of names) {
    881    if (specialStringBehavior && /^[0-9]+$/.test(name)) {
    882      const num = parseInt(name, 10);
    883      if (num.toString() === name && num >= 0 && num < length) {
    884        continue;
    885      }
    886    }
    887 
    888    const desc = propertyDescriptor(objectActor, name, depth, true);
    889    if (!desc) {
    890      continue;
    891    }
    892 
    893    preview.ownProperties[name] = desc;
    894    if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
    895      break;
    896    }
    897  }
    898 
    899  if (i === OBJECT_PREVIEW_MAX_ITEMS) {
    900    return true;
    901  }
    902 
    903  const privatePropertiesSymbols = ObjectUtils.getSafePrivatePropertiesSymbols(
    904    obj
    905  );
    906  if (privatePropertiesSymbols.length) {
    907    preview.privatePropertiesLength = privatePropertiesSymbols.length;
    908    preview.privateProperties = [];
    909 
    910    // Retrieve private properties, which are represented as non-enumerable Symbols
    911    for (const privateProperty of privatePropertiesSymbols) {
    912      if (
    913        !privateProperty.description ||
    914        !privateProperty.description.startsWith("#")
    915      ) {
    916        continue;
    917      }
    918      const descriptor = propertyDescriptor(objectActor, privateProperty, depth);
    919      if (!descriptor) {
    920        continue;
    921      }
    922 
    923      preview.privateProperties.push(
    924        Object.assign(
    925          {
    926            descriptor,
    927          },
    928          objectActor.createValueGrip(privateProperty, depth)
    929        )
    930      );
    931 
    932      if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
    933        break;
    934      }
    935    }
    936  }
    937 
    938  if (i === OBJECT_PREVIEW_MAX_ITEMS) {
    939    return true;
    940  }
    941 
    942  const symbols = ObjectUtils.getSafeOwnPropertySymbols(obj);
    943  if (symbols.length) {
    944    preview.ownSymbolsLength = symbols.length;
    945    preview.ownSymbols = [];
    946 
    947    for (const symbol of symbols) {
    948      const descriptor = propertyDescriptor(objectActor, symbol, depth, true);
    949      if (!descriptor) {
    950        continue;
    951      }
    952 
    953      preview.ownSymbols.push(
    954        Object.assign(
    955          {
    956            descriptor,
    957          },
    958          objectActor.createValueGrip(symbol, depth)
    959        )
    960      );
    961 
    962      if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
    963        break;
    964      }
    965    }
    966  }
    967 
    968  if (i === OBJECT_PREVIEW_MAX_ITEMS) {
    969    return true;
    970  }
    971 
    972  const safeGetterValues = objectActor._findSafeGetterValues(
    973    Object.keys(preview.ownProperties),
    974    depth,
    975    OBJECT_PREVIEW_MAX_ITEMS - i
    976  );
    977  if (Object.keys(safeGetterValues).length) {
    978    preview.safeGetterValues = safeGetterValues;
    979  }
    980 
    981  return true;
    982 }
    983 
    984 // Preview functions that do not rely on the object class.
    985 previewers.Object = [
    986  function TypedArray(objectActor, grip, depth) {
    987    const { obj, className } = objectActor;
    988    if (!ObjectUtils.isTypedArray(obj)) {
    989      return false;
    990    }
    991 
    992    grip.preview = {
    993      kind: "ArrayLike",
    994      length: ObjectUtils.getArrayLength(obj),
    995    };
    996 
    997    if (depth > 1) {
    998      return true;
    999    }
   1000 
   1001    const previewLength = Math.min(
   1002      OBJECT_PREVIEW_MAX_ITEMS,
   1003      grip.preview.length
   1004    );
   1005    grip.preview.items = [];
   1006    const isBigIntArray = className.startsWith("BigInt") || className.startsWith("BigUint");
   1007 
   1008    for (let i = 0; i < previewLength; i++) {
   1009      const desc = obj.getOwnPropertyDescriptor(i);
   1010      if (!desc) {
   1011        break;
   1012      }
   1013 
   1014      // We need to create grips for items of BigInt arrays. Other typed arrays are fine
   1015      // as they hold serializable primitives (Numbers)
   1016      const item = isBigIntArray
   1017        ? ObjectUtils.createBigIntValueGrip(desc.value)
   1018        : desc.value;
   1019      grip.preview.items.push(item);
   1020    }
   1021 
   1022    return true;
   1023  },
   1024 
   1025  function Error(objectActor, grip, depth) {
   1026    if (!ERROR_CLASSNAMES.has(objectActor.className)) {
   1027      return false;
   1028    }
   1029 
   1030    const { obj, allowSideEffect = false } = objectActor;
   1031 
   1032    // The name and/or message could be getters, and even if it's unsafe,
   1033    // we do want to show it to the user, unless the error is muted
   1034    // (See Bug 1710694).
   1035    const invokeUnsafeGetters = allowSideEffect && !obj.isMutedError;
   1036 
   1037    const name = DevToolsUtils.getProperty(obj, "name", invokeUnsafeGetters);
   1038    const msg = DevToolsUtils.getProperty(obj, "message", invokeUnsafeGetters);
   1039    const stack = DevToolsUtils.getProperty(obj, "stack");
   1040    const fileName = DevToolsUtils.getProperty(obj, "fileName");
   1041    const lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
   1042    const columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
   1043 
   1044    grip.preview = {
   1045      kind: "Error",
   1046      name: objectActor.createValueGrip(name, depth),
   1047      message: objectActor.createValueGrip(msg, depth),
   1048      stack: objectActor.createValueGrip(stack, depth),
   1049      fileName: objectActor.createValueGrip(fileName, depth),
   1050      lineNumber: objectActor.createValueGrip(lineNumber, depth),
   1051      columnNumber: objectActor.createValueGrip(columnNumber, depth),
   1052    };
   1053 
   1054    const errorHasCause = obj.getOwnPropertyNames().includes("cause");
   1055    if (errorHasCause) {
   1056      grip.preview.cause = objectActor.createValueGrip(
   1057        DevToolsUtils.getProperty(obj, "cause", true),
   1058        depth
   1059      );
   1060    }
   1061 
   1062    return true;
   1063  },
   1064 
   1065  function CSSMediaRule(objectActor, grip, depth) {
   1066    const { safeRawObj } = objectActor;
   1067    if (!safeRawObj || objectActor.className != "CSSMediaRule" || isWorker) {
   1068      return false;
   1069    }
   1070    grip.preview = {
   1071      kind: "ObjectWithText",
   1072      text: objectActor.createValueGrip(safeRawObj.conditionText, depth),
   1073    };
   1074    return true;
   1075  },
   1076 
   1077  function CSSStyleRule(objectActor, grip, depth) {
   1078    const { safeRawObj } = objectActor;
   1079    if (!safeRawObj || objectActor.className != "CSSStyleRule" || isWorker) {
   1080      return false;
   1081    }
   1082    grip.preview = {
   1083      kind: "ObjectWithText",
   1084      text: objectActor.createValueGrip(safeRawObj.selectorText, depth),
   1085    };
   1086    return true;
   1087  },
   1088 
   1089  function ObjectWithURL(objectActor, grip, depth) {
   1090    const { safeRawObj } = objectActor;
   1091    if (isWorker || !safeRawObj) {
   1092      return false;
   1093    }
   1094 
   1095    const isWindow = Window.isInstance(safeRawObj);
   1096    if (!OBJECT_WITH_URL_CLASSNAMES.has(objectActor.className) && !isWindow) {
   1097      return false;
   1098    }
   1099 
   1100    let url;
   1101    if (isWindow && safeRawObj.location) {
   1102      try {
   1103        url = safeRawObj.location.href;
   1104      } catch(e) {
   1105        // This can happen when we have a cross-process window.
   1106        // In such case, let's retrieve the url from the iframe.
   1107        // For window.top from a remote iframe, there's no way we can't retrieve the URL,
   1108        // so return a label that help user know what's going on.
   1109        url = safeRawObj.browsingContext?.embedderElement?.src || "Restricted";
   1110      }
   1111    } else if (safeRawObj.href) {
   1112      url = safeRawObj.href;
   1113    } else {
   1114      return false;
   1115    }
   1116 
   1117    grip.preview = {
   1118      kind: "ObjectWithURL",
   1119      url: objectActor.createValueGrip(url, depth),
   1120    };
   1121 
   1122    return true;
   1123  },
   1124 
   1125  function ArrayLike(objectActor, grip, depth) {
   1126    const { safeRawObj } = objectActor;
   1127    if (
   1128      !safeRawObj ||
   1129      !ARRAY_LIKE_CLASSNAMES.has(objectActor.className) ||
   1130      typeof safeRawObj.length != "number" ||
   1131      isWorker
   1132    ) {
   1133      return false;
   1134    }
   1135 
   1136    grip.preview = {
   1137      kind: "ArrayLike",
   1138      length: safeRawObj.length,
   1139    };
   1140 
   1141    if (depth > 1) {
   1142      return true;
   1143    }
   1144 
   1145    const items = (grip.preview.items = []);
   1146 
   1147    for (
   1148      let i = 0;
   1149      i < safeRawObj.length && items.length < OBJECT_PREVIEW_MAX_ITEMS;
   1150      i++
   1151    ) {
   1152      const value = ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, safeRawObj[i]);
   1153      items.push(objectActor.createValueGrip(value, depth));
   1154    }
   1155 
   1156    return true;
   1157  },
   1158 
   1159  function CSSStyleDeclaration(objectActor, grip, depth) {
   1160    const { safeRawObj, className } = objectActor;
   1161    if (
   1162      !safeRawObj ||
   1163      (className != "CSSStyleDeclaration" && className != "CSSStyleProperties") ||
   1164      isWorker
   1165    ) {
   1166      return false;
   1167    }
   1168 
   1169    grip.preview = {
   1170      kind: "MapLike",
   1171      size: safeRawObj.length,
   1172    };
   1173 
   1174    const entries = (grip.preview.entries = []);
   1175 
   1176    for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS && i < safeRawObj.length; i++) {
   1177      const prop = safeRawObj[i];
   1178      const value = safeRawObj.getPropertyValue(prop);
   1179      entries.push([prop, objectActor.createValueGrip(value, depth)]);
   1180    }
   1181 
   1182    return true;
   1183  },
   1184 
   1185  function DOMNode(objectActor, grip, depth) {
   1186    const { safeRawObj } = objectActor;
   1187    if (
   1188      objectActor.className == "Object" ||
   1189      !safeRawObj ||
   1190      !Node.isInstance(safeRawObj) ||
   1191      isWorker
   1192    ) {
   1193      return false;
   1194    }
   1195 
   1196    const { obj, className } = objectActor;
   1197 
   1198    const preview = (grip.preview = {
   1199      kind: "DOMNode",
   1200      nodeType: safeRawObj.nodeType,
   1201      nodeName: safeRawObj.nodeName,
   1202      isConnected: safeRawObj.isConnected === true,
   1203    });
   1204 
   1205    if (safeRawObj.nodeType == safeRawObj.DOCUMENT_NODE && safeRawObj.location) {
   1206      preview.location = objectActor.createValueGrip(safeRawObj.location.href, depth);
   1207    } else if (className == "DocumentFragment") {
   1208      preview.childNodesLength = safeRawObj.childNodes.length;
   1209 
   1210      if (depth < 2) {
   1211        preview.childNodes = [];
   1212        for (const node of safeRawObj.childNodes) {
   1213          const actor = objectActor.createValueGrip(obj.makeDebuggeeValue(node), depth);
   1214          preview.childNodes.push(actor);
   1215          if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
   1216            break;
   1217          }
   1218        }
   1219      }
   1220    } else if (Element.isInstance(safeRawObj)) {
   1221      // For HTML elements (in an HTML document, at least), the nodeName is an
   1222      // uppercased version of the actual element name.  Check for HTML
   1223      // elements, that is elements in the HTML namespace, and lowercase the
   1224      // nodeName in that case.
   1225      if (safeRawObj.namespaceURI == "http://www.w3.org/1999/xhtml") {
   1226        preview.nodeName = preview.nodeName.toLowerCase();
   1227      }
   1228 
   1229      // Add preview for DOM element attributes.
   1230      preview.attributes = {};
   1231      preview.attributesLength = safeRawObj.attributes.length;
   1232      for (const attr of safeRawObj.attributes) {
   1233        preview.attributes[attr.nodeName] = objectActor.createValueGrip(attr.value, depth);
   1234      }
   1235 
   1236      // Custom elements may have private properties. Ensure that we provide
   1237      // enough information for ObjectInspector to know it should check for
   1238      // them.
   1239      const privatePropertiesSymbols = ObjectUtils.getSafePrivatePropertiesSymbols(
   1240        obj
   1241      );
   1242      if (privatePropertiesSymbols.length) {
   1243        preview.privatePropertiesLength = privatePropertiesSymbols.length;
   1244      }
   1245    } else if (className == "Attr") {
   1246      preview.value = objectActor.createValueGrip(safeRawObj.value, depth);
   1247    } else if (
   1248      className == "Text" ||
   1249      className == "CDATASection" ||
   1250      className == "Comment"
   1251    ) {
   1252      preview.textContent = objectActor.createValueGrip(safeRawObj.textContent, depth);
   1253    }
   1254 
   1255    return true;
   1256  },
   1257 
   1258  function DOMEvent(objectActor, grip, depth) {
   1259    const { safeRawObj } = objectActor;
   1260    if (!safeRawObj || !Event.isInstance(safeRawObj) || isWorker) {
   1261      return false;
   1262    }
   1263 
   1264    const { obj, className } = objectActor;
   1265    const preview = (grip.preview = {
   1266      kind: "DOMEvent",
   1267      type: safeRawObj.type,
   1268      properties: Object.create(null),
   1269    });
   1270 
   1271    if (depth < 2) {
   1272      const target = obj.makeDebuggeeValue(safeRawObj.target);
   1273      preview.target = objectActor.createValueGrip(target, depth);
   1274    }
   1275 
   1276    if (className == "KeyboardEvent") {
   1277      preview.eventKind = "key";
   1278      preview.modifiers = ObjectUtils.getModifiersForEvent(safeRawObj);
   1279    }
   1280 
   1281    const props = ObjectUtils.getPropsForEvent(className);
   1282 
   1283    // Add event-specific properties.
   1284    for (const prop of props) {
   1285      let value = safeRawObj[prop];
   1286      if (ObjectUtils.isObjectOrFunction(value)) {
   1287        // Skip properties pointing to objects.
   1288        if (depth > 1) {
   1289          continue;
   1290        }
   1291        value = obj.makeDebuggeeValue(value);
   1292      }
   1293      preview.properties[prop] = objectActor.createValueGrip(value, depth);
   1294    }
   1295 
   1296    // Add any properties we find on the event object.
   1297    if (!props.length) {
   1298      let i = 0;
   1299      for (const prop in safeRawObj) {
   1300        let value = safeRawObj[prop];
   1301        if (
   1302          prop == "target" ||
   1303          prop == "type" ||
   1304          value === null ||
   1305          typeof value == "function"
   1306        ) {
   1307          continue;
   1308        }
   1309        if (value && typeof value == "object") {
   1310          if (depth > 1) {
   1311            continue;
   1312          }
   1313          value = obj.makeDebuggeeValue(value);
   1314        }
   1315        preview.properties[prop] = objectActor.createValueGrip(value, depth);
   1316        if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
   1317          break;
   1318        }
   1319      }
   1320    }
   1321 
   1322    return true;
   1323  },
   1324 
   1325  function DOMException(objectActor, grip, depth) {
   1326    const { safeRawObj } = objectActor;
   1327    if (!safeRawObj || objectActor.className !== "DOMException" || isWorker) {
   1328      return false;
   1329    }
   1330 
   1331    grip.preview = {
   1332      kind: "DOMException",
   1333      name: objectActor.createValueGrip(safeRawObj.name, depth),
   1334      message: objectActor.createValueGrip(safeRawObj.message, depth),
   1335      code: objectActor.createValueGrip(safeRawObj.code, depth),
   1336      result: objectActor.createValueGrip(safeRawObj.result, depth),
   1337      filename: objectActor.createValueGrip(safeRawObj.filename, depth),
   1338      lineNumber: objectActor.createValueGrip(safeRawObj.lineNumber, depth),
   1339      columnNumber: objectActor.createValueGrip(safeRawObj.columnNumber, depth),
   1340      stack: objectActor.createValueGrip(safeRawObj.stack, depth),
   1341    };
   1342 
   1343    return true;
   1344  },
   1345 
   1346  function Object(objectActor, grip, depth) {
   1347    return GenericObject(objectActor, grip, depth);
   1348  },
   1349 ];
   1350 
   1351 module.exports = previewers;