зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
ffed33b20b
Коммит
ada58cbb70
|
@ -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>
|
3
devtools/client/webconsole/test/browser/test-click-function-to-source.min.js
поставляемый
Normal file
3
devtools/client/webconsole/test/browser/test-click-function-to-source.min.js
поставляемый
Normal file
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче