shortest-paths.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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. const {
  6. DOM: dom,
  7. createClass,
  8. PropTypes,
  9. } = require("devtools/client/shared/vendor/react");
  10. const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
  11. const { getSourceNames } = require("devtools/client/shared/source-utils");
  12. const { L10N } = require("../utils");
  13. const GRAPH_DEFAULTS = {
  14. translate: [20, 20],
  15. scale: 1
  16. };
  17. const NO_STACK = "noStack";
  18. const NO_FILENAME = "noFilename";
  19. const ROOT_LIST = "JS::ubi::RootList";
  20. function stringifyLabel(label, id) {
  21. const sanitized = [];
  22. for (let i = 0, length = label.length; i < length; i++) {
  23. const piece = label[i];
  24. if (isSavedFrame(piece)) {
  25. const { short } = getSourceNames(piece.source);
  26. sanitized[i] = `${piece.functionDisplayName} @ ${short}:${piece.line}:${piece.column}`;
  27. } else if (piece === NO_STACK) {
  28. sanitized[i] = L10N.getStr("tree-item.nostack");
  29. } else if (piece === NO_FILENAME) {
  30. sanitized[i] = L10N.getStr("tree-item.nofilename");
  31. } else if (piece === ROOT_LIST) {
  32. // Don't use the usual labeling machinery for root lists: replace it
  33. // with the "GC Roots" string.
  34. sanitized.splice(0, label.length);
  35. sanitized.push(L10N.getStr("tree-item.rootlist"));
  36. break;
  37. } else {
  38. sanitized[i] = "" + piece;
  39. }
  40. }
  41. return `${sanitized.join(" › ")} @ 0x${id.toString(16)}`;
  42. }
  43. module.exports = createClass({
  44. displayName: "ShortestPaths",
  45. propTypes: {
  46. graph: PropTypes.shape({
  47. nodes: PropTypes.arrayOf(PropTypes.object),
  48. edges: PropTypes.arrayOf(PropTypes.object),
  49. }),
  50. },
  51. getInitialState() {
  52. return { zoom: null };
  53. },
  54. shouldComponentUpdate(nextProps) {
  55. return this.props.graph != nextProps.graph;
  56. },
  57. componentDidMount() {
  58. if (this.props.graph) {
  59. this._renderGraph(this.refs.container, this.props.graph);
  60. }
  61. },
  62. componentDidUpdate() {
  63. if (this.props.graph) {
  64. this._renderGraph(this.refs.container, this.props.graph);
  65. }
  66. },
  67. componentWillUnmount() {
  68. if (this.state.zoom) {
  69. this.state.zoom.on("zoom", null);
  70. }
  71. },
  72. render() {
  73. let contents;
  74. if (this.props.graph) {
  75. // Let the componentDidMount or componentDidUpdate method draw the graph
  76. // with DagreD3. We just provide the container for the graph here.
  77. contents = dom.div({
  78. ref: "container",
  79. style: {
  80. flex: 1,
  81. height: "100%",
  82. width: "100%",
  83. }
  84. });
  85. } else {
  86. contents = dom.div(
  87. {
  88. id: "shortest-paths-select-node-msg"
  89. },
  90. L10N.getStr("shortest-paths.select-node")
  91. );
  92. }
  93. return dom.div(
  94. {
  95. id: "shortest-paths",
  96. className: "vbox",
  97. },
  98. dom.label(
  99. {
  100. id: "shortest-paths-header",
  101. className: "header",
  102. },
  103. L10N.getStr("shortest-paths.header")
  104. ),
  105. contents
  106. );
  107. },
  108. _renderGraph(container, { nodes, edges }) {
  109. if (!container.firstChild) {
  110. const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  111. svg.setAttribute("id", "graph-svg");
  112. svg.setAttribute("xlink", "http://www.w3.org/1999/xlink");
  113. svg.style.width = "100%";
  114. svg.style.height = "100%";
  115. const target = document.createElementNS("http://www.w3.org/2000/svg", "g");
  116. target.setAttribute("id", "graph-target");
  117. target.style.width = "100%";
  118. target.style.height = "100%";
  119. svg.appendChild(target);
  120. container.appendChild(svg);
  121. }
  122. const graph = new dagreD3.Digraph();
  123. for (let i = 0; i < nodes.length; i++) {
  124. graph.addNode(nodes[i].id, {
  125. id: nodes[i].id,
  126. label: stringifyLabel(nodes[i].label, nodes[i].id),
  127. });
  128. }
  129. for (let i = 0; i < edges.length; i++) {
  130. graph.addEdge(null, edges[i].from, edges[i].to, {
  131. label: edges[i].name
  132. });
  133. }
  134. const renderer = new dagreD3.Renderer();
  135. renderer.drawNodes();
  136. renderer.drawEdgePaths();
  137. const svg = d3.select("#graph-svg");
  138. const target = d3.select("#graph-target");
  139. let zoom = this.state.zoom;
  140. if (!zoom) {
  141. zoom = d3.behavior.zoom().on("zoom", function () {
  142. target.attr(
  143. "transform",
  144. `translate(${d3.event.translate}) scale(${d3.event.scale})`
  145. );
  146. });
  147. svg.call(zoom);
  148. this.setState({ zoom });
  149. }
  150. const { translate, scale } = GRAPH_DEFAULTS;
  151. zoom.scale(scale);
  152. zoom.translate(translate);
  153. target.attr("transform", `translate(${translate}) scale(${scale})`);
  154. const layout = dagreD3.layout();
  155. renderer.layout(layout).run(graph, target);
  156. },
  157. });