/* 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 {
Component,
} = require(
"resource://devtools/client/shared/vendor/react.js");
const PropTypes = require(
"resource://devtools/client/shared/vendor/react-prop-types.js");
const dom = require(
"resource://devtools/client/shared/vendor/react-dom-factories.js");
const { isSavedFrame } = require(
"resource://devtools/shared/DevToolsUtils.js");
const {
getSourceNames,
} = require(
"resource://devtools/client/shared/source-utils.js");
const { L10N } = require(
"resource://devtools/client/memory/utils.js");
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)}`;
}
class ShortestPaths
extends Component {
static get propTypes() {
return {
graph: PropTypes.shape({
nodes: PropTypes.arrayOf(PropTypes.object),
edges: PropTypes.arrayOf(PropTypes.object),
}),
};
}
constructor(props) {
super(props);
this.state = { zoom:
null };
this._renderGraph =
this._renderGraph.bind(
this);
}
componentDidMount() {
if (
this.props.graph) {
this._renderGraph(
this.refs.container,
this.props.graph);
}
}
shouldComponentUpdate(nextProps) {
return this.props.graph != nextProps.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);
}
}
_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);
}
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
);
}
}
module.exports = ShortestPaths;