frame.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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 { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
  6. const { getSourceNames, parseURL,
  7. isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
  8. const { LocalizationHelper } = require("devtools/shared/l10n");
  9. const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
  10. const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
  11. module.exports = createClass({
  12. displayName: "Frame",
  13. propTypes: {
  14. // SavedFrame, or an object containing all the required properties.
  15. frame: PropTypes.shape({
  16. functionDisplayName: PropTypes.string,
  17. source: PropTypes.string.isRequired,
  18. line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
  19. column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
  20. }).isRequired,
  21. // Clicking on the frame link -- probably should link to the debugger.
  22. onClick: PropTypes.func.isRequired,
  23. // Option to display a function name before the source link.
  24. showFunctionName: PropTypes.bool,
  25. // Option to display a function name even if it's anonymous.
  26. showAnonymousFunctionName: PropTypes.bool,
  27. // Option to display a host name after the source link.
  28. showHost: PropTypes.bool,
  29. // Option to display a host name if the filename is empty or just '/'
  30. showEmptyPathAsHost: PropTypes.bool,
  31. // Option to display a full source instead of just the filename.
  32. showFullSourceUrl: PropTypes.bool,
  33. // Service to enable the source map feature for console.
  34. sourceMapService: PropTypes.object,
  35. },
  36. getDefaultProps() {
  37. return {
  38. showFunctionName: false,
  39. showAnonymousFunctionName: false,
  40. showHost: false,
  41. showEmptyPathAsHost: false,
  42. showFullSourceUrl: false,
  43. };
  44. },
  45. componentWillMount() {
  46. const sourceMapService = this.props.sourceMapService;
  47. if (sourceMapService) {
  48. const source = this.getSource();
  49. sourceMapService.subscribe(source, this.onSourceUpdated);
  50. }
  51. },
  52. componentWillUnmount() {
  53. const sourceMapService = this.props.sourceMapService;
  54. if (sourceMapService) {
  55. const source = this.getSource();
  56. sourceMapService.unsubscribe(source, this.onSourceUpdated);
  57. }
  58. },
  59. /**
  60. * Component method to update the FrameView when a resolved location is available
  61. * @param event
  62. * @param location
  63. */
  64. onSourceUpdated(event, location, resolvedLocation) {
  65. const frame = this.getFrame(resolvedLocation);
  66. this.setState({
  67. frame,
  68. isSourceMapped: true,
  69. });
  70. },
  71. /**
  72. * Utility method to convert the Frame object to the
  73. * Source Object model required by SourceMapService
  74. * @param frame
  75. * @returns {{url: *, line: *, column: *}}
  76. */
  77. getSource(frame) {
  78. frame = frame || this.props.frame;
  79. const { source, line, column } = frame;
  80. return {
  81. url: source,
  82. line,
  83. column,
  84. };
  85. },
  86. /**
  87. * Utility method to convert the Source object model to the
  88. * Frame object model required by FrameView class.
  89. * @param source
  90. * @returns {{source: *, line: *, column: *, functionDisplayName: *}}
  91. */
  92. getFrame(source) {
  93. const { url, line, column } = source;
  94. return {
  95. source: url,
  96. line,
  97. column,
  98. functionDisplayName: this.props.frame.functionDisplayName,
  99. };
  100. },
  101. render() {
  102. let frame, isSourceMapped;
  103. let {
  104. onClick,
  105. showFunctionName,
  106. showAnonymousFunctionName,
  107. showHost,
  108. showEmptyPathAsHost,
  109. showFullSourceUrl
  110. } = this.props;
  111. if (this.state && this.state.isSourceMapped) {
  112. frame = this.state.frame;
  113. isSourceMapped = this.state.isSourceMapped;
  114. } else {
  115. frame = this.props.frame;
  116. }
  117. let source = frame.source ? String(frame.source) : "";
  118. let line = frame.line != void 0 ? Number(frame.line) : null;
  119. let column = frame.column != void 0 ? Number(frame.column) : null;
  120. const { short, long, host } = getSourceNames(source);
  121. // Reparse the URL to determine if we should link this; `getSourceNames`
  122. // has already cached this indirectly. We don't want to attempt to
  123. // link to "self-hosted" and "(unknown)". However, we do want to link
  124. // to Scratchpad URIs.
  125. // Source mapped sources might not necessary linkable, but they
  126. // are still valid in the debugger.
  127. const isLinkable = !!(isScratchpadScheme(source) || parseURL(source))
  128. || isSourceMapped;
  129. const elements = [];
  130. const sourceElements = [];
  131. let sourceEl;
  132. let tooltip = long;
  133. // Exclude all falsy values, including `0`, as line numbers start with 1.
  134. if (line) {
  135. tooltip += `:${line}`;
  136. // Intentionally exclude 0
  137. if (column) {
  138. tooltip += `:${column}`;
  139. }
  140. }
  141. let attributes = {
  142. "data-url": long,
  143. className: "frame-link",
  144. };
  145. if (showFunctionName) {
  146. let functionDisplayName = frame.functionDisplayName;
  147. if (!functionDisplayName && showAnonymousFunctionName) {
  148. functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction");
  149. }
  150. if (functionDisplayName) {
  151. elements.push(
  152. dom.span({ className: "frame-link-function-display-name" },
  153. functionDisplayName),
  154. " "
  155. );
  156. }
  157. }
  158. let displaySource = showFullSourceUrl ? long : short;
  159. if (isSourceMapped) {
  160. displaySource = getSourceMappedFile(displaySource);
  161. } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
  162. displaySource = host;
  163. }
  164. sourceElements.push(dom.span({
  165. className: "frame-link-filename",
  166. }, displaySource));
  167. // If we have a line number > 0.
  168. if (line) {
  169. let lineInfo = `:${line}`;
  170. // Add `data-line` attribute for testing
  171. attributes["data-line"] = line;
  172. // Intentionally exclude 0
  173. if (column) {
  174. lineInfo += `:${column}`;
  175. // Add `data-column` attribute for testing
  176. attributes["data-column"] = column;
  177. }
  178. sourceElements.push(dom.span({ className: "frame-link-line" }, lineInfo));
  179. }
  180. // Inner el is useful for achieving ellipsis on the left and correct LTR/RTL
  181. // ordering. See CSS styles for frame-link-source-[inner] and bug 1290056.
  182. let sourceInnerEl = dom.span({
  183. className: "frame-link-source-inner",
  184. title: isLinkable ?
  185. l10n.getFormatStr("frame.viewsourceindebugger", tooltip) : tooltip,
  186. }, sourceElements);
  187. // If source is not a URL (self-hosted, eval, etc.), don't make
  188. // it an anchor link, as we can't link to it.
  189. if (isLinkable) {
  190. sourceEl = dom.a({
  191. onClick: e => {
  192. e.preventDefault();
  193. onClick(this.getSource(frame));
  194. },
  195. href: source,
  196. className: "frame-link-source",
  197. draggable: false,
  198. }, sourceInnerEl);
  199. } else {
  200. sourceEl = dom.span({
  201. className: "frame-link-source",
  202. }, sourceInnerEl);
  203. }
  204. elements.push(sourceEl);
  205. if (showHost && host) {
  206. elements.push(" ", dom.span({ className: "frame-link-host" }, host));
  207. }
  208. return dom.span(attributes, ...elements);
  209. }
  210. });