Bug 1433373 - Use source mapping for console jump to definition button r=nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D38096

--HG--
extra : moz-landing-system : lando
This commit is contained in:
wartmanm 2019-10-07 08:40:04 +00:00
Родитель ffed33b20b
Коммит ada58cbb70
11 изменённых файлов: 186 добавлений и 17 удалений

Просмотреть файл

@ -21,10 +21,16 @@ FunctionRep.propTypes = {
object: PropTypes.object.isRequired, object: PropTypes.object.isRequired,
parameterNames: PropTypes.array, parameterNames: PropTypes.array,
onViewSourceInDebugger: PropTypes.func, onViewSourceInDebugger: PropTypes.func,
sourceMapService: PropTypes.object,
}; };
function FunctionRep(props) { function FunctionRep(props) {
const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props; const {
object: grip,
onViewSourceInDebugger,
recordTelemetryEvent,
sourceMapService,
} = props;
let jumpToDefinitionButton; let jumpToDefinitionButton;
if ( if (
@ -37,14 +43,19 @@ function FunctionRep(props) {
className: "jump-definition", className: "jump-definition",
draggable: false, draggable: false,
title: "Jump to definition", title: "Jump to definition",
onClick: e => { onClick: async e => {
// Stop the event propagation so we don't trigger ObjectInspector // Stop the event propagation so we don't trigger ObjectInspector
// expand/collapse. // expand/collapse.
e.stopPropagation(); e.stopPropagation();
if (recordTelemetryEvent) { if (recordTelemetryEvent) {
recordTelemetryEvent("jump_to_definition"); recordTelemetryEvent("jump_to_definition");
} }
onViewSourceInDebugger(grip.location);
const sourceLocation = await getSourceLocation(
grip.location,
sourceMapService
);
onViewSourceInDebugger(sourceLocation);
}, },
}); });
} }
@ -181,6 +192,24 @@ function supportsObject(grip, noGrip = false) {
return type == "Function"; return type == "Function";
} }
async function getSourceLocation(location, sourceMapService) {
if (!sourceMapService) {
return location;
}
try {
const originalLocation = await sourceMapService.originalPositionFor(
location.url,
location.line,
location.column
);
if (originalLocation) {
const { sourceUrl, line, column, sourceId } = originalLocation;
return { url: sourceUrl, line, column, sourceId };
}
} catch (e) {}
return location;
}
// Exports from this module // Exports from this module
module.exports = { module.exports = {

Просмотреть файл

@ -298,8 +298,11 @@ describe("Function - Anonymous generator function", () => {
}); });
describe("Function - Jump to definition", () => { describe("Function - Jump to definition", () => {
it("renders an icon when onViewSourceInDebugger props is provided", () => { it("renders an icon when onViewSourceInDebugger props is provided", async () => {
const onViewSourceInDebugger = jest.fn(); let onViewSourceInDebugger;
const onViewSourceCalled = new Promise(resolve => {
onViewSourceInDebugger = jest.fn(resolve);
});
const object = stubs.get("getRandom"); const object = stubs.get("getRandom");
const renderedComponent = renderRep(object, { const renderedComponent = renderRep(object, {
onViewSourceInDebugger, onViewSourceInDebugger,
@ -310,6 +313,7 @@ describe("Function - Jump to definition", () => {
type: "click", type: "click",
stopPropagation: () => {}, stopPropagation: () => {},
}); });
await onViewSourceCalled;
expect(node.exists()).toBeTruthy(); expect(node.exists()).toBeTruthy();
expect(onViewSourceInDebugger.mock.calls).toHaveLength(1); expect(onViewSourceInDebugger.mock.calls).toHaveLength(1);
@ -362,7 +366,7 @@ describe("Function - Jump to definition", () => {
const object = { const object = {
...stubs.get("getRandom"), ...stubs.get("getRandom"),
}; };
object.location.url = null; object.location = { ...object.location, url: null };
const renderedComponent = renderRep(object, { const renderedComponent = renderRep(object, {
onViewSourceInDebugger: () => {}, onViewSourceInDebugger: () => {},
}); });
@ -380,6 +384,39 @@ describe("Function - Jump to definition", () => {
const node = renderedComponent.find(".jump-definition"); const node = renderedComponent.find(".jump-definition");
expect(node.exists()).toBeFalsy(); expect(node.exists()).toBeFalsy();
}); });
it("applies source mapping to the object's location", async () => {
let onViewSourceInDebugger;
const onViewSourceCalled = new Promise(resolve => {
onViewSourceInDebugger = jest.fn(resolve);
});
const object = stubs.get("getRandom");
const { url, line, column } = object.location;
const sourceId = "test source id";
const originalPositionFor = jest.fn(() =>
Promise.resolve({ sourceUrl: url, line, column, sourceId })
);
const renderedComponent = renderRep(object, {
onViewSourceInDebugger,
sourceMapService: { originalPositionFor },
});
const node = renderedComponent.find(".jump-definition");
node.simulate("click", {
type: "click",
stopPropagation: () => {},
});
await onViewSourceCalled;
expect(originalPositionFor.mock.calls).toHaveLength(1);
expect(originalPositionFor.mock.calls[0]).toEqual([url, line, column]);
expect(onViewSourceInDebugger.mock.calls).toHaveLength(1);
console.log(onViewSourceInDebugger.mock.calls[0]);
const expectedLocation = { ...object.location, sourceId };
expect(onViewSourceInDebugger.mock.calls[0][0]).toEqual(expectedLocation);
});
}); });
describe("Function - Simplify name", () => { describe("Function - Simplify name", () => {

Просмотреть файл

@ -2843,14 +2843,16 @@ const IGNORED_SOURCE_URLS = ["debugger eval code"];
FunctionRep.propTypes = { FunctionRep.propTypes = {
object: PropTypes.object.isRequired, object: PropTypes.object.isRequired,
parameterNames: PropTypes.array, parameterNames: PropTypes.array,
onViewSourceInDebugger: PropTypes.func onViewSourceInDebugger: PropTypes.func,
sourceMapService: PropTypes.object
}; };
function FunctionRep(props) { function FunctionRep(props) {
const { const {
object: grip, object: grip,
onViewSourceInDebugger, onViewSourceInDebugger,
recordTelemetryEvent recordTelemetryEvent,
sourceMapService
} = props; } = props;
let jumpToDefinitionButton; let jumpToDefinitionButton;
@ -2859,7 +2861,7 @@ function FunctionRep(props) {
className: "jump-definition", className: "jump-definition",
draggable: false, draggable: false,
title: "Jump to definition", title: "Jump to definition",
onClick: e => { onClick: async e => {
// Stop the event propagation so we don't trigger ObjectInspector // Stop the event propagation so we don't trigger ObjectInspector
// expand/collapse. // expand/collapse.
e.stopPropagation(); e.stopPropagation();
@ -2868,7 +2870,8 @@ function FunctionRep(props) {
recordTelemetryEvent("jump_to_definition"); recordTelemetryEvent("jump_to_definition");
} }
onViewSourceInDebugger(grip.location); const sourceLocation = await getSourceLocation(grip.location, sourceMapService);
onViewSourceInDebugger(sourceLocation);
} }
}); });
} }
@ -2986,6 +2989,33 @@ function supportsObject(grip, noGrip = false) {
} }
return type == "Function"; return type == "Function";
}
async function getSourceLocation(location, sourceMapService) {
if (!sourceMapService) {
return location;
}
try {
const originalLocation = await sourceMapService.originalPositionFor(location.url, location.line, location.column);
if (originalLocation) {
const {
sourceUrl,
line,
column,
sourceId
} = originalLocation;
return {
url: sourceUrl,
line,
column,
sourceId
};
}
} catch (e) {}
return location;
} // Exports from this module } // Exports from this module

Просмотреть файл

@ -41,6 +41,9 @@ support-files =
test-certificate-messages.html test-certificate-messages.html
test-click-function-to-source.html test-click-function-to-source.html
test-click-function-to-source.js test-click-function-to-source.js
test-click-function-to-mapped-source.html
test-click-function-to-source.min.js
test-click-function-to-source.min.js.map
test-closure-optimized-out.html test-closure-optimized-out.html
test-console-filters.html test-console-filters.html
test-console-filter-by-regex-input.html test-console-filter-by-regex-input.html
@ -298,6 +301,7 @@ tags = mcb
skip-if = fission skip-if = fission
[browser_webconsole_clear_cache.js] [browser_webconsole_clear_cache.js]
[browser_webconsole_click_function_to_source.js] [browser_webconsole_click_function_to_source.js]
[browser_webconsole_click_function_to_mapped_source.js]
[browser_webconsole_clickable_urls.js] [browser_webconsole_clickable_urls.js]
[browser_webconsole_close_unfocused_window.js] [browser_webconsole_close_unfocused_window.js]
[browser_webconsole_closing_after_completion.js] [browser_webconsole_closing_after_completion.js]

Просмотреть файл

@ -0,0 +1,54 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that clicking on a function in a source-mapped file displays its
// original source in the debugger. See Bug 1433373.
"use strict";
requestLongerTimeout(5);
const TEST_URI =
"http://example.com/browser/devtools/client/webconsole/" +
"test/browser/" +
"test-click-function-to-mapped-source.html";
const TEST_ORIGINAL_URI =
"http://example.com/browser/devtools/client/webconsole/" +
"test/browser/" +
"test-click-function-to-source.js";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
info("Log a function");
const onLoggedFunction = waitForMessage(hud, "function foo");
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
content.wrappedJSObject.foo();
});
const { node } = await onLoggedFunction;
const jumpIcon = node.querySelector(".jump-definition");
ok(jumpIcon, "A jump to definition button is rendered, as expected");
info("Click on the jump to definition button.");
jumpIcon.click();
info("Wait for the Debugger panel to open.");
const toolbox = hud.toolbox;
await toolbox.getPanelWhenReady("jsdebugger");
const dbg = createDebuggerContext(toolbox);
await waitForSelectedSource(dbg, TEST_ORIGINAL_URI);
await waitForSelectedLocation(dbg, 9);
const pendingLocation = dbg.selectors.getPendingSelectedLocation();
const { url, line, column } = pendingLocation;
is(url, TEST_ORIGINAL_URI, "Debugger is open at the expected file");
is(line, 9, "Debugger is open at the expected line");
// If we loaded the original file, we'd have column 12 for the function's
// start position, but 9 is correct for the location in the source map.
is(column, 9, "Debugger is open at the expected column");
});

