--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -78,12 +78,18 @@ SourceMapURLService.prototype.originalPo
if (!urlInfo) {
return null;
}
// Call getOriginalURLs to make sure the source map has been
// fetched. We don't actually need the result of this though.
await this._sourceMapService.getOriginalURLs(urlInfo);
const location = { sourceId: urlInfo.id, line, column, sourceUrl: url };
let resolvedLocation = await this._sourceMapService.getOriginalLocation(location);
- return resolvedLocation === location ? null : resolvedLocation;
+ if (!resolvedLocation ||
+ (resolvedLocation.line === location.line &&
+ resolvedLocation.column === location.column &&
+ resolvedLocation.sourceUrl === location.sourceUrl)) {
+ return null;
+ }
+ return resolvedLocation;
};
exports.SourceMapURLService = SourceMapURLService;
--- a/devtools/client/shared/components/stack-trace.js
+++ b/devtools/client/shared/components/stack-trace.js
@@ -30,23 +30,26 @@ const AsyncFrame = createFactory(createC
const StackTrace = createClass({
displayName: "StackTrace",
propTypes: {
stacktrace: PropTypes.array.isRequired,
onViewSourceInDebugger: PropTypes.func.isRequired,
onViewSourceInScratchpad: PropTypes.func,
+ // Service to enable the source map feature.
+ sourceMapService: PropTypes.object,
},
render() {
let {
stacktrace,
onViewSourceInDebugger,
- onViewSourceInScratchpad
+ onViewSourceInScratchpad,
+ sourceMapService,
} = this.props;
let frames = [];
stacktrace.forEach((s, i) => {
if (s.asyncCause) {
frames.push("\t", AsyncFrame({
key: `${i}-asyncframe`,
asyncCause: s.asyncCause
@@ -62,17 +65,18 @@ const StackTrace = createClass({
line: s.lineNumber,
column: s.columnNumber,
},
showFunctionName: true,
showAnonymousFunctionName: true,
showFullSourceUrl: true,
onClick: (/^Scratchpad\/\d+$/.test(source))
? onViewSourceInScratchpad
- : onViewSourceInDebugger
+ : onViewSourceInDebugger,
+ sourceMapService,
}), "\n");
});
return dom.div({ className: "stack-trace" }, frames);
}
});
module.exports = StackTrace;
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -6,16 +6,17 @@ support-files =
[test_HSplitBox_01.html]
[test_notification_box_01.html]
[test_notification_box_02.html]
[test_notification_box_03.html]
[test_searchbox.html]
[test_searchbox-with-autocomplete.html]
[test_sidebar_toggle.html]
[test_stack-trace.html]
+[test_stack-trace-source-maps.html]
[test_tabs_accessibility.html]
[test_tabs_menu.html]
[test_tree_01.html]
[test_tree_02.html]
[test_tree_03.html]
[test_tree_04.html]
[test_tree_05.html]
[test_tree_06.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_stack-trace-source-maps.html
@@ -0,0 +1,105 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test the rendering of a stack trace with source maps
+-->
+<head>
+ <meta charset="utf-8">
+ <title>StackTrace component test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+
+window.onload = function () {
+ let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+ let React = browserRequire("devtools/client/shared/vendor/react");
+ let StackTrace = React.createFactory(
+ browserRequire("devtools/client/shared/components/stack-trace")
+ );
+ ok(StackTrace, "Got the StackTrace factory");
+
+ add_task(function* () {
+ let stacktrace = [
+ {
+ filename: "https://bugzilla.mozilla.org/bundle.js",
+ lineNumber: 99,
+ columnNumber: 10
+ },
+ {
+ functionName: "loadFunc",
+ filename: "https://bugzilla.mozilla.org/bundle.js",
+ lineNumber: 108,
+ }
+ ];
+
+ let props = {
+ stacktrace,
+ onViewSourceInDebugger: () => {},
+ onViewSourceInScratchpad: () => {},
+ // A mock source map service.
+ sourceMapService: {
+ originalPositionFor: function (url, line, column) {
+ let newLine = line === 99 ? 1 : 7;
+ // Return a phony promise-like thing that resolves
+ // immediately.
+ return {
+ then: function (consequence) {
+ consequence({
+ sourceId: "whatever",
+ sourceUrl: "https://bugzilla.mozilla.org/original.js",
+ line: newLine,
+ column,
+ });
+ },
+ };
+ }
+ },
+ };
+
+ let trace = ReactDOM.render(StackTrace(props), window.document.body);
+ yield forceRender(trace);
+
+ let traceEl = ReactDOM.findDOMNode(trace);
+ ok(traceEl, "Rendered StackTrace has an element");
+
+ // Get the child nodes and filter out the text-only whitespace ones
+ let frameEls = Array.from(traceEl.childNodes)
+ .filter(n => n.className && n.className.includes("frame"));
+ ok(frameEls, "Rendered StackTrace has frames");
+ is(frameEls.length, 2, "StackTrace has 2 frames");
+
+ checkFrameString({
+ el: frameEls[0],
+ functionName: "<anonymous>",
+ source: "https://bugzilla.mozilla.org/original.js",
+ file: "original.js",
+ line: 1,
+ column: 10,
+ shouldLink: true,
+ tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:1:10",
+ });
+
+ checkFrameString({
+ el: frameEls[1],
+ functionName: "loadFunc",
+ source: "https://bugzilla.mozilla.org/original.js",
+ file: "original.js",
+ line: 7,
+ column: null,
+ shouldLink: true,
+ tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:7",
+ });
+ });
+};
+</script>
+</body>
+</html>
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -3551,19 +3551,22 @@ Widgets.Stacktrace.prototype = extend(Wi
if (this.element) {
return this;
}
let result = this.element = this.document.createElementNS(XHTML_NS, "div");
result.className = "stacktrace devtools-monospace";
if (this.stacktrace) {
+ const target = this.message.output.toolboxTarget;
+ const toolbox = gDevTools.getToolbox(target);
this.output.owner.ReactDOM.render(this.output.owner.StackTraceView({
stacktrace: this.stacktrace,
- onViewSourceInDebugger: frame => this.output.openLocationInDebugger(frame)
+ onViewSourceInDebugger: frame => this.output.openLocationInDebugger(frame),
+ sourceMapService: toolbox ? toolbox.sourceMapURLService : null,
}), result);
}
return this;
}
});
/**
--- a/devtools/client/webconsole/net/components/net-info-body.js
+++ b/devtools/client/webconsole/net/components/net-info-body.js
@@ -33,17 +33,19 @@ const PropTypes = React.PropTypes;
*/
var NetInfoBody = React.createClass({
propTypes: {
tabActive: PropTypes.number.isRequired,
actions: PropTypes.object.isRequired,
data: PropTypes.shape({
request: PropTypes.object.isRequired,
response: PropTypes.object.isRequired
- })
+ }),
+ // Service to enable the source map feature.
+ sourceMapService: PropTypes.object,
},
displayName: "NetInfoBody",
getDefaultProps() {
return {
tabActive: 0
};
@@ -71,17 +73,17 @@ var NetInfoBody = React.createClass({
},
hasStackTrace() {
let {cause} = this.state.data;
return cause && cause.stacktrace && cause.stacktrace.length > 0;
},
getTabPanels() {
- let actions = this.props.actions;
+ let { actions, sourceMapService } = this.props;
let data = this.state.data;
let {request} = data;
// Flags for optional tabs. Some tabs are visible only if there
// are data to display.
let hasParams = request.queryString && request.queryString.length;
let hasPostData = request.bodySize > 0;
@@ -148,17 +150,18 @@ var NetInfoBody = React.createClass({
if (this.hasStackTrace()) {
panels.push(
TabPanel({
className: "stacktrace-tab",
key: "stacktrace",
title: Locale.$STR("netRequest.callstack")},
StackTraceTab({
data: data,
- actions: actions
+ actions: actions,
+ sourceMapService: sourceMapService,
})
)
);
}
return panels;
},
--- a/devtools/client/webconsole/net/components/stacktrace-tab.js
+++ b/devtools/client/webconsole/net/components/stacktrace-tab.js
@@ -8,22 +8,24 @@ const StackTrace = createFactory(require
const StackTraceTab = createClass({
displayName: "StackTraceTab",
propTypes: {
data: PropTypes.object.isRequired,
actions: PropTypes.shape({
onViewSourceInDebugger: PropTypes.func.isRequired
- })
+ }),
+ // Service to enable the source map feature.
+ sourceMapService: PropTypes.object,
},
render() {
let { stacktrace } = this.props.data.cause;
- let { actions } = this.props;
+ let { actions, sourceMapService } = this.props;
let onViewSourceInDebugger = actions.onViewSourceInDebugger.bind(actions);
- return StackTrace({ stacktrace, onViewSourceInDebugger });
+ return StackTrace({ stacktrace, onViewSourceInDebugger, sourceMapService });
}
});
// Exports from this module
module.exports = StackTraceTab;
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -158,17 +158,18 @@ NetRequest.prototype = {
let doc = messageBody.ownerDocument;
this.netInfoBodyBox = doc.createElementNS(XHTML_NS, "div");
this.netInfoBodyBox.classList.add("netInfoBody");
messageBody.appendChild(this.netInfoBodyBox);
// As soon as Redux is in place state and actions will come from
// separate modules.
let body = NetInfoBody({
- actions: this
+ actions: this,
+ sourceMapService: this.owner.sourceMapURLService,
});
// Render net info body!
this.body = ReactDOM.render(body, this.netInfoBodyBox);
this.refresh();
},
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -25,16 +25,17 @@ const ConsoleOutput = createClass({
displayName: "ConsoleOutput",
propTypes: {
messages: PropTypes.object.isRequired,
messagesUi: PropTypes.object.isRequired,
serviceContainer: PropTypes.shape({
attachRefToHud: PropTypes.func.isRequired,
openContextMenu: PropTypes.func.isRequired,
+ sourceMapService: PropTypes.object,
}),
autoscroll: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired,
timestampsVisible: PropTypes.bool,
groups: PropTypes.object.isRequired,
messagesTableData: PropTypes.object.isRequired,
},
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -15,44 +15,42 @@ const Message = createFactory(require("d
ConsoleCommand.displayName = "ConsoleCommand";
ConsoleCommand.propTypes = {
message: PropTypes.object.isRequired,
autoscroll: PropTypes.bool.isRequired,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
+ serviceContainer: PropTypes.object,
};
ConsoleCommand.defaultProps = {
indent: 0,
};
/**
* Displays input from the console.
*/
function ConsoleCommand(props) {
const {
autoscroll,
indent,
message,
timestampsVisible,
+ serviceContainer,
} = props;
const {
source,
type,
level,
messageText: messageBody,
} = message;
- const {
- serviceContainer,
- } = props;
-
return Message({
source,
type,
level,
topLevelClasses: [],
messageBody,
scrollToMessage: autoscroll,
serviceContainer,
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -15,16 +15,17 @@ const Message = createFactory(require("d
const GripMessageBody = require("devtools/client/webconsole/new-console-output/components/grip-message-body");
EvaluationResult.displayName = "EvaluationResult";
EvaluationResult.propTypes = {
message: PropTypes.object.isRequired,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
+ serviceContainer: PropTypes.object,
};
EvaluationResult.defaultProps = {
indent: 0,
};
function EvaluationResult(props) {
const {
--- a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -15,16 +15,17 @@ const Message = createFactory(require("d
PageError.displayName = "PageError";
PageError.propTypes = {
message: PropTypes.object.isRequired,
open: PropTypes.bool,
indent: PropTypes.number.isRequired,
timestampsVisible: PropTypes.bool.isRequired,
+ serviceContainer: PropTypes.object,
};
PageError.defaultProps = {
open: false,
indent: 0,
};
function PageError(props) {
--- a/devtools/client/webconsole/new-console-output/components/message.js
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -143,16 +143,17 @@ const Message = createClass({
attachment = dom.div(
{
className: "stacktrace devtools-monospace"
},
StackTrace({
stacktrace: stacktrace,
onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
onViewSourceInScratchpad: serviceContainer.onViewSourceInScratchpad,
+ sourceMapService: serviceContainer.sourceMapService,
})
);
}
// If there is an expandable part, make it collapsible.
let collapse = null;
if (collapsible) {
collapse = CollapseButton({