DominatorTree.js (7498B)
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 5 "use strict"; 6 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 { assert } = require("resource://devtools/shared/DevToolsUtils.js"); 14 const { 15 createParentMap, 16 } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js"); 17 const Tree = createFactory( 18 require("resource://devtools/client/shared/components/VirtualizedTree.js") 19 ); 20 const DominatorTreeItem = createFactory( 21 require("resource://devtools/client/memory/components/DominatorTreeItem.js") 22 ); 23 const { L10N } = require("resource://devtools/client/memory/utils.js"); 24 const { 25 TREE_ROW_HEIGHT, 26 dominatorTreeState, 27 } = require("resource://devtools/client/memory/constants.js"); 28 const { 29 dominatorTreeModel, 30 } = require("resource://devtools/client/memory/models.js"); 31 const DominatorTreeLazyChildren = require("resource://devtools/client/memory/dominator-tree-lazy-children.js"); 32 33 const DOMINATOR_TREE_AUTO_EXPAND_DEPTH = 3; 34 35 /** 36 * A throbber that represents a subtree in the dominator tree that is actively 37 * being incrementally loaded and fetched from the `HeapAnalysesWorker`. 38 */ 39 class DominatorTreeSubtreeFetchingClass extends Component { 40 static get propTypes() { 41 return { 42 depth: PropTypes.number.isRequired, 43 }; 44 } 45 46 shouldComponentUpdate(nextProps) { 47 return this.props.depth !== nextProps.depth; 48 } 49 50 render() { 51 const { depth } = this.props; 52 53 return dom.div( 54 { 55 className: "heap-tree-item subtree-fetching", 56 }, 57 dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }), 58 dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }), 59 dom.span({ 60 className: "heap-tree-item-field heap-tree-item-name devtools-throbber", 61 style: { marginInlineStart: depth * TREE_ROW_HEIGHT }, 62 }) 63 ); 64 } 65 } 66 67 /** 68 * A link to fetch and load more siblings in the dominator tree, when there are 69 * already many loaded above. 70 */ 71 class DominatorTreeSiblingLinkClass extends Component { 72 static get propTypes() { 73 return { 74 depth: PropTypes.number.isRequired, 75 item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired, 76 onLoadMoreSiblings: PropTypes.func.isRequired, 77 }; 78 } 79 80 shouldComponentUpdate(nextProps) { 81 return this.props.depth !== nextProps.depth; 82 } 83 84 render() { 85 const { depth, item, onLoadMoreSiblings } = this.props; 86 87 return dom.div( 88 { 89 className: `heap-tree-item more-children`, 90 }, 91 dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }), 92 dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }), 93 dom.span( 94 { 95 className: "heap-tree-item-field heap-tree-item-name", 96 style: { marginInlineStart: depth * TREE_ROW_HEIGHT }, 97 }, 98 dom.a( 99 { 100 className: "load-more-link", 101 onClick: () => onLoadMoreSiblings(item), 102 }, 103 L10N.getStr("tree-item.load-more") 104 ) 105 ) 106 ); 107 } 108 } 109 110 class DominatorTree extends Component { 111 static get propTypes() { 112 return { 113 dominatorTree: dominatorTreeModel.isRequired, 114 onLoadMoreSiblings: PropTypes.func.isRequired, 115 onViewSourceInDebugger: PropTypes.func.isRequired, 116 onExpand: PropTypes.func.isRequired, 117 onCollapse: PropTypes.func.isRequired, 118 onFocus: PropTypes.func.isRequired, 119 }; 120 } 121 122 shouldComponentUpdate(nextProps) { 123 // Safe to use referential equality here because all of our mutations on 124 // dominator tree models use immutableUpdate in a persistent manner. The 125 // exception to the rule are mutations of the expanded set, however we take 126 // care that the dominatorTree model itself is still re-allocated when 127 // mutations to the expanded set occur. Because of the re-allocations, we 128 // can continue using referential equality here. 129 return this.props.dominatorTree !== nextProps.dominatorTree; 130 } 131 132 render() { 133 const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = 134 this.props; 135 136 const parentMap = createParentMap(dominatorTree.root, node => node.nodeId); 137 138 return Tree({ 139 key: "dominator-tree-tree", 140 autoExpandDepth: DOMINATOR_TREE_AUTO_EXPAND_DEPTH, 141 preventNavigationOnArrowRight: false, 142 focused: dominatorTree.focused, 143 getParent: node => 144 node instanceof DominatorTreeLazyChildren 145 ? parentMap[node.parentNodeId()] 146 : parentMap[node.nodeId], 147 getChildren: node => { 148 const children = node.children ? node.children.slice() : []; 149 if (node.moreChildrenAvailable) { 150 children.push( 151 new DominatorTreeLazyChildren(node.nodeId, children.length) 152 ); 153 } 154 return children; 155 }, 156 isExpanded: node => { 157 return node instanceof DominatorTreeLazyChildren 158 ? false 159 : dominatorTree.expanded.has(node.nodeId); 160 }, 161 onExpand: item => { 162 if (item instanceof DominatorTreeLazyChildren) { 163 return; 164 } 165 166 if ( 167 item.moreChildrenAvailable && 168 (!item.children || !item.children.length) 169 ) { 170 const startIndex = item.children ? item.children.length : 0; 171 onLoadMoreSiblings( 172 new DominatorTreeLazyChildren(item.nodeId, startIndex) 173 ); 174 } 175 176 this.props.onExpand(item); 177 }, 178 onCollapse: item => { 179 if (item instanceof DominatorTreeLazyChildren) { 180 return; 181 } 182 183 this.props.onCollapse(item); 184 }, 185 onFocus: item => { 186 if (item instanceof DominatorTreeLazyChildren) { 187 return; 188 } 189 190 this.props.onFocus(item); 191 }, 192 renderItem: (item, depth, focused, arrow, expanded) => { 193 if (item instanceof DominatorTreeLazyChildren) { 194 if (item.isFirstChild()) { 195 assert( 196 dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING, 197 "If we are displaying a throbber for loading a subtree, " + 198 "then we should be INCREMENTAL_FETCHING those children right now" 199 ); 200 return DominatorTreeSubtreeFetching({ 201 key: item.key(), 202 depth, 203 focused, 204 }); 205 } 206 207 return DominatorTreeSiblingLink({ 208 key: item.key(), 209 item, 210 depth, 211 onLoadMoreSiblings, 212 }); 213 } 214 215 return DominatorTreeItem({ 216 item, 217 depth, 218 arrow, 219 expanded, 220 getPercentSize: size => 221 (size / dominatorTree.root.retainedSize) * 100, 222 onViewSourceInDebugger, 223 }); 224 }, 225 getRoots: () => [dominatorTree.root], 226 getKey: node => 227 node instanceof DominatorTreeLazyChildren ? node.key() : node.nodeId, 228 itemHeight: TREE_ROW_HEIGHT, 229 }); 230 } 231 } 232 233 const DominatorTreeSubtreeFetching = createFactory( 234 DominatorTreeSubtreeFetchingClass 235 ); 236 const DominatorTreeSiblingLink = createFactory(DominatorTreeSiblingLinkClass); 237 238 module.exports = DominatorTree;