tor-browser

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

node.js (26818B)


      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 const {
      6  maybeEscapePropertyName,
      7 } = ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/rep-utils.mjs", {global: "current"});
      8 const ArrayRep = ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/array.mjs", {global: "current"});
      9 const GripArrayRep = ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/grip-array.mjs", {global: "current"});
     10 const GripMap =  ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/grip-map.mjs", {global: "current"});
     11 const GripEntryRep =  ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/grip-entry.mjs", {global: "current"});
     12 const ErrorRep =  ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/error.mjs", {global: "current"});
     13 const BigIntRep =  ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/big-int.mjs", {global: "current"});
     14 const {
     15  isLongString,
     16 } = ChromeUtils.importESModule("resource://devtools/client/shared/components/reps/reps/string.mjs", {global: "current"});
     17 
     18 const MAX_NUMERICAL_PROPERTIES = 100;
     19 
     20 const NODE_TYPES = {
     21  BUCKET: Symbol("[n…m]"),
     22  DEFAULT_PROPERTIES: Symbol("<default properties>"),
     23  ENTRIES: Symbol("<entries>"),
     24  GET: Symbol("<get>"),
     25  GRIP: Symbol("GRIP"),
     26  MAP_ENTRY_KEY: Symbol("<key>"),
     27  MAP_ENTRY_VALUE: Symbol("<value>"),
     28  PROMISE_REASON: Symbol("<reason>"),
     29  PROMISE_STATE: Symbol("<state>"),
     30  PROMISE_VALUE: Symbol("<value>"),
     31  PROXY_HANDLER: Symbol("<handler>"),
     32  PROXY_TARGET: Symbol("<target>"),
     33  SET: Symbol("<set>"),
     34  PROTOTYPE: Symbol("<prototype>"),
     35  BLOCK: Symbol("☲"),
     36  PRIMITIVE_VALUE: Symbol("<primitive value>")
     37 };
     38 
     39 let WINDOW_PROPERTIES = {};
     40 
     41 if (typeof window === "object") {
     42  WINDOW_PROPERTIES = Object.getOwnPropertyNames(window);
     43 }
     44 
     45 function getType(item) {
     46  return item.type;
     47 }
     48 
     49 function getValue(item) {
     50  if (nodeHasValue(item)) {
     51    return item.contents.value;
     52  }
     53 
     54  if (nodeHasGetterValue(item)) {
     55    return item.contents.getterValue;
     56  }
     57 
     58  if (nodeHasAccessors(item)) {
     59    return item.contents;
     60  }
     61 
     62  return undefined;
     63 }
     64 
     65 function getFront(item) {
     66  return item && item.contents && item.contents.front;
     67 }
     68 
     69 function getActor(item, roots) {
     70  const isRoot = isNodeRoot(item, roots);
     71  const value = getValue(item);
     72  return isRoot || !value ? null : value.actor;
     73 }
     74 
     75 function isNodeRoot(item, roots) {
     76  const gripItem = getClosestGripNode(item);
     77  const value = getValue(gripItem);
     78 
     79  return (
     80    value &&
     81    roots.some(root => {
     82      const rootValue = getValue(root);
     83      return rootValue && rootValue.actor === value.actor;
     84    })
     85  );
     86 }
     87 
     88 function nodeIsBucket(item) {
     89  return getType(item) === NODE_TYPES.BUCKET;
     90 }
     91 
     92 function nodeIsEntries(item) {
     93  return getType(item) === NODE_TYPES.ENTRIES;
     94 }
     95 
     96 function nodeIsMapEntry(item) {
     97  return GripEntryRep.supportsObject(getValue(item));
     98 }
     99 
    100 function nodeHasChildren(item) {
    101  return Array.isArray(item.contents);
    102 }
    103 
    104 function nodeHasValue(item) {
    105  return item && item.contents && item.contents.hasOwnProperty("value");
    106 }
    107 
    108 function nodeHasGetterValue(item) {
    109  return item && item.contents && item.contents.hasOwnProperty("getterValue");
    110 }
    111 
    112 function nodeIsObject(item) {
    113  const value = getValue(item);
    114  return value && value.type === "object";
    115 }
    116 
    117 function nodeIsArrayLike(item) {
    118  const value = getValue(item);
    119  return GripArrayRep.supportsObject(value) || ArrayRep.supportsObject(value);
    120 }
    121 
    122 function nodeIsFunction(item) {
    123  const value = getValue(item);
    124  return value && value.class === "Function";
    125 }
    126 
    127 function nodeIsOptimizedOut(item) {
    128  const value = getValue(item);
    129  return !nodeHasChildren(item) && value && value.optimizedOut;
    130 }
    131 
    132 function nodeIsUninitializedBinding(item) {
    133  const value = getValue(item);
    134  return value && value.uninitialized;
    135 }
    136 
    137 // Used to check if an item represents a binding that exists in a sourcemap's
    138 // original file content, but does not match up with a binding found in the
    139 // generated code.
    140 function nodeIsUnmappedBinding(item) {
    141  const value = getValue(item);
    142  return value && value.unmapped;
    143 }
    144 
    145 // Used to check if an item represents a binding that exists in the debugger's
    146 // parser result, but does not match up with a binding returned by the
    147 // devtools server.
    148 function nodeIsUnscopedBinding(item) {
    149  const value = getValue(item);
    150  return value && value.unscoped;
    151 }
    152 
    153 function nodeIsMissingArguments(item) {
    154  const value = getValue(item);
    155  return !nodeHasChildren(item) && value && value.missingArguments;
    156 }
    157 
    158 function nodeHasProperties(item) {
    159  return !nodeHasChildren(item) && nodeIsObject(item);
    160 }
    161 
    162 function nodeIsPrimitive(item) {
    163  return (
    164    nodeIsBigInt(item) ||
    165    (!nodeHasChildren(item) &&
    166      !nodeHasProperties(item) &&
    167      !nodeIsEntries(item) &&
    168      !nodeIsMapEntry(item) &&
    169      !nodeHasAccessors(item) &&
    170      !nodeIsBucket(item) &&
    171      !nodeIsLongString(item))
    172  );
    173 }
    174 
    175 function nodeIsDefaultProperties(item) {
    176  return getType(item) === NODE_TYPES.DEFAULT_PROPERTIES;
    177 }
    178 
    179 function isDefaultWindowProperty(name) {
    180  return WINDOW_PROPERTIES.includes(name);
    181 }
    182 
    183 function nodeIsPromise(item) {
    184  const value = getValue(item);
    185  if (!value) {
    186    return false;
    187  }
    188 
    189  return value.class == "Promise";
    190 }
    191 
    192 function nodeIsProxy(item) {
    193  const value = getValue(item);
    194  if (!value) {
    195    return false;
    196  }
    197 
    198  return value.class == "Proxy";
    199 }
    200 
    201 function nodeIsPrototype(item) {
    202  return getType(item) === NODE_TYPES.PROTOTYPE;
    203 }
    204 
    205 function nodeIsWindow(item) {
    206  const value = getValue(item);
    207  if (!value) {
    208    return false;
    209  }
    210 
    211  return value.class == "Window";
    212 }
    213 
    214 function nodeIsGetter(item) {
    215  return getType(item) === NODE_TYPES.GET;
    216 }
    217 
    218 function nodeIsSetter(item) {
    219  return getType(item) === NODE_TYPES.SET;
    220 }
    221 
    222 function nodeIsBlock(item) {
    223  return getType(item) === NODE_TYPES.BLOCK;
    224 }
    225 
    226 function nodeIsError(item) {
    227  return ErrorRep.supportsObject(getValue(item));
    228 }
    229 
    230 function nodeIsLongString(item) {
    231  return isLongString(getValue(item));
    232 }
    233 
    234 function nodeIsBigInt(item) {
    235  return BigIntRep.supportsObject(getValue(item));
    236 }
    237 
    238 function nodeHasFullText(item) {
    239  const value = getValue(item);
    240  return nodeIsLongString(item) && value.hasOwnProperty("fullText");
    241 }
    242 
    243 function nodeHasGetter(item) {
    244  const getter = getNodeGetter(item);
    245  return getter && getter.type !== "undefined";
    246 }
    247 
    248 function nodeHasSetter(item) {
    249  const setter = getNodeSetter(item);
    250  return setter && setter.type !== "undefined";
    251 }
    252 
    253 function nodeHasAccessors(item) {
    254  return nodeHasGetter(item) || nodeHasSetter(item);
    255 }
    256 
    257 function nodeSupportsNumericalBucketing(item) {
    258  // We exclude elements with entries since it's the <entries> node
    259  // itself that can have buckets.
    260  return (
    261    (nodeIsArrayLike(item) && !nodeHasEntries(item)) ||
    262    nodeIsEntries(item) ||
    263    nodeIsBucket(item)
    264  );
    265 }
    266 
    267 function nodeHasEntries(item) {
    268  const value = getValue(item);
    269  if (!value) {
    270    return false;
    271  }
    272 
    273  const className = value.class;
    274  return (
    275    className === "Map" ||
    276    className === "Set" ||
    277    className === "WeakMap" ||
    278    className === "WeakSet" ||
    279    className === "Storage" ||
    280    className === "URLSearchParams" ||
    281    className === "Headers" ||
    282    className === "FormData" ||
    283    className === "MIDIInputMap" ||
    284    className === "MIDIOutputMap" ||
    285    className === "HighlightRegistry" ||
    286    className === "CustomStateSet"
    287  );
    288 }
    289 
    290 function nodeNeedsNumericalBuckets(item) {
    291  return (
    292    nodeSupportsNumericalBucketing(item) &&
    293    getNumericalPropertiesCount(item) > MAX_NUMERICAL_PROPERTIES
    294  );
    295 }
    296 
    297 function makeNodesForPromiseProperties(loadedProps, item) {
    298  const { reason, value, state } = loadedProps.promiseState;
    299  const properties = [];
    300 
    301  if (state) {
    302    properties.push(
    303      createNode({
    304        parent: item,
    305        name: "<state>",
    306        contents: { value: state },
    307        type: NODE_TYPES.PROMISE_STATE,
    308      })
    309    );
    310  }
    311 
    312  if (reason) {
    313    properties.push(
    314      createNode({
    315        parent: item,
    316        name: "<reason>",
    317        contents: {
    318          value: reason.getGrip ? reason.getGrip() : reason,
    319          front: reason.getGrip ? reason : null,
    320        },
    321        type: NODE_TYPES.PROMISE_REASON,
    322      })
    323    );
    324  }
    325 
    326  if (value) {
    327    properties.push(
    328      createNode({
    329        parent: item,
    330        name: "<value>",
    331        contents: {
    332          value: value.getGrip ? value.getGrip() : value,
    333          front: value.getGrip ? value : null,
    334        },
    335        type: NODE_TYPES.PROMISE_VALUE,
    336      })
    337    );
    338  }
    339 
    340  return properties;
    341 }
    342 
    343 function makeNodesForProxyProperties(loadedProps, item) {
    344  const { proxyHandler, proxyTarget } = loadedProps;
    345 
    346  const isProxyHandlerFront = proxyHandler && proxyHandler.getGrip;
    347  const proxyHandlerGrip = isProxyHandlerFront
    348    ? proxyHandler.getGrip()
    349    : proxyHandler;
    350  const proxyHandlerFront = isProxyHandlerFront ? proxyHandler : null;
    351 
    352  const isProxyTargetFront = proxyTarget && proxyTarget.getGrip;
    353  const proxyTargetGrip = isProxyTargetFront
    354    ? proxyTarget.getGrip()
    355    : proxyTarget;
    356  const proxyTargetFront = isProxyTargetFront ? proxyTarget : null;
    357 
    358  return [
    359    createNode({
    360      parent: item,
    361      name: "<target>",
    362      contents: { value: proxyTargetGrip, front: proxyTargetFront },
    363      type: NODE_TYPES.PROXY_TARGET,
    364    }),
    365    createNode({
    366      parent: item,
    367      name: "<handler>",
    368      contents: { value: proxyHandlerGrip, front: proxyHandlerFront },
    369      type: NODE_TYPES.PROXY_HANDLER,
    370    }),
    371  ];
    372 }
    373 
    374 function makeNodesForEntries(item) {
    375  const nodeName = "<entries>";
    376 
    377  return createNode({
    378    parent: item,
    379    name: nodeName,
    380    contents: null,
    381    type: NODE_TYPES.ENTRIES,
    382  });
    383 }
    384 
    385 function makeNodeForPrimitiveValue(parent, value) {
    386  const nodeName = "<primitive value>";
    387 
    388  return createNode({
    389    parent,
    390    name: nodeName,
    391    contents: {value},
    392    type: NODE_TYPES.PRIMITIVE_VALUE,
    393  });
    394 }
    395 
    396 function makeNodesForMapEntry(item) {
    397  const nodeValue = getValue(item);
    398  if (!nodeValue || !nodeValue.preview) {
    399    return [];
    400  }
    401 
    402  const { key, value } = nodeValue.preview;
    403  const isKeyFront = key && key.getGrip;
    404  const keyGrip = isKeyFront ? key.getGrip() : key;
    405  const keyFront = isKeyFront ? key : null;
    406 
    407  const isValueFront = value && value.getGrip;
    408  const valueGrip = isValueFront ? value.getGrip() : value;
    409  const valueFront = isValueFront ? value : null;
    410 
    411  return [
    412    createNode({
    413      parent: item,
    414      name: "<key>",
    415      contents: { value: keyGrip, front: keyFront },
    416      type: NODE_TYPES.MAP_ENTRY_KEY,
    417    }),
    418    createNode({
    419      parent: item,
    420      name: "<value>",
    421      contents: { value: valueGrip, front: valueFront },
    422      type: NODE_TYPES.MAP_ENTRY_VALUE,
    423    }),
    424  ];
    425 }
    426 
    427 function getNodeGetter(item) {
    428  return item && item.contents ? item.contents.get : undefined;
    429 }
    430 
    431 function getNodeSetter(item) {
    432  return item && item.contents ? item.contents.set : undefined;
    433 }
    434 
    435 function sortProperties(properties) {
    436  return properties.sort((a, b) => {
    437    // Sort numbers in ascending order and sort strings lexicographically
    438    const aInt = parseInt(a, 10);
    439    const bInt = parseInt(b, 10);
    440 
    441    if (isNaN(aInt) || isNaN(bInt)) {
    442      return a > b ? 1 : -1;
    443    }
    444 
    445    return aInt - bInt;
    446  });
    447 }
    448 
    449 function makeNumericalBuckets(parent) {
    450  const numProperties = getNumericalPropertiesCount(parent);
    451 
    452  // We want to have at most a hundred slices.
    453  const bucketSize =
    454    10 ** Math.max(2, Math.ceil(Math.log10(numProperties)) - 2);
    455  const numBuckets = Math.ceil(numProperties / bucketSize);
    456 
    457  const buckets = [];
    458  for (let i = 1; i <= numBuckets; i++) {
    459    const minKey = (i - 1) * bucketSize;
    460    const maxKey = Math.min(i * bucketSize - 1, numProperties - 1);
    461    const startIndex = nodeIsBucket(parent) ? parent.meta.startIndex : 0;
    462    const minIndex = startIndex + minKey;
    463    const maxIndex = startIndex + maxKey;
    464    const bucketName = `[${minIndex}${maxIndex}]`;
    465 
    466    buckets.push(
    467      createNode({
    468        parent,
    469        name: bucketName,
    470        contents: null,
    471        type: NODE_TYPES.BUCKET,
    472        meta: {
    473          startIndex: minIndex,
    474          endIndex: maxIndex,
    475        },
    476      })
    477    );
    478  }
    479  return buckets;
    480 }
    481 
    482 function makeDefaultPropsBucket(propertiesNames, parent, ownProperties) {
    483  const userPropertiesNames = [];
    484  const defaultProperties = [];
    485 
    486  propertiesNames.forEach(name => {
    487    if (isDefaultWindowProperty(name)) {
    488      defaultProperties.push(name);
    489    } else {
    490      userPropertiesNames.push(name);
    491    }
    492  });
    493 
    494  const nodes = makeNodesForOwnProps(
    495    userPropertiesNames,
    496    parent,
    497    ownProperties
    498  );
    499 
    500  if (defaultProperties.length) {
    501    const defaultPropertiesNode = createNode({
    502      parent,
    503      name: "<default properties>",
    504      contents: null,
    505      type: NODE_TYPES.DEFAULT_PROPERTIES,
    506    });
    507 
    508    const defaultNodes = makeNodesForOwnProps(
    509      defaultProperties,
    510      defaultPropertiesNode,
    511      ownProperties
    512    );
    513    nodes.push(setNodeChildren(defaultPropertiesNode, defaultNodes));
    514  }
    515  return nodes;
    516 }
    517 
    518 function makeNodesForOwnProps(propertiesNames, parent, ownProperties) {
    519  return propertiesNames.map(name => {
    520    const property = ownProperties[name];
    521 
    522    let propertyValue = property;
    523    if (property && property.hasOwnProperty("getterValue")) {
    524      propertyValue = property.getterValue;
    525    } else if (property && property.hasOwnProperty("value")) {
    526      propertyValue = property.value;
    527    }
    528 
    529    // propertyValue can be a front (LongString or Object) or a primitive grip.
    530    const isFront = propertyValue && propertyValue.getGrip;
    531    const front = isFront ? propertyValue : null;
    532    const grip = isFront ? front.getGrip() : propertyValue;
    533 
    534    return createNode({
    535      parent,
    536      name: maybeEscapePropertyName(name),
    537      propertyName: name,
    538      contents: {
    539        ...(property || {}),
    540        value: grip,
    541        front,
    542      },
    543    });
    544  });
    545 }
    546 
    547 // eslint-disable-next-line complexity
    548 function makeNodesForProperties(objProps, parent) {
    549  const {
    550    ownProperties = {},
    551    ownSymbols,
    552    privateProperties,
    553    prototype,
    554    safeGetterValues,
    555  } = objProps;
    556 
    557  const parentValue = getValue(parent);
    558  const allProperties = { ...ownProperties, ...safeGetterValues };
    559 
    560  // Ignore properties that are neither non-concrete nor getters/setters.
    561  const propertiesNames = sortProperties(Object.keys(allProperties)).filter(
    562    name => {
    563      if (!allProperties[name]) {
    564        return false;
    565      }
    566 
    567      const properties = Object.getOwnPropertyNames(allProperties[name]);
    568      return properties.some(property =>
    569        ["value", "getterValue", "get", "set"].includes(property)
    570      );
    571    }
    572  );
    573 
    574  const isParentNodeWindow = parentValue && parentValue.class == "Window";
    575  const nodes = isParentNodeWindow
    576    ? makeDefaultPropsBucket(propertiesNames, parent, allProperties)
    577    : makeNodesForOwnProps(propertiesNames, parent, allProperties);
    578 
    579  if (Array.isArray(ownSymbols)) {
    580    ownSymbols.forEach((ownSymbol, index) => {
    581      const descriptorValue = ownSymbol?.descriptor?.value;
    582      const hasGrip = descriptorValue?.getGrip;
    583      const symbolGrip = hasGrip ? descriptorValue.getGrip() : descriptorValue;
    584      const symbolFront = hasGrip ? ownSymbol.descriptor.value : null;
    585 
    586      nodes.push(
    587        createNode({
    588          parent,
    589          name: ownSymbol.name,
    590          path: `symbol-${index}`,
    591          contents: {
    592            value: symbolGrip,
    593            front: symbolFront,
    594          },
    595        })
    596      );
    597    }, this);
    598  }
    599 
    600  if (Array.isArray(privateProperties)) {
    601    privateProperties.forEach((privateProperty, index) => {
    602      const descriptorValue = privateProperty?.descriptor?.value;
    603      const hasGrip = descriptorValue?.getGrip;
    604      const privatePropertyGrip = hasGrip
    605        ? descriptorValue.getGrip()
    606        : descriptorValue;
    607      const privatePropertyFront = hasGrip
    608        ? privateProperty.descriptor.value
    609        : null;
    610 
    611      nodes.push(
    612        createNode({
    613          parent,
    614          name: privateProperty.name,
    615          path: `private-${index}`,
    616          contents: {
    617            value: privatePropertyGrip,
    618            front: privatePropertyFront,
    619          },
    620        })
    621      );
    622    }, this);
    623  }
    624 
    625  if (nodeIsPromise(parent)) {
    626    nodes.push(...makeNodesForPromiseProperties(objProps, parent));
    627  }
    628 
    629  if (nodeHasEntries(parent)) {
    630    nodes.push(makeNodesForEntries(parent));
    631  }
    632 
    633  // Add accessor nodes if needed
    634  const defaultPropertiesNode = isParentNodeWindow
    635    ? nodes.find(node => nodeIsDefaultProperties(node))
    636    : null;
    637 
    638  for (const name of propertiesNames) {
    639    const property = allProperties[name];
    640    const isDefaultProperty =
    641      isParentNodeWindow &&
    642      defaultPropertiesNode &&
    643      isDefaultWindowProperty(name);
    644    const parentNode = isDefaultProperty ? defaultPropertiesNode : parent;
    645    const parentContentsArray =
    646      isDefaultProperty && defaultPropertiesNode
    647        ? defaultPropertiesNode.contents
    648        : nodes;
    649 
    650    if (property.get && property.get.type !== "undefined") {
    651      parentContentsArray.push(
    652        createGetterNode({
    653          parent: parentNode,
    654          property,
    655          name,
    656        })
    657      );
    658    }
    659 
    660    if (property.set && property.set.type !== "undefined") {
    661      parentContentsArray.push(
    662        createSetterNode({
    663          parent: parentNode,
    664          property,
    665          name,
    666        })
    667      );
    668    }
    669  }
    670 
    671  const preview = parentValue?.preview;
    672 
    673  if (preview && Object.hasOwn(preview, 'wrappedValue')) {
    674    const primitiveValue = preview.wrappedValue
    675    nodes.push(makeNodeForPrimitiveValue(parentValue, primitiveValue))
    676  }
    677 
    678  // Add the prototype if it exists and is not null
    679  if (prototype && prototype.type !== "null") {
    680    nodes.push(makeNodeForPrototype(objProps, parent));
    681  }
    682 
    683  return nodes;
    684 }
    685 
    686 function setNodeFullText(loadedProps, node) {
    687  if (nodeHasFullText(node) || !nodeIsLongString(node)) {
    688    return node;
    689  }
    690 
    691  const { fullText } = loadedProps;
    692  if (nodeHasValue(node)) {
    693    node.contents.value.fullText = fullText;
    694  } else if (nodeHasGetterValue(node)) {
    695    node.contents.getterValue.fullText = fullText;
    696  }
    697 
    698  return node;
    699 }
    700 
    701 function makeNodeForPrototype(objProps, parent) {
    702  const { prototype } = objProps || {};
    703 
    704  // Add the prototype if it exists and is not null
    705  if (prototype && prototype.type !== "null") {
    706    return createNode({
    707      parent,
    708      name: "<prototype>",
    709      contents: {
    710        value: prototype.getGrip ? prototype.getGrip() : prototype,
    711        front: prototype.getGrip ? prototype : null,
    712      },
    713      type: NODE_TYPES.PROTOTYPE,
    714    });
    715  }
    716 
    717  return null;
    718 }
    719 
    720 function createNode(options) {
    721  const {
    722    parent,
    723    name,
    724    propertyName,
    725    path,
    726    contents,
    727    type = NODE_TYPES.GRIP,
    728    meta,
    729  } = options;
    730 
    731  if (contents === undefined) {
    732    return null;
    733  }
    734 
    735  // The path is important to uniquely identify the item in the entire
    736  // tree. This helps debugging & optimizes React's rendering of large
    737  // lists. The path will be separated by property name.
    738 
    739  return {
    740    parent,
    741    name,
    742    // `name` can be escaped; propertyName contains the original property name.
    743    propertyName,
    744    path: createPath(parent && parent.path, path || name),
    745    contents,
    746    type,
    747    meta,
    748  };
    749 }
    750 
    751 function createGetterNode({ parent, property, name }) {
    752  const isFront = property.get && property.get.getGrip;
    753  const grip = isFront ? property.get.getGrip() : property.get;
    754  const front = isFront ? property.get : null;
    755 
    756  return createNode({
    757    parent,
    758    name: `<get ${name}()>`,
    759    contents: { value: grip, front },
    760    type: NODE_TYPES.GET,
    761  });
    762 }
    763 
    764 function createSetterNode({ parent, property, name }) {
    765  const isFront = property.set && property.set.getGrip;
    766  const grip = isFront ? property.set.getGrip() : property.set;
    767  const front = isFront ? property.set : null;
    768 
    769  return createNode({
    770    parent,
    771    name: `<set ${name}()>`,
    772    contents: { value: grip, front },
    773    type: NODE_TYPES.SET,
    774  });
    775 }
    776 
    777 function setNodeChildren(node, children) {
    778  node.contents = children;
    779  return node;
    780 }
    781 
    782 function getEvaluatedItem(item, evaluations) {
    783  if (!evaluations.has(item.path)) {
    784    return item;
    785  }
    786 
    787  const evaluation = evaluations.get(item.path);
    788  const isFront =
    789    evaluation && evaluation.getterValue && evaluation.getterValue.getGrip;
    790 
    791  const contents = isFront
    792    ? {
    793        getterValue: evaluation.getterValue.getGrip(),
    794        front: evaluation.getterValue,
    795      }
    796    : evaluations.get(item.path);
    797 
    798  return {
    799    ...item,
    800    contents,
    801  };
    802 }
    803 
    804 function getChildrenWithEvaluations(options) {
    805  const { item, loadedProperties, cachedNodes, evaluations } = options;
    806 
    807  const children = getChildren({
    808    loadedProperties,
    809    cachedNodes,
    810    item,
    811  });
    812 
    813  if (Array.isArray(children)) {
    814    return children.map(i => getEvaluatedItem(i, evaluations));
    815  }
    816 
    817  if (children) {
    818    return getEvaluatedItem(children, evaluations);
    819  }
    820 
    821  return [];
    822 }
    823 
    824 function getChildren(options) {
    825  const { cachedNodes, item, loadedProperties = new Map() } = options;
    826 
    827  const key = item.path;
    828  if (cachedNodes && cachedNodes.has(key)) {
    829    return cachedNodes.get(key);
    830  }
    831 
    832  const loadedProps = loadedProperties.get(key);
    833  const hasLoadedProps = loadedProperties.has(key);
    834 
    835  // Because we are dynamically creating the tree as the user
    836  // expands it (not precalculated tree structure), we cache child
    837  // arrays. This not only helps performance, but is necessary
    838  // because the expanded state depends on instances of nodes
    839  // being the same across renders. If we didn't do this, each
    840  // node would be a new instance every render.
    841  // If the node needs properties, we only add children to
    842  // the cache if the properties are loaded.
    843  const addToCache = children => {
    844    if (cachedNodes) {
    845      cachedNodes.set(item.path, children);
    846    }
    847    return children;
    848  };
    849 
    850  // Nodes can either have children already, or be an object with
    851  // properties that we need to go and fetch.
    852  if (nodeHasChildren(item)) {
    853    return addToCache(item.contents);
    854  }
    855 
    856  if (nodeIsMapEntry(item)) {
    857    return addToCache(makeNodesForMapEntry(item));
    858  }
    859 
    860  if (nodeIsProxy(item) && hasLoadedProps) {
    861    return addToCache(makeNodesForProxyProperties(loadedProps, item));
    862  }
    863 
    864  if (nodeIsLongString(item) && hasLoadedProps) {
    865    // Set longString object's fullText to fetched one.
    866    return addToCache(setNodeFullText(loadedProps, item));
    867  }
    868 
    869  if (nodeNeedsNumericalBuckets(item) && hasLoadedProps) {
    870    // Even if we have numerical buckets, we should have loaded non indexed
    871    // properties.
    872    const bucketNodes = makeNumericalBuckets(item);
    873    return addToCache(
    874      bucketNodes.concat(makeNodesForProperties(loadedProps, item))
    875    );
    876  }
    877 
    878  if (!nodeIsEntries(item) && !nodeIsBucket(item) && !nodeHasProperties(item)) {
    879    return [];
    880  }
    881 
    882  if (!hasLoadedProps) {
    883    return [];
    884  }
    885 
    886  return addToCache(makeNodesForProperties(loadedProps, item));
    887 }
    888 
    889 // Builds an expression that resolves to the value of the item in question
    890 // e.g. `b` in { a: { b: 2 } } resolves to `a.b`
    891 function getPathExpression(item) {
    892  if (item && item.parent) {
    893    const parent = nodeIsBucket(item.parent) ? item.parent.parent : item.parent;
    894    return `${getPathExpression(parent)}.${item.name}`;
    895  }
    896 
    897  return item.name;
    898 }
    899 
    900 function getParent(item) {
    901  return item.parent;
    902 }
    903 
    904 function getNumericalPropertiesCount(item) {
    905  if (nodeIsBucket(item)) {
    906    return item.meta.endIndex - item.meta.startIndex + 1;
    907  }
    908 
    909  const value = getValue(getClosestGripNode(item));
    910  if (!value) {
    911    return 0;
    912  }
    913 
    914  if (GripArrayRep.supportsObject(value)) {
    915    return GripArrayRep.getLength(value);
    916  }
    917 
    918  if (GripMap.supportsObject(value)) {
    919    return GripMap.getLength(value);
    920  }
    921 
    922  // TODO: We can also have numerical properties on Objects, but at the
    923  // moment we don't have a way to distinguish them from non-indexed properties,
    924  // as they are all computed in a ownPropertiesLength property.
    925 
    926  return 0;
    927 }
    928 
    929 function getClosestGripNode(item) {
    930  const type = getType(item);
    931  if (
    932    type !== NODE_TYPES.BUCKET &&
    933    type !== NODE_TYPES.DEFAULT_PROPERTIES &&
    934    type !== NODE_TYPES.ENTRIES
    935  ) {
    936    return item;
    937  }
    938 
    939  const parent = getParent(item);
    940  if (!parent) {
    941    return null;
    942  }
    943 
    944  return getClosestGripNode(parent);
    945 }
    946 
    947 function getClosestNonBucketNode(item) {
    948  const type = getType(item);
    949 
    950  if (type !== NODE_TYPES.BUCKET) {
    951    return item;
    952  }
    953 
    954  const parent = getParent(item);
    955  if (!parent) {
    956    return null;
    957  }
    958 
    959  return getClosestNonBucketNode(parent);
    960 }
    961 
    962 function getParentGripNode(item) {
    963  const parentNode = getParent(item);
    964  if (!parentNode) {
    965    return null;
    966  }
    967 
    968  return getClosestGripNode(parentNode);
    969 }
    970 
    971 function getParentGripValue(item) {
    972  const parentGripNode = getParentGripNode(item);
    973  if (!parentGripNode) {
    974    return null;
    975  }
    976 
    977  return getValue(parentGripNode);
    978 }
    979 
    980 function getParentFront(item) {
    981  const parentGripNode = getParentGripNode(item);
    982  if (!parentGripNode) {
    983    return null;
    984  }
    985 
    986  return getFront(parentGripNode);
    987 }
    988 
    989 function getNonPrototypeParentGripValue(item) {
    990  const parentGripNode = getParentGripNode(item);
    991  if (!parentGripNode) {
    992    return null;
    993  }
    994 
    995  if (getType(parentGripNode) === NODE_TYPES.PROTOTYPE) {
    996    return getNonPrototypeParentGripValue(parentGripNode);
    997  }
    998 
    999  return getValue(parentGripNode);
   1000 }
   1001 
   1002 function createPath(parentPath, path) {
   1003  return parentPath ? `${parentPath}${path}` : path;
   1004 }
   1005 
   1006 module.exports = {
   1007  createNode,
   1008  createGetterNode,
   1009  createSetterNode,
   1010  getActor,
   1011  getChildren,
   1012  getChildrenWithEvaluations,
   1013  getClosestGripNode,
   1014  getClosestNonBucketNode,
   1015  getEvaluatedItem,
   1016  getFront,
   1017  getPathExpression,
   1018  getParent,
   1019  getParentFront,
   1020  getParentGripValue,
   1021  getNonPrototypeParentGripValue,
   1022  getNumericalPropertiesCount,
   1023  getValue,
   1024  makeNodesForEntries,
   1025  makeNodesForPromiseProperties,
   1026  makeNodesForProperties,
   1027  makeNumericalBuckets,
   1028  nodeHasAccessors,
   1029  nodeHasChildren,
   1030  nodeHasEntries,
   1031  nodeHasProperties,
   1032  nodeHasGetter,
   1033  nodeHasSetter,
   1034  nodeIsBlock,
   1035  nodeIsBucket,
   1036  nodeIsDefaultProperties,
   1037  nodeIsEntries,
   1038  nodeIsError,
   1039  nodeIsLongString,
   1040  nodeHasFullText,
   1041  nodeIsFunction,
   1042  nodeIsGetter,
   1043  nodeIsMapEntry,
   1044  nodeIsMissingArguments,
   1045  nodeIsObject,
   1046  nodeIsOptimizedOut,
   1047  nodeIsPrimitive,
   1048  nodeIsPromise,
   1049  nodeIsPrototype,
   1050  nodeIsProxy,
   1051  nodeIsSetter,
   1052  nodeIsUninitializedBinding,
   1053  nodeIsUnmappedBinding,
   1054  nodeIsUnscopedBinding,
   1055  nodeIsWindow,
   1056  nodeNeedsNumericalBuckets,
   1057  nodeSupportsNumericalBucketing,
   1058  setNodeChildren,
   1059  sortProperties,
   1060  NODE_TYPES,
   1061 };