tor-browser

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

property-iterator.js (22113B)


      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.js");
      8 const {
      9  propertyIteratorSpec,
     10 } = require("resource://devtools/shared/specs/property-iterator.js");
     11 
     12 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     13 loader.lazyRequireGetter(
     14  this,
     15  "ObjectUtils",
     16  "resource://devtools/server/actors/object/utils.js"
     17 );
     18 loader.lazyRequireGetter(
     19  this,
     20  "propertyDescriptor",
     21  "resource://devtools/server/actors/object/property-descriptor.js",
     22  true
     23 );
     24 
     25 /**
     26 * Creates an actor to iterate over an object's property names and values.
     27 *
     28 * @param objectActor ObjectActor
     29 *        The object actor.
     30 * @param options Object
     31 *        A dictionary object with various boolean attributes:
     32 *        - enumEntries Boolean
     33 *          If true, enumerates the entries of a Map or Set object
     34 *          instead of enumerating properties.
     35 *        - ignoreIndexedProperties Boolean
     36 *          If true, filters out Array items.
     37 *          e.g. properties names between `0` and `object.length`.
     38 *        - ignoreNonIndexedProperties Boolean
     39 *          If true, filters out items that aren't array items
     40 *          e.g. properties names that are not a number between `0`
     41 *          and `object.length`.
     42 *        - sort Boolean
     43 *          If true, the iterator will sort the properties by name
     44 *          before dispatching them.
     45 *        - query String
     46 *          If non-empty, will filter the properties by names and values
     47 *          containing this query string. The match is not case-sensitive.
     48 *          Regarding value filtering it just compare to the stringification
     49 *          of the property value.
     50 */
     51 class PropertyIteratorActor extends Actor {
     52    constructor(objectActor, options, conn) {
     53      super(conn, propertyIteratorSpec);
     54      if (!DevToolsUtils.isSafeDebuggerObject(objectActor.obj)) {
     55        this.iterator = {
     56          size: 0,
     57          propertyName: _index => undefined,
     58          propertyDescription: _index => undefined,
     59        };
     60      } else if (options.enumEntries) {
     61        const cls = objectActor.className;
     62        if (cls == "Map") {
     63          this.iterator = enumMapEntries(objectActor, 0);
     64        } else if (cls == "WeakMap") {
     65          this.iterator = enumWeakMapEntries(objectActor, 0);
     66        } else if (cls == "Set") {
     67          this.iterator = enumSetEntries(objectActor, 0);
     68        } else if (cls == "WeakSet") {
     69          this.iterator = enumWeakSetEntries(objectActor, 0);
     70        } else if (cls == "Storage") {
     71          this.iterator = enumStorageEntries(objectActor, 0);
     72        } else if (cls == "URLSearchParams") {
     73          this.iterator = enumURLSearchParamsEntries(objectActor, 0);
     74        } else if (cls == "Headers") {
     75          this.iterator = enumHeadersEntries(objectActor, 0);
     76        } else if (cls == "HighlightRegistry") {
     77          this.iterator = enumHighlightRegistryEntries(objectActor, 0);
     78        } else if (cls == "FormData") {
     79          this.iterator = enumFormDataEntries(objectActor, 0);
     80        } else if (cls == "MIDIInputMap") {
     81          this.iterator = enumMidiInputMapEntries(objectActor, 0);
     82        } else if (cls == "MIDIOutputMap") {
     83          this.iterator = enumMidiOutputMapEntries(objectActor, 0);
     84        } else if (cls == "CustomStateSet") {
     85          this.iterator = enumCustomStateSetEntries(objectActor, 0);
     86        } else {
     87          throw new Error(
     88            "Unsupported class to enumerate entries from: " + cls
     89          );
     90        }
     91      } else if (
     92        ObjectUtils.isArray(objectActor.obj) &&
     93        options.ignoreNonIndexedProperties &&
     94        !options.query
     95      ) {
     96        this.iterator = enumArrayProperties(objectActor, options, 0);
     97      } else {
     98        this.iterator = enumObjectProperties(objectActor, options, 0);
     99      }
    100    }
    101 
    102    form() {
    103      return {
    104        type: this.typeName,
    105        actor: this.actorID,
    106        count: this.iterator.size,
    107      };
    108    }
    109 
    110    names({ indexes }) {
    111      const list = [];
    112      for (const idx of indexes) {
    113        list.push(this.iterator.propertyName(idx));
    114      }
    115      return indexes;
    116    }
    117 
    118    slice({ start, count }) {
    119      const ownProperties = Object.create(null);
    120      for (let i = start, m = start + count; i < m; i++) {
    121        const name = this.iterator.propertyName(i);
    122        ownProperties[name] = this.iterator.propertyDescription(i);
    123      }
    124 
    125      return {
    126        ownProperties,
    127      };
    128    }
    129 
    130    all() {
    131      return this.slice({ start: 0, count: this.iterator.size });
    132    }
    133  }
    134 
    135 function waiveXrays(obj) {
    136  return isWorker ? obj : Cu.waiveXrays(obj);
    137 }
    138 
    139 function unwaiveXrays(obj) {
    140  return isWorker ? obj : Cu.unwaiveXrays(obj);
    141 }
    142 
    143 /**
    144 * Helper function to create a grip from a Map/Set entry
    145 */
    146 function gripFromEntry(objectActor, entry, depth) {
    147  entry = unwaiveXrays(entry);
    148  return objectActor.createValueGrip(
    149    ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, entry),
    150    depth
    151  );
    152 }
    153 
    154 function enumArrayProperties(objectActor, options, depth) {
    155  return {
    156    size: ObjectUtils.getArrayLength(objectActor.obj),
    157    propertyName(index) {
    158      return index;
    159    },
    160    propertyDescription(index) {
    161      return propertyDescriptor(objectActor, index, depth);
    162    },
    163  };
    164 }
    165 
    166 function enumObjectProperties(objectActor, options, depth) {
    167  let names = [];
    168  try {
    169    names = objectActor.obj.getOwnPropertyNames();
    170  } catch (ex) {
    171    // Calling getOwnPropertyNames() on some wrapped native prototypes is not
    172    // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
    173  }
    174 
    175  if (options.ignoreNonIndexedProperties || options.ignoreIndexedProperties) {
    176    const length = DevToolsUtils.getProperty(objectActor.obj, "length");
    177    let sliceIndex;
    178 
    179    const isLengthTrustworthy =
    180      isUint32(length) &&
    181      (!length || ObjectUtils.isArrayIndex(names[length - 1])) &&
    182      !ObjectUtils.isArrayIndex(names[length]);
    183 
    184    if (!isLengthTrustworthy) {
    185      // The length property may not reflect what the object looks like, let's find
    186      // where indexed properties end.
    187 
    188      if (!ObjectUtils.isArrayIndex(names[0])) {
    189        // If the first item is not a number, this means there is no indexed properties
    190        // in this object.
    191        sliceIndex = 0;
    192      } else {
    193        sliceIndex = names.length;
    194        while (sliceIndex > 0) {
    195          if (ObjectUtils.isArrayIndex(names[sliceIndex - 1])) {
    196            break;
    197          }
    198          sliceIndex--;
    199        }
    200      }
    201    } else {
    202      sliceIndex = length;
    203    }
    204 
    205    // It appears that getOwnPropertyNames always returns indexed properties
    206    // first, so we can safely slice `names` for/against indexed properties.
    207    // We do such clever operation to optimize very large array inspection.
    208    if (options.ignoreIndexedProperties) {
    209      // Keep items after `sliceIndex` index
    210      names = names.slice(sliceIndex);
    211    } else if (options.ignoreNonIndexedProperties) {
    212      // Keep `sliceIndex` first items
    213      names.length = sliceIndex;
    214    }
    215  }
    216 
    217  const safeGetterValues = objectActor._findSafeGetterValues(names, depth);
    218  const safeGetterNames = Object.keys(safeGetterValues);
    219  // Merge the safe getter values into the existing properties list.
    220  for (const name of safeGetterNames) {
    221    if (!names.includes(name)) {
    222      names.push(name);
    223    }
    224  }
    225 
    226  if (options.query) {
    227    let { query } = options;
    228    query = query.toLowerCase();
    229    names = names.filter(name => {
    230      // Filter on attribute names
    231      if (name.toLowerCase().includes(query)) {
    232        return true;
    233      }
    234      // and then on attribute values
    235      let desc;
    236      try {
    237        desc = objectActor.obj.getOwnPropertyDescriptor(name);
    238      } catch (e) {
    239        // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
    240        // allowed (bug 560072).
    241      }
    242      if (desc?.value && String(desc.value).includes(query)) {
    243        return true;
    244      }
    245      return false;
    246    });
    247  }
    248 
    249  if (options.sort) {
    250    names.sort();
    251  }
    252 
    253  return {
    254    size: names.length,
    255    propertyName(index) {
    256      return names[index];
    257    },
    258    propertyDescription(index) {
    259      const name = names[index];
    260      let desc = propertyDescriptor(objectActor, name, depth);
    261      if (!desc) {
    262        desc = safeGetterValues[name];
    263      } else if (name in safeGetterValues) {
    264        // Merge the safe getter values into the existing properties list.
    265        const { getterValue, getterPrototypeLevel } = safeGetterValues[name];
    266        desc.getterValue = getterValue;
    267        desc.getterPrototypeLevel = getterPrototypeLevel;
    268      }
    269      return desc;
    270    },
    271  };
    272 }
    273 
    274 function getMapEntries(objectActor) {
    275  const { obj, rawObj } = objectActor;
    276  // Iterating over a Map via .entries goes through various intermediate
    277  // objects - an Iterator object, then a 2-element Array object, then the
    278  // actual values we care about. We don't have Xrays to Iterator objects,
    279  // so we get Opaque wrappers for them. And even though we have Xrays to
    280  // Arrays, the semantics often deny access to the entires based on the
    281  // nature of the values. So we need waive Xrays for the iterator object
    282  // and the tupes, and then re-apply them on the underlying values until
    283  // we fix bug 1023984.
    284  //
    285  // Even then though, we might want to continue waiving Xrays here for the
    286  // same reason we do so for Arrays above - this filtering behavior is likely
    287  // to be more confusing than beneficial in the case of Object previews.
    288  const iterator = obj.makeDebuggeeValue(
    289    waiveXrays(Map.prototype.keys.call(rawObj))
    290  );
    291  return [...DevToolsUtils.makeDebuggeeIterator(iterator)].map(k => {
    292    const key = waiveXrays(ObjectUtils.unwrapDebuggeeValue(k));
    293    const value = Map.prototype.get.call(rawObj, key);
    294    return [key, value];
    295  });
    296 }
    297 
    298 function enumMapEntries(objectActor, depth) {
    299  const entries = getMapEntries(objectActor);
    300 
    301  return {
    302    *[Symbol.iterator]() {
    303      for (const [key, value] of entries) {
    304        yield [key, value].map(val => gripFromEntry(objectActor, val, depth));
    305      }
    306    },
    307    size: entries.length,
    308    propertyName(index) {
    309      return index;
    310    },
    311    propertyDescription(index) {
    312      const [key, val] = entries[index];
    313      return {
    314        enumerable: true,
    315        value: {
    316          type: "mapEntry",
    317          preview: {
    318            key: gripFromEntry(objectActor, key, depth),
    319            value: gripFromEntry(objectActor, val, depth),
    320          },
    321        },
    322      };
    323    },
    324  };
    325 }
    326 
    327 function enumStorageEntries(objectActor, depth) {
    328  // Iterating over local / sessionStorage entries goes through various
    329  // intermediate objects - an Iterator object, then a 2-element Array object,
    330  // then the actual values we care about. We don't have Xrays to Iterator
    331  // objects, so we get Opaque wrappers for them.
    332  const { rawObj } = objectActor;
    333  const keys = [];
    334  for (let i = 0; i < rawObj.length; i++) {
    335    keys.push(rawObj.key(i));
    336  }
    337  return {
    338    *[Symbol.iterator]() {
    339      for (const key of keys) {
    340        const value = rawObj.getItem(key);
    341        yield [key, value].map(val => gripFromEntry(objectActor, val, depth));
    342      }
    343    },
    344    size: keys.length,
    345    propertyName(index) {
    346      return index;
    347    },
    348    propertyDescription(index) {
    349      const key = keys[index];
    350      const val = rawObj.getItem(key);
    351      return {
    352        enumerable: true,
    353        value: {
    354          type: "storageEntry",
    355          preview: {
    356            key: gripFromEntry(objectActor, key, depth),
    357            value: gripFromEntry(objectActor, val, depth),
    358          },
    359        },
    360      };
    361    },
    362  };
    363 }
    364 
    365 function enumURLSearchParamsEntries(objectActor, depth) {
    366  const entries = [...waiveXrays(URLSearchParams.prototype.entries.call(objectActor.rawObj))];
    367 
    368  return {
    369    *[Symbol.iterator]() {
    370      for (const [key, value] of entries) {
    371        yield [key, value];
    372      }
    373    },
    374    size: entries.length,
    375    propertyName(index) {
    376      // UrlSearchParams entries can have the same key multiple times (e.g. `?a=1&a=2`),
    377      // so let's return the index as a name to be able to display them properly in the client.
    378      return index;
    379    },
    380    propertyDescription(index) {
    381      const [key, value] = entries[index];
    382 
    383      return {
    384        enumerable: true,
    385        value: {
    386          type: "urlSearchParamsEntry",
    387          preview: {
    388            key: gripFromEntry(objectActor, key, depth),
    389            value: gripFromEntry(objectActor, value, depth),
    390          },
    391        },
    392      };
    393    },
    394  };
    395 }
    396 
    397 function enumFormDataEntries(objectActor, depth) {
    398  const entries = [...waiveXrays(FormData.prototype.entries.call(objectActor.rawObj))];
    399 
    400  return {
    401    *[Symbol.iterator]() {
    402      for (const [key, value] of entries) {
    403        yield [key, value];
    404      }
    405    },
    406    size: entries.length,
    407    propertyName(index) {
    408      return index;
    409    },
    410    propertyDescription(index) {
    411      const [key, value] = entries[index];
    412 
    413      return {
    414        enumerable: true,
    415        value: {
    416          type: "formDataEntry",
    417          preview: {
    418            key: gripFromEntry(objectActor, key, depth),
    419            value: gripFromEntry(objectActor, value, depth),
    420          },
    421        },
    422      };
    423    },
    424  };
    425 }
    426 
    427 function enumHeadersEntries(objectActor, depth) {
    428  const entries = [...waiveXrays(Headers.prototype.entries.call(objectActor.rawObj))];
    429 
    430  return {
    431    *[Symbol.iterator]() {
    432      for (const [key, value] of entries) {
    433        yield [key, value];
    434      }
    435    },
    436    size: entries.length,
    437    propertyName(index) {
    438      return entries[index][0];
    439    },
    440    propertyDescription(index) {
    441      return {
    442        enumerable: true,
    443        value: gripFromEntry(objectActor, entries[index][1], depth),
    444      };
    445    },
    446  };
    447 }
    448 
    449 function enumHighlightRegistryEntries(objectActor, depth) {
    450  const entriesFuncDbgObj = objectActor.obj.getProperty("entries").return;
    451  const entriesDbgObj = entriesFuncDbgObj ? entriesFuncDbgObj.call(objectActor.obj).return : null;
    452  const entries = entriesDbgObj
    453    ? [...waiveXrays( entriesDbgObj.unsafeDereference())]
    454    : [];
    455 
    456  return {
    457    *[Symbol.iterator]() {
    458      for (const [key, value] of entries) {
    459        yield [key, gripFromEntry(objectActor, value, depth)];
    460      }
    461    },
    462    size: entries.length,
    463    propertyName(index) {
    464      return index;
    465    },
    466    propertyDescription(index) {
    467      const [key, value] = entries[index];
    468      return {
    469        enumerable: true,
    470        value: {
    471          type: "highlightRegistryEntry",
    472          preview: {
    473            key,
    474            value: gripFromEntry(objectActor, value, depth),
    475          },
    476        },
    477      };
    478    },
    479  };
    480 }
    481 
    482 function enumMidiInputMapEntries(objectActor, depth) {
    483  // We need to waive `rawObj` as we can't get the iterator from the Xray for MapLike (See Bug 1173651).
    484  // We also need to waive Xrays on the result of the call to `entries` as we don't have
    485  // Xrays to Iterator objects (see Bug 1023984)
    486  const entries = Array.from(
    487    waiveXrays(MIDIInputMap.prototype.entries.call(waiveXrays(objectActor.rawObj)))
    488  );
    489 
    490  return {
    491    *[Symbol.iterator]() {
    492      for (const [key, value] of entries) {
    493        yield [key, gripFromEntry(objectActor, value, depth)];
    494      }
    495    },
    496    size: entries.length,
    497    propertyName(index) {
    498      return entries[index][0];
    499    },
    500    propertyDescription(index) {
    501      return {
    502        enumerable: true,
    503        value: gripFromEntry(objectActor, entries[index][1], depth),
    504      };
    505    },
    506  };
    507 }
    508 
    509 function enumMidiOutputMapEntries(objectActor, depth) {
    510  // We need to waive `rawObj` as we can't get the iterator from the Xray for MapLike (See Bug 1173651).
    511  // We also need to waive Xrays on the result of the call to `entries` as we don't have
    512  // Xrays to Iterator objects (see Bug 1023984)
    513  const entries = Array.from(
    514    waiveXrays(MIDIOutputMap.prototype.entries.call(waiveXrays(objectActor.rawObj)))
    515  );
    516 
    517  return {
    518    *[Symbol.iterator]() {
    519      for (const [key, value] of entries) {
    520        yield [key, gripFromEntry(objectActor, value, depth)];
    521      }
    522    },
    523    size: entries.length,
    524    propertyName(index) {
    525      return entries[index][0];
    526    },
    527    propertyDescription(index) {
    528      return {
    529        enumerable: true,
    530        value: gripFromEntry(objectActor, entries[index][1], depth),
    531      };
    532    },
    533  };
    534 }
    535 
    536 function getWeakMapEntries(rawObj) {
    537  // We currently lack XrayWrappers for WeakMap, so when we iterate over
    538  // the values, the temporary iterator objects get created in the target
    539  // compartment. However, we _do_ have Xrays to Object now, so we end up
    540  // Xraying those temporary objects, and filtering access to |it.value|
    541  // based on whether or not it's Xrayable and/or callable, which breaks
    542  // the for/of iteration.
    543  //
    544  // This code is designed to handle untrusted objects, so we can safely
    545  // waive Xrays on the iterable, and relying on the Debugger machinery to
    546  // make sure we handle the resulting objects carefully.
    547  const keys = waiveXrays(ChromeUtils.nondeterministicGetWeakMapKeys(rawObj));
    548 
    549  return keys.map(k => [k, WeakMap.prototype.get.call(rawObj, k)]);
    550 }
    551 
    552 function enumWeakMapEntries(objectActor, depth) {
    553  const entries = getWeakMapEntries(objectActor.rawObj);
    554 
    555  return {
    556    *[Symbol.iterator]() {
    557      for (let i = 0; i < entries.length; i++) {
    558        yield entries[i].map(val => gripFromEntry(objectActor, val, depth));
    559      }
    560    },
    561    size: entries.length,
    562    propertyName(index) {
    563      return index;
    564    },
    565    propertyDescription(index) {
    566      const [key, val] = entries[index];
    567      return {
    568        enumerable: true,
    569        value: {
    570          type: "mapEntry",
    571          preview: {
    572            key: gripFromEntry(objectActor, key, depth),
    573            value: gripFromEntry(objectActor, val, depth),
    574          },
    575        },
    576      };
    577    },
    578  };
    579 }
    580 
    581 function getSetValues(objectActor) {
    582  // We currently lack XrayWrappers for Set, so when we iterate over
    583  // the values, the temporary iterator objects get created in the target
    584  // compartment. However, we _do_ have Xrays to Object now, so we end up
    585  // Xraying those temporary objects, and filtering access to |it.value|
    586  // based on whether or not it's Xrayable and/or callable, which breaks
    587  // the for/of iteration.
    588  //
    589  // This code is designed to handle untrusted objects, so we can safely
    590  // waive Xrays on the iterable, and relying on the Debugger machinery to
    591  // make sure we handle the resulting objects carefully.
    592  const iterator = objectActor.obj.makeDebuggeeValue(
    593    waiveXrays(Set.prototype.values.call(objectActor.rawObj))
    594  );
    595  return [...DevToolsUtils.makeDebuggeeIterator(iterator)];
    596 }
    597 
    598 function enumSetEntries(objectActor, depth) {
    599  const values = getSetValues(objectActor).map(v =>
    600    waiveXrays(ObjectUtils.unwrapDebuggeeValue(v))
    601  );
    602 
    603  return {
    604    *[Symbol.iterator]() {
    605      for (const item of values) {
    606        yield gripFromEntry(objectActor, item, depth);
    607      }
    608    },
    609    size: values.length,
    610    propertyName(index) {
    611      return index;
    612    },
    613    propertyDescription(index) {
    614      const val = values[index];
    615      return {
    616        enumerable: true,
    617        value: gripFromEntry(objectActor, val, depth),
    618      };
    619    },
    620  };
    621 }
    622 
    623 function getWeakSetEntries(rawObj) {
    624  // We currently lack XrayWrappers for WeakSet, so when we iterate over
    625  // the values, the temporary iterator objects get created in the target
    626  // compartment. However, we _do_ have Xrays to Object now, so we end up
    627  // Xraying those temporary objects, and filtering access to |it.value|
    628  // based on whether or not it's Xrayable and/or callable, which breaks
    629  // the for/of iteration.
    630  //
    631  // This code is designed to handle untrusted objects, so we can safely
    632  // waive Xrays on the iterable, and relying on the Debugger machinery to
    633  // make sure we handle the resulting objects carefully.
    634  return waiveXrays(ChromeUtils.nondeterministicGetWeakSetKeys(rawObj));
    635 }
    636 
    637 function enumWeakSetEntries(objectActor, depth) {
    638  const keys = getWeakSetEntries(objectActor.rawObj);
    639 
    640  return {
    641    *[Symbol.iterator]() {
    642      for (const item of keys) {
    643        yield gripFromEntry(objectActor, item, depth);
    644      }
    645    },
    646    size: keys.length,
    647    propertyName(index) {
    648      return index;
    649    },
    650    propertyDescription(index) {
    651      const val = keys[index];
    652      return {
    653        enumerable: true,
    654        value: gripFromEntry(objectActor, val, depth),
    655      };
    656    },
    657  };
    658 }
    659 
    660 function enumCustomStateSetEntries(objectActor, depth) {
    661  const { rawObj } = objectActor;
    662  // We need to waive `rawObj` as we can't get the iterator from the Xray for SetLike (See Bug 1173651).
    663  // We also need to waive Xrays on the result of the call to `values` as we don't have
    664  // Xrays to Iterator objects (see Bug 1023984)
    665  const values = Array.from(
    666    // eslint-disable-next-line no-undef
    667    waiveXrays(CustomStateSet.prototype.values.call(waiveXrays(rawObj)))
    668  );
    669 
    670  return {
    671    *[Symbol.iterator]() {
    672      for (const item of values) {
    673        yield gripFromEntry(objectActor, item, depth);
    674      }
    675    },
    676    size: values.length,
    677    propertyName(index) {
    678      return index;
    679    },
    680    propertyDescription(index) {
    681      const val = values[index];
    682      return {
    683        enumerable: true,
    684        value: gripFromEntry(objectActor, val, depth),
    685      };
    686    },
    687  };
    688 }
    689 
    690 /**
    691 * Returns true if the parameter can be stored as a 32-bit unsigned integer.
    692 * If so, it will be suitable for use as the length of an array object.
    693 *
    694 * @param num Number
    695 *        The number to test.
    696 * @return Boolean
    697 */
    698 function isUint32(num) {
    699  return num >>> 0 === num;
    700 }
    701 
    702 module.exports = {
    703  PropertyIteratorActor,
    704  enumCustomStateSetEntries,
    705  enumMapEntries,
    706  enumMidiInputMapEntries,
    707  enumMidiOutputMapEntries,
    708  enumSetEntries,
    709  enumURLSearchParamsEntries,
    710  enumFormDataEntries,
    711  enumHeadersEntries,
    712  enumHighlightRegistryEntries,
    713  enumWeakMapEntries,
    714  enumWeakSetEntries,
    715 };