summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/components/frame.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/components/frame.js')
-rw-r--r--devtools/client/shared/components/frame.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/devtools/client/shared/components/frame.js b/devtools/client/shared/components/frame.js
new file mode 100644
index 000000000..5abe5f057
--- /dev/null
+++ b/devtools/client/shared/components/frame.js
@@ -0,0 +1,239 @@
+/* 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);
+ }
+});