123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- /* 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 { getSourceNames, parseURL,
- isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
- const { LocalizationHelper } = require("devtools/shared/l10n");
- const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
- const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
- module.exports = createClass({
- displayName: "Frame",
- propTypes: {
- // SavedFrame, or an object containing all the required properties.
- frame: PropTypes.shape({
- functionDisplayName: PropTypes.string,
- source: PropTypes.string.isRequired,
- line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
- column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
- }).isRequired,
- // Clicking on the frame link -- probably should link to the debugger.
- onClick: PropTypes.func.isRequired,
- // Option to display a function name before the source link.
- showFunctionName: PropTypes.bool,
- // Option to display a function name even if it's anonymous.
- showAnonymousFunctionName: PropTypes.bool,
- // Option to display a host name after the source link.
- showHost: PropTypes.bool,
- // Option to display a host name if the filename is empty or just '/'
- showEmptyPathAsHost: PropTypes.bool,
- // Option to display a full source instead of just the filename.
- showFullSourceUrl: PropTypes.bool,
- // Service to enable the source map feature for console.
- sourceMapService: PropTypes.object,
- },
- getDefaultProps() {
- return {
- showFunctionName: false,
- showAnonymousFunctionName: false,
- showHost: false,
- showEmptyPathAsHost: false,
- showFullSourceUrl: false,
- };
- },
- componentWillMount() {
- const sourceMapService = this.props.sourceMapService;
- if (sourceMapService) {
- const source = this.getSource();
- sourceMapService.subscribe(source, this.onSourceUpdated);
- }
- },
- componentWillUnmount() {
- const sourceMapService = this.props.sourceMapService;
- if (sourceMapService) {
- const source = this.getSource();
- sourceMapService.unsubscribe(source, this.onSourceUpdated);
- }
- },
- /**
- * Component method to update the FrameView when a resolved location is available
- * @param event
- * @param location
- */
- onSourceUpdated(event, location, resolvedLocation) {
- const frame = this.getFrame(resolvedLocation);
- this.setState({
- frame,
- isSourceMapped: true,
- });
- },
- /**
- * Utility method to convert the Frame object to the
- * Source Object model required by SourceMapService
- * @param frame
- * @returns {{url: *, line: *, column: *}}
- */
- getSource(frame) {
- frame = frame || this.props.frame;
- const { source, line, column } = frame;
- return {
- url: source,
- line,
- column,
- };
- },
- /**
- * Utility method to convert the Source object model to the
- * Frame object model required by FrameView class.
- * @param source
- * @returns {{source: *, line: *, column: *, functionDisplayName: *}}
- */
- getFrame(source) {
- const { url, line, column } = source;
- return {
- source: url,
- line,
- column,
- functionDisplayName: this.props.frame.functionDisplayName,
- };
- },
- render() {
- let frame, isSourceMapped;
- let {
- onClick,
- showFunctionName,
- showAnonymousFunctionName,
- showHost,
- showEmptyPathAsHost,
- showFullSourceUrl
- } = this.props;
- if (this.state && this.state.isSourceMapped) {
- frame = this.state.frame;
- isSourceMapped = this.state.isSourceMapped;
- } else {
- frame = this.props.frame;
- }
- let source = frame.source ? String(frame.source) : "";
- let line = frame.line != void 0 ? Number(frame.line) : null;
- let column = frame.column != void 0 ? Number(frame.column) : null;
- const { short, long, host } = getSourceNames(source);
- // Reparse the URL to determine if we should link this; `getSourceNames`
- // has already cached this indirectly. We don't want to attempt to
- // link to "self-hosted" and "(unknown)". However, we do want to link
- // to Scratchpad URIs.
- // Source mapped sources might not necessary linkable, but they
- // are still valid in the debugger.
- const isLinkable = !!(isScratchpadScheme(source) || parseURL(source))
- || isSourceMapped;
- const elements = [];
- const sourceElements = [];
- let sourceEl;
- let tooltip = long;
- // Exclude all falsy values, including `0`, as line numbers start with 1.
- if (line) {
- tooltip += `:${line}`;
- // Intentionally exclude 0
- if (column) {
- tooltip += `:${column}`;
- }
- }
- let attributes = {
- "data-url": long,
- className: "frame-link",
- };
- if (showFunctionName) {
- let functionDisplayName = frame.functionDisplayName;
- if (!functionDisplayName && showAnonymousFunctionName) {
- functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction");
- }
- if (functionDisplayName) {
- elements.push(
- dom.span({ className: "frame-link-function-display-name" },
- functionDisplayName),
- " "
- );
- }
- }
- let displaySource = showFullSourceUrl ? long : short;
- if (isSourceMapped) {
- displaySource = getSourceMappedFile(displaySource);
- } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
- displaySource = host;
- }
- sourceElements.push(dom.span({
- className: "frame-link-filename",
- }, displaySource));
- // If we have a line number > 0.
- if (line) {
- let lineInfo = `:${line}`;
- // Add `data-line` attribute for testing
- attributes["data-line"] = line;
- // Intentionally exclude 0
- if (column) {
- lineInfo += `:${column}`;
- // Add `data-column` attribute for testing
- attributes["data-column"] = column;
- }
- sourceElements.push(dom.span({ className: "frame-link-line" }, lineInfo));
- }
- // Inner el is useful for achieving ellipsis on the left and correct LTR/RTL
- // ordering. See CSS styles for frame-link-source-[inner] and bug 1290056.
- let sourceInnerEl = dom.span({
- className: "frame-link-source-inner",
- title: isLinkable ?
- l10n.getFormatStr("frame.viewsourceindebugger", tooltip) : tooltip,
- }, sourceElements);
- // If source is not a URL (self-hosted, eval, etc.), don't make
- // it an anchor link, as we can't link to it.
- if (isLinkable) {
- sourceEl = dom.a({
- onClick: e => {
- e.preventDefault();
- onClick(this.getSource(frame));
- },
- href: source,
- className: "frame-link-source",
- draggable: false,
- }, sourceInnerEl);
- } else {
- sourceEl = dom.span({
- className: "frame-link-source",
- }, sourceInnerEl);
- }
- elements.push(sourceEl);
- if (showHost && host) {
- elements.push(" ", dom.span({ className: "frame-link-host" }, host));
- }
- return dom.span(attributes, ...elements);
- }
- });
|