Просмотреть файл

@ -20,12 +20,6 @@ const TEST_SCRIPT_URI =
add_task(async function() { add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI); const hud = await openNewTabAndConsole(TEST_URI);
info("Open the Debugger panel.");
await openDebugger();
info("And right after come back to the Console panel.");
await openConsole();
info("Log a function"); info("Log a function");
const onLoggedFunction = waitForMessage(hud, "function foo"); const onLoggedFunction = waitForMessage(hud, "function foo");
ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
@ -38,11 +32,15 @@ add_task(async function() {
info("Click on the jump to definition button."); info("Click on the jump to definition button.");
jumpIcon.click(); jumpIcon.click();
info("Wait for the Debugger panel to open.");
const toolbox = hud.toolbox; const toolbox = hud.toolbox;
await toolbox.getPanelWhenReady("jsdebugger");
const dbg = createDebuggerContext(toolbox); const dbg = createDebuggerContext(toolbox);
await waitForSelectedSource(dbg, TEST_SCRIPT_URI); await waitForSelectedSource(dbg, TEST_SCRIPT_URI);
const pendingLocation = dbg.selectors.getPendingSelectedLocation(); const pendingLocation = dbg.selectors.getPendingSelectedLocation();
const { line } = pendingLocation; const { line, column } = pendingLocation;
is(line, 9, "Debugger is open at the expected line"); is(line, 9, "Debugger is open at the expected line");
is(column, 12, "Debugger is open at the expected column");
}); });

Просмотреть файл

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf-8">
<title>Click on function should point to source</title>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="test-click-function-to-source.min.js"></script>
</head>
<body></body>
</html>

Просмотреть файл

@ -0,0 +1,3 @@
/* eslint-disable */
function foo(){console.log(foo)}
//# sourceMappingURL=test-click-function-to-source.min.js.map

Просмотреть файл

@ -0,0 +1 @@
{"version":3,"sources":["test-click-function-to-source.js"],"names":["foo","console","log"],"mappings":";AAQA,SAASA,MACPC,QAAQC,IAAIF"}

Просмотреть файл

@ -69,6 +69,7 @@ function getObjectInspector(grip, serviceContainer, override = {}) {
onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger, onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
recordTelemetryEvent: serviceContainer.recordTelemetryEvent, recordTelemetryEvent: serviceContainer.recordTelemetryEvent,
openLink: serviceContainer.openLink, openLink: serviceContainer.openLink,
sourceMapService: serviceContainer.sourceMapService,
renderStacktrace: stacktrace => renderStacktrace: stacktrace =>
createElement(SmartTrace, { createElement(SmartTrace, {
key: "stacktrace", key: "stacktrace",

Просмотреть файл

@ -123,6 +123,7 @@ const previewers = {
grip.location = { grip.location = {
url: obj.script.url, url: obj.script.url,
line: obj.script.startLine, line: obj.script.startLine,
column: obj.script.startColumn
}; };
} }