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 };