CensusTreeItem.js (5710B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { isSavedFrame } = require("resource://devtools/shared/DevToolsUtils.js"); 7 const { 8 Component, 9 createFactory, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 13 const { 14 L10N, 15 formatNumber, 16 formatPercent, 17 } = require("resource://devtools/client/memory/utils.js"); 18 const Frame = createFactory( 19 require("resource://devtools/client/shared/components/Frame.js") 20 ); 21 const { 22 TREE_ROW_HEIGHT, 23 } = require("resource://devtools/client/memory/constants.js"); 24 const models = require("resource://devtools/client/memory/models.js"); 25 26 class CensusTreeItem extends Component { 27 static get propTypes() { 28 return { 29 arrow: PropTypes.any, 30 depth: PropTypes.number.isRequired, 31 diffing: models.app.diffing, 32 expanded: PropTypes.bool.isRequired, 33 focused: PropTypes.bool.isRequired, 34 getPercentBytes: PropTypes.func.isRequired, 35 getPercentCount: PropTypes.func.isRequired, 36 inverted: PropTypes.bool, 37 item: PropTypes.object.isRequired, 38 onViewIndividuals: PropTypes.func.isRequired, 39 onViewSourceInDebugger: PropTypes.func.isRequired, 40 }; 41 } 42 43 constructor(props) { 44 super(props); 45 this.toLabel = this.toLabel.bind(this); 46 } 47 48 shouldComponentUpdate(nextProps) { 49 return ( 50 this.props.item != nextProps.item || 51 this.props.depth != nextProps.depth || 52 this.props.expanded != nextProps.expanded || 53 this.props.focused != nextProps.focused || 54 this.props.diffing != nextProps.diffing 55 ); 56 } 57 58 toLabel(name, onViewSourceInDebugger) { 59 if (isSavedFrame(name)) { 60 return Frame({ 61 frame: name, 62 onClick: onViewSourceInDebugger, 63 showFunctionName: true, 64 showHost: true, 65 }); 66 } 67 68 if (name === null) { 69 return L10N.getStr("tree-item.root"); 70 } 71 72 if (name === "noStack") { 73 return L10N.getStr("tree-item.nostack"); 74 } 75 76 if (name === "noFilename") { 77 return L10N.getStr("tree-item.nofilename"); 78 } 79 80 return String(name); 81 } 82 83 render() { 84 const { 85 item, 86 depth, 87 arrow, 88 focused, 89 getPercentBytes, 90 getPercentCount, 91 diffing, 92 onViewSourceInDebugger, 93 onViewIndividuals, 94 inverted, 95 } = this.props; 96 97 const bytes = formatNumber(item.bytes, !!diffing); 98 const percentBytes = formatPercent(getPercentBytes(item.bytes), !!diffing); 99 100 const count = formatNumber(item.count, !!diffing); 101 const percentCount = formatPercent(getPercentCount(item.count), !!diffing); 102 103 const totalBytes = formatNumber(item.totalBytes, !!diffing); 104 const percentTotalBytes = formatPercent( 105 getPercentBytes(item.totalBytes), 106 !!diffing 107 ); 108 109 const totalCount = formatNumber(item.totalCount, !!diffing); 110 const percentTotalCount = formatPercent( 111 getPercentCount(item.totalCount), 112 !!diffing 113 ); 114 115 let pointer; 116 if (inverted && depth > 0) { 117 pointer = dom.span({ className: "children-pointer" }, "↖"); 118 } else if (!inverted && item.children?.length) { 119 pointer = dom.span({ className: "children-pointer" }, "↘"); 120 } 121 122 let individualsCell; 123 if (!diffing) { 124 let individualsButton; 125 if (item.reportLeafIndex !== undefined) { 126 individualsButton = dom.button( 127 { 128 key: `individuals-button-${item.id}`, 129 title: L10N.getStr("tree-item.view-individuals.tooltip"), 130 className: "devtools-button individuals-button", 131 onClick: e => { 132 // Don't let the event bubble up to cause this item to focus after 133 // we have switched views, which would lead to assertion failures. 134 e.preventDefault(); 135 e.stopPropagation(); 136 137 onViewIndividuals(item); 138 }, 139 }, 140 "⁂" 141 ); 142 } 143 individualsCell = dom.span( 144 { className: "heap-tree-item-field heap-tree-item-individuals" }, 145 individualsButton 146 ); 147 } 148 149 return dom.div( 150 { className: `heap-tree-item ${focused ? "focused" : ""}` }, 151 dom.span( 152 { className: "heap-tree-item-field heap-tree-item-bytes" }, 153 dom.span({ className: "heap-tree-number" }, bytes), 154 dom.span({ className: "heap-tree-percent" }, percentBytes) 155 ), 156 dom.span( 157 { className: "heap-tree-item-field heap-tree-item-count" }, 158 dom.span({ className: "heap-tree-number" }, count), 159 dom.span({ className: "heap-tree-percent" }, percentCount) 160 ), 161 dom.span( 162 { className: "heap-tree-item-field heap-tree-item-total-bytes" }, 163 dom.span({ className: "heap-tree-number" }, totalBytes), 164 dom.span({ className: "heap-tree-percent" }, percentTotalBytes) 165 ), 166 dom.span( 167 { className: "heap-tree-item-field heap-tree-item-total-count" }, 168 dom.span({ className: "heap-tree-number" }, totalCount), 169 dom.span({ className: "heap-tree-percent" }, percentTotalCount) 170 ), 171 individualsCell, 172 dom.span( 173 { 174 className: "heap-tree-item-field heap-tree-item-name", 175 style: { marginInlineStart: depth * TREE_ROW_HEIGHT }, 176 }, 177 arrow, 178 pointer, 179 this.toLabel(item.name, onViewSourceInDebugger) 180 ) 181 ); 182 } 183 } 184 185 module.exports = CensusTreeItem;