summaryrefslogtreecommitdiffstats
path: root/devtools/client/memory/components/shortest-paths.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/memory/components/shortest-paths.js')
-rw-r--r--devtools/client/memory/components/shortest-paths.js184
1 files changed, 184 insertions, 0 deletions
diff --git a/devtools/client/memory/components/shortest-paths.js b/devtools/client/memory/components/shortest-paths.js
new file mode 100644
index 000000000..23cb8134b
--- /dev/null
+++ b/devtools/client/memory/components/shortest-paths.js
@@ -0,0 +1,184 @@
+/* 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/. */
+"use strict";
+
+const {
+ DOM: dom,
+ createClass,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
+const { getSourceNames } = require("devtools/client/shared/source-utils");
+const { L10N } = require("../utils");
+
+const GRAPH_DEFAULTS = {
+ translate: [20, 20],
+ scale: 1
+};
+
+const NO_STACK = "noStack";
+const NO_FILENAME = "noFilename";
+const ROOT_LIST = "JS::ubi::RootList";
+
+function stringifyLabel(label, id) {
+ const sanitized = [];
+
+ for (let i = 0, length = label.length; i < length; i++) {
+ const piece = label[i];
+
+ if (isSavedFrame(piece)) {
+ const { short } = getSourceNames(piece.source);
+ sanitized[i] = `${piece.functionDisplayName} @ ${short}:${piece.line}:${piece.column}`;
+ } else if (piece === NO_STACK) {
+ sanitized[i] = L10N.getStr("tree-item.nostack");
+ } else if (piece === NO_FILENAME) {
+ sanitized[i] = L10N.getStr("tree-item.nofilename");
+ } else if (piece === ROOT_LIST) {
+ // Don't use the usual labeling machinery for root lists: replace it
+ // with the "GC Roots" string.
+ sanitized.splice(0, label.length);
+ sanitized.push(L10N.getStr("tree-item.rootlist"));
+ break;
+ } else {
+ sanitized[i] = "" + piece;
+ }
+ }
+
+ return `${sanitized.join(" › ")} @ 0x${id.toString(16)}`;
+}
+
+module.exports = createClass({
+ displayName: "ShortestPaths",
+
+ propTypes: {
+ graph: PropTypes.shape({
+ nodes: PropTypes.arrayOf(PropTypes.object),
+ edges: PropTypes.arrayOf(PropTypes.object),
+ }),
+ },
+
+ getInitialState() {
+ return { zoom: null };
+ },
+
+ shouldComponentUpdate(nextProps) {
+ return this.props.graph != nextProps.graph;
+ },
+
+ componentDidMount() {
+ if (this.props.graph) {
+ this._renderGraph(this.refs.container, this.props.graph);
+ }
+ },
+
+ componentDidUpdate() {
+ if (this.props.graph) {
+ this._renderGraph(this.refs.container, this.props.graph);
+ }
+ },
+
+ componentWillUnmount() {
+ if (this.state.zoom) {
+ this.state.zoom.on("zoom", null);
+ }
+ },
+
+ render() {
+ let contents;
+ if (this.props.graph) {
+ // Let the componentDidMount or componentDidUpdate method draw the graph
+ // with DagreD3. We just provide the container for the graph here.
+ contents = dom.div({
+ ref: "container",
+ style: {
+ flex: 1,
+ height: "100%",
+ width: "100%",
+ }
+ });
+ } else {
+ contents = dom.div(
+ {
+ id: "shortest-paths-select-node-msg"
+ },
+ L10N.getStr("shortest-paths.select-node")
+ );
+ }
+
+ return dom.div(
+ {
+ id: "shortest-paths",
+ className: "vbox",
+ },
+ dom.label(
+ {
+ id: "shortest-paths-header",
+ className: "header",
+ },
+ L10N.getStr("shortest-paths.header")
+ ),
+ contents
+ );
+ },
+
+ _renderGraph(container, { nodes, edges }) {
+ if (!container.firstChild) {
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("id", "graph-svg");
+ svg.setAttribute("xlink", "http://www.w3.org/1999/xlink");
+ svg.style.width = "100%";
+ svg.style.height = "100%";
+
+ const target = document.createElementNS("http://www.w3.org/2000/svg", "g");
+ target.setAttribute("id", "graph-target");
+ target.style.width = "100%";
+ target.style.height = "100%";
+
+ svg.appendChild(target);
+ container.appendChild(svg);
+ }
+
+ const graph = new dagreD3.Digraph();
+
+ for (let i = 0; i < nodes.length; i++) {
+ graph.addNode(nodes[i].id, {
+ id: nodes[i].id,
+ label: stringifyLabel(nodes[i].label, nodes[i].id),
+ });
+ }
+
+ for (let i = 0; i < edges.length; i++) {
+ graph.addEdge(null, edges[i].from, edges[i].to, {
+ label: edges[i].name
+ });
+ }
+
+ const renderer = new dagreD3.Renderer();
+ renderer.drawNodes();
+ renderer.drawEdgePaths();
+
+ const svg = d3.select("#graph-svg");
+ const target = d3.select("#graph-target");
+
+ let zoom = this.state.zoom;
+ if (!zoom) {
+ zoom = d3.behavior.zoom().on("zoom", function () {
+ target.attr(
+ "transform",
+ `translate(${d3.event.translate}) scale(${d3.event.scale})`
+ );
+ });
+ svg.call(zoom);
+ this.setState({ zoom });
+ }
+
+ const { translate, scale } = GRAPH_DEFAULTS;
+ zoom.scale(scale);
+ zoom.translate(translate);
+ target.attr("transform", `translate(${translate}) scale(${scale})`);
+
+ const layout = dagreD3.layout();
+ renderer.layout(layout).run(graph, target);
+ },
+});