summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/components/dominator-tree.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/components/dominator-tree.js')
-rw-r--r--devtools/client/memory/components/dominator-tree.js216
1 files changed, 216 insertions, 0 deletions
diff --git a/devtools/client/memory/components/dominator-tree.js b/devtools/client/memory/components/dominator-tree.js
new file mode 100644
index 000000000..a1105a4fc
--- /dev/null
+++ b/devtools/client/memory/components/dominator-tree.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils");
+const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
+const Tree = createFactory(require("devtools/client/shared/components/tree"));
+const DominatorTreeItem = createFactory(require("./dominator-tree-item"));
+const { L10N } = require("../utils");
+const { TREE_ROW_HEIGHT, dominatorTreeState } = require("../constants");
+const { dominatorTreeModel } = require("../models");
+const DominatorTreeLazyChildren = require("../dominator-tree-lazy-children");
+
+const DOMINATOR_TREE_AUTO_EXPAND_DEPTH = 3;
+
+/**
+ * A throbber that represents a subtree in the dominator tree that is actively
+ * being incrementally loaded and fetched from the `HeapAnalysesWorker`.
+ */
+const DominatorTreeSubtreeFetching = createFactory(createClass({
+ displayName: "DominatorTreeSubtreeFetching",
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return this.props.depth !== nextProps.depth
+ || this.props.focused !== nextProps.focused;
+ },
+
+ propTypes: {
+ depth: PropTypes.number.isRequired,
+ focused: PropTypes.bool.isRequired,
+ },
+
+ render() {
+ let {
+ depth,
+ focused,
+ } = this.props;
+
+ return dom.div(
+ {
+ className: `heap-tree-item subtree-fetching ${focused ? "focused" : ""}`
+ },
+ dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
+ dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
+ dom.span({
+ className: "heap-tree-item-field heap-tree-item-name devtools-throbber",
+ style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
+ })
+ );
+ }
+}));
+
+/**
+ * A link to fetch and load more siblings in the dominator tree, when there are
+ * already many loaded above.
+ */
+const DominatorTreeSiblingLink = createFactory(createClass({
+ displayName: "DominatorTreeSiblingLink",
+
+ propTypes: {
+ depth: PropTypes.number.isRequired,
+ focused: PropTypes.bool.isRequired,
+ item: PropTypes.instanceOf(DominatorTreeLazyChildren).isRequired,
+ onLoadMoreSiblings: PropTypes.func.isRequired,
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return this.props.depth !== nextProps.depth
+ || this.props.focused !== nextProps.focused;
+ },
+
+ render() {
+ let {
+ depth,
+ focused,
+ item,
+ onLoadMoreSiblings,
+ } = this.props;
+
+ return dom.div(
+ {
+ className: `heap-tree-item more-children ${focused ? "focused" : ""}`
+ },
+ dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
+ dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }),
+ dom.span(
+ {
+ className: "heap-tree-item-field heap-tree-item-name",
+ style: { marginInlineStart: depth * TREE_ROW_HEIGHT }
+ },
+ dom.a(
+ {
+ onClick: () => onLoadMoreSiblings(item)
+ },
+ L10N.getStr("tree-item.load-more")
+ )
+ )
+ );
+ }
+}));
+
+/**
+ * The actual dominator tree rendered as an expandable and collapsible tree.
+ */
+const DominatorTree = module.exports = createClass({
+ displayName: "DominatorTree",
+
+ propTypes: {
+ dominatorTree: dominatorTreeModel.isRequired,
+ onLoadMoreSiblings: PropTypes.func.isRequired,
+ onViewSourceInDebugger: PropTypes.func.isRequired,
+ onExpand: PropTypes.func.isRequired,
+ onCollapse: PropTypes.func.isRequired,
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ // Safe to use referential equality here because all of our mutations on
+ // dominator tree models use immutableUpdate in a persistent manner. The
+ // exception to the rule are mutations of the expanded set, however we take
+ // care that the dominatorTree model itself is still re-allocated when
+ // mutations to the expanded set occur. Because of the re-allocations, we
+ // can continue using referential equality here.
+ return this.props.dominatorTree !== nextProps.dominatorTree;
+ },
+
+ render() {
+ const { dominatorTree, onViewSourceInDebugger, onLoadMoreSiblings } = this.props;
+
+ const parentMap = createParentMap(dominatorTree.root, node => node.nodeId);
+
+ return Tree({
+ key: "dominator-tree-tree",
+ autoExpandDepth: DOMINATOR_TREE_AUTO_EXPAND_DEPTH,
+ focused: dominatorTree.focused,
+ getParent: node =>
+ node instanceof DominatorTreeLazyChildren
+ ? parentMap[node.parentNodeId()]
+ : parentMap[node.nodeId],
+ getChildren: node => {
+ const children = node.children ? node.children.slice() : [];
+ if (node.moreChildrenAvailable) {
+ children.push(new DominatorTreeLazyChildren(node.nodeId, children.length));
+ }
+ return children;
+ },
+ isExpanded: node => {
+ return node instanceof DominatorTreeLazyChildren
+ ? false
+ : dominatorTree.expanded.has(node.nodeId);
+ },
+ onExpand: item => {
+ if (item instanceof DominatorTreeLazyChildren) {
+ return;
+ }
+
+ if (item.moreChildrenAvailable && (!item.children || !item.children.length)) {
+ const startIndex = item.children ? item.children.length : 0;
+ onLoadMoreSiblings(new DominatorTreeLazyChildren(item.nodeId, startIndex));
+ }
+
+ this.props.onExpand(item);
+ },
+ onCollapse: item => {
+ if (item instanceof DominatorTreeLazyChildren) {
+ return;
+ }
+
+ this.props.onCollapse(item);
+ },
+ onFocus: item => {
+ if (item instanceof DominatorTreeLazyChildren) {
+ return;
+ }
+
+ this.props.onFocus(item);
+ },
+ renderItem: (item, depth, focused, arrow, expanded) => {
+ if (item instanceof DominatorTreeLazyChildren) {
+ if (item.isFirstChild()) {
+ assert(dominatorTree.state === dominatorTreeState.INCREMENTAL_FETCHING,
+ "If we are displaying a throbber for loading a subtree, " +
+ "then we should be INCREMENTAL_FETCHING those children right now");
+ return DominatorTreeSubtreeFetching({
+ key: item.key(),
+ depth,
+ focused,
+ });
+ }
+
+ return DominatorTreeSiblingLink({
+ key: item.key(),
+ item,
+ depth,
+ focused,
+ onLoadMoreSiblings,
+ });
+ }
+
+ return DominatorTreeItem({
+ item,
+ depth,
+ focused,
+ arrow,
+ expanded,
+ getPercentSize: size => (size / dominatorTree.root.retainedSize) * 100,
+ onViewSourceInDebugger,
+ });
+ },
+ getRoots: () => [dominatorTree.root],
+ getKey: node =>
+ node instanceof DominatorTreeLazyChildren ? node.key() : node.nodeId,
+ itemHeight: TREE_ROW_HEIGHT,
+ });
+ }
+});