From 15e1d86b8e151bcc2bfc8b816e1884cae753fb58 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Fri, 25 May 2018 13:52:20 -0400 Subject: [PATCH] Bug 1464486 - Update Debugger Frontend v59. r=dwalsh --- devtools/client/debugger/new/README.mozilla | 6 +- .../client/debugger/new/dist/debugger.css | 24 ++- .../client/debugger/new/dist/parser-worker.js | 62 ++++-- .../debugger/new/src/actions/breakpoints.js | 6 +- .../new/src/components/Editor/EditorMenu.js | 4 +- .../new/src/components/Editor/EmptyLines.js | 2 +- .../new/src/components/Editor/GutterMenu.js | 2 +- .../debugger/new/src/components/Editor/Tab.js | 4 +- .../components/SecondaryPanes/Breakpoint.js | 43 ++-- .../components/SecondaryPanes/Breakpoints.js | 44 +--- .../SecondaryPanes/Breakpoints/Breakpoint.js | 196 +++++++++++++++++ .../Breakpoints/BreakpointsContextMenu.js | 201 ++++++++++++++++++ .../SecondaryPanes/Breakpoints/index.js | 105 +++++++++ .../SecondaryPanes/Breakpoints/moz.build | 14 ++ .../client/debugger/new/src/reducers/ast.js | 10 +- .../new/src/selectors/breakpointSources.js | 37 ++++ .../debugger/new/src/selectors/index.js | 9 + .../new/src/selectors/mappedBreakpoints.js | 57 +++++ .../debugger/new/src/selectors/moz.build | 1 + .../new/src/utils/breakpoint/index.js | 9 + .../new/src/utils/pause/mapScopes/index.js | 60 ++++-- .../new/src/utils/pause/mapScopes/moz.build | 1 + .../utils/pause/mapScopes/rangeMetadata.js | 92 ++++++++ .../client/debugger/new/src/utils/source.js | 2 +- .../new/src/utils/sources-tree/utils.js | 5 +- .../client/debugger/new/src/utils/text.js | 25 ++- .../src/workers/parser/getScopes/visitor.js | 60 ++++-- .../debugger/new/test/mochitest/browser.ini | 2 + .../mochitest/browser_dbg-scopes-mutations.js | 2 +- .../browser_dbg-sourcemapped-scopes.js | 39 ++-- .../test/mochitest/browser_dbg-sourcemaps3.js | 2 +- .../mochitest/examples/doc-sourcemapped.html | 2 + .../fixtures/babel-lex-and-nonlex/input.js | 9 + .../fixtures/babel-lex-and-nonlex/output.js | 90 ++++++++ .../babel-lex-and-nonlex/output.js.map | 1 + devtools/client/shared/source-map/index.js | 2 + devtools/client/shared/source-map/worker.js | 54 +++++ devtools/client/shared/vendor/WasmDis.js | 7 +- 38 files changed, 1130 insertions(+), 161 deletions(-) create mode 100644 devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js create mode 100644 devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js create mode 100644 devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js create mode 100644 devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/moz.build create mode 100644 devtools/client/debugger/new/src/selectors/breakpointSources.js create mode 100644 devtools/client/debugger/new/src/selectors/mappedBreakpoints.js create mode 100644 devtools/client/debugger/new/src/utils/pause/mapScopes/rangeMetadata.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/input.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js create mode 100644 devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js.map diff --git a/devtools/client/debugger/new/README.mozilla b/devtools/client/debugger/new/README.mozilla index 3b4f93efac95..1a436b7edb3a 100644 --- a/devtools/client/debugger/new/README.mozilla +++ b/devtools/client/debugger/new/README.mozilla @@ -1,13 +1,13 @@ This is the debugger.html project output. See https://github.com/devtools-html/debugger.html -Version 58 +Version 59 -Comparison: https://github.com/devtools-html/debugger.html/compare/release-57...release-58 +Comparison: https://github.com/devtools-html/debugger.html/compare/release-58...release-59 Packages: - babel-plugin-transform-es2015-modules-commonjs @6.26.2 - babel-preset-react @6.24.1 - react @16.2.0 - react-dom @16.2.0 -- webpack @3.12.0 +- webpack @3.11.0 diff --git a/devtools/client/debugger/new/dist/debugger.css b/devtools/client/debugger/new/dist/debugger.css index d0c926e50cfe..0017087554ab 100644 --- a/devtools/client/debugger/new/dist/debugger.css +++ b/devtools/client/debugger/new/dist/debugger.css @@ -1108,7 +1108,10 @@ html .toggle-button.end.vertical svg { .search-field i.magnifying-glass, .search-field i.sad-face { - padding: 6px; + padding-top: 5px; + padding-bottom: 5px; + padding-inline-end: 10px; + padding-inline-start: 5px; width: 24px; } @@ -1118,6 +1121,11 @@ html .toggle-button.end.vertical svg { width: 40px; } +.search-field.big i.sad-face { + padding-top: 4px; + padding-inline-start: 4px; +} + .search-field .magnifying-glass path, .search-field .magnifying-glass ellipse { stroke: var(--theme-comment); @@ -1134,18 +1142,13 @@ html .toggle-button.end.vertical svg { .search-field .summary { line-height: 27px; text-align: center; - padding-right: 10px; + padding-inline-end: 10px; color: var(--theme-body-color-inactive); align-self: center; padding-top: 1px; white-space: nowrap; } -.search-field.big .summary { - padding: 5px 0 5px 0; - line-height: 2rem; -} - .search-field .search-nav-buttons { display: flex; user-select: none; @@ -1261,7 +1264,7 @@ html .toggle-button.end.vertical svg { } .project-text-search .search-field .close-btn.big { - margin-top: 6px; + margin-top: 2px; } .project-text-search .managed-tree { @@ -3077,7 +3080,7 @@ html[dir="rtl"] .breakpoints-list .breakpoint .breakpoint-line { max-width: calc(100% - var(--breakpoint-expression-right-clear-space)); display: inline-block; padding-inline-end: 8px; - cursor: default; + cursor: pointer; flex-grow: 1; text-overflow: ellipsis; overflow: hidden; @@ -3261,7 +3264,7 @@ html[dir="rtl"] .breakpoints-list .breakpoint .breakpoint-line { .expression-container__close-btn { position: absolute; offset-inline-end: 0px; - top: 0px; + top: 1px; } .expression-content { @@ -4049,6 +4052,7 @@ html .welcomebox .toggle-button-end.collapsed { position: relative; transition: all 0.15s ease; min-width: 40px; + max-width: 100%; overflow: hidden; padding: 5px; margin-inline-start: 3px; diff --git a/devtools/client/debugger/new/dist/parser-worker.js b/devtools/client/debugger/new/dist/parser-worker.js index fead7a6b9d59..f3a93442fdd2 100644 --- a/devtools/client/debugger/new/dist/parser-worker.js +++ b/devtools/client/debugger/new/dist/parser-worker.js @@ -22811,7 +22811,7 @@ function toParsedScopes(children, sourceId) { return { start: scope.loc.start, end: scope.loc.end, - type: scope.type === "module" ? "block" : scope.type, + type: scope.type === "module" || scope.type === "function-body" ? "block" : scope.type, displayName: scope.displayName, bindings: scope.bindings, children: toParsedScopes(scope.children, sourceId) @@ -22906,9 +22906,18 @@ function isLetOrConst(node) { } function hasLexicalDeclaration(node, parent) { + const nodes = []; + if (t.isSwitchStatement(node)) { + for (const caseNode of node.cases) { + nodes.push(...caseNode.consequent); + } + } else { + nodes.push(...node.body); + } + const isFunctionBody = t.isFunction(parent, { body: node }); - return node.body.some(child => isLexicalVariable(child) || !isFunctionBody && child.type === "FunctionDeclaration" || child.type === "ClassDeclaration"); + return nodes.some(child => isLexicalVariable(child) || t.isClassDeclaration(child) || !isFunctionBody && t.isFunctionDeclaration(child)); } function isLexicalVariable(node) { return isNode(node, "VariableDeclaration") && isLetOrConst(node); @@ -22973,19 +22982,27 @@ const scopeCollectionVisitor = { // This ignores Annex B function declaration hoisting, which // is probably a fine assumption. state.declarationBindingIds.add(node.id); - const fnScope = getVarScope(scope); - scope.bindings[node.id.name] = { - type: fnScope === scope ? "var" : "let", - refs: [{ - type: "fn-decl", - start: fromBabelLocation(node.id.loc.start, state.sourceId), - end: fromBabelLocation(node.id.loc.end, state.sourceId), - declaration: { - start: fromBabelLocation(node.loc.start, state.sourceId), - end: fromBabelLocation(node.loc.end, state.sourceId) - } - }] - }; + const refs = [{ + type: "fn-decl", + start: fromBabelLocation(node.id.loc.start, state.sourceId), + end: fromBabelLocation(node.id.loc.end, state.sourceId), + declaration: { + start: fromBabelLocation(node.loc.start, state.sourceId), + end: fromBabelLocation(node.loc.end, state.sourceId) + } + }]; + + if (scope.type === "block") { + scope.bindings[node.id.name] = { + type: "let", + refs + }; + } else { + getVarScope(scope).bindings[node.id.name] = { + type: "var", + refs + }; + } } scope = pushTempScope(state, "function", (0, _getFunctionName2.default)(node, parentNode), { @@ -23007,6 +23024,13 @@ const scopeCollectionVisitor = { refs: [] }; } + + if (t.isBlockStatement(node.body) && hasLexicalDeclaration(node.body, node)) { + scope = pushTempScope(state, "function-body", "Function Body", { + start: fromBabelLocation(node.body.loc.start, state.sourceId), + end: fromBabelLocation(node.body.loc.end, state.sourceId) + }); + } } else if (t.isClass(node)) { if (t.isIdentifier(node.id)) { // For decorated classes, the AST considers the first the decorator @@ -23075,7 +23099,9 @@ const scopeCollectionVisitor = { end: fromBabelLocation(node.loc.end, state.sourceId) }); parseDeclarator(node.param, scope, "var", "catch", node, state); - } else if (t.isBlockStatement(node) && hasLexicalDeclaration(node, parentNode)) { + } else if (t.isBlockStatement(node) && + // Function body's are handled in the function logic above. + !t.isFunction(parentNode) && hasLexicalDeclaration(node, parentNode)) { // Debugger will create new lexical environment for the block. pushTempScope(state, "block", "Block", { start: fromBabelLocation(node.loc.start, state.sourceId), @@ -23199,7 +23225,7 @@ const scopeCollectionVisitor = { type: "implicit", refs: [] }; - } else if (t.isSwitchStatement(node) && node.cases.some(caseNode => caseNode.consequent.some(child => isLexicalVariable(child)))) { + } else if (t.isSwitchStatement(node) && hasLexicalDeclaration(node, parentNode)) { pushTempScope(state, "block", "Switch", { start: fromBabelLocation(node.loc.start, state.sourceId), end: fromBabelLocation(node.loc.end, state.sourceId) @@ -25012,6 +25038,7 @@ const { const dispatcher = new WorkerDispatcher(); const getOriginalURLs = dispatcher.task("getOriginalURLs"); +const getOriginalRanges = dispatcher.task("getOriginalRanges"); const getGeneratedRanges = dispatcher.task("getGeneratedRanges", { queue: true }); @@ -25035,6 +25062,7 @@ module.exports = { isOriginalId, hasMappedSource, getOriginalURLs, + getOriginalRanges, getGeneratedRanges, getGeneratedLocation, getAllGeneratedLocations, diff --git a/devtools/client/debugger/new/src/actions/breakpoints.js b/devtools/client/debugger/new/src/actions/breakpoints.js index 8b85ac66a0e4..d341384b536d 100644 --- a/devtools/client/debugger/new/src/actions/breakpoints.js +++ b/devtools/client/debugger/new/src/actions/breakpoints.js @@ -417,7 +417,7 @@ function toggleBreakpoint(line, column) { line, column }); - const isEmptyLine = (0, _ast.isEmptyLineInSource)(state, line, selectedSource); + const isEmptyLine = (0, _ast.isEmptyLineInSource)(state, line, selectedSource.id); if (!bp && isEmptyLine || bp && bp.loading) { return; @@ -434,8 +434,8 @@ function toggleBreakpoint(line, column) { } return dispatch(addBreakpoint({ - sourceId: selectedSource.get("id"), - sourceUrl: selectedSource.get("url"), + sourceId: selectedSource.id, + sourceUrl: selectedSource.url, line: line, column: column })); diff --git a/devtools/client/debugger/new/src/components/Editor/EditorMenu.js b/devtools/client/debugger/new/src/components/Editor/EditorMenu.js index b237383db7b4..08bd9b34fb97 100644 --- a/devtools/client/debugger/new/src/components/Editor/EditorMenu.js +++ b/devtools/client/debugger/new/src/components/Editor/EditorMenu.js @@ -211,9 +211,9 @@ const mapStateToProps = state => { return { selectedLocation: (0, _selectors.getSelectedLocation)(state), selectedSource, - hasPrettyPrint: !!(0, _selectors.getPrettySource)(state, selectedSource.get("id")), + hasPrettyPrint: !!(0, _selectors.getPrettySource)(state, selectedSource.id), contextMenu: (0, _selectors.getContextMenu)(state), - getFunctionText: line => (0, _function.findFunctionText)(line, selectedSource.toJS(), symbols), + getFunctionText: line => (0, _function.findFunctionText)(line, selectedSource, symbols), getFunctionLocation: line => (0, _ast.findClosestFunction)(symbols, { line, column: Infinity diff --git a/devtools/client/debugger/new/src/components/Editor/EmptyLines.js b/devtools/client/debugger/new/src/components/Editor/EmptyLines.js index 0a71012633ee..3423790d33a1 100644 --- a/devtools/client/debugger/new/src/components/Editor/EmptyLines.js +++ b/devtools/client/debugger/new/src/components/Editor/EmptyLines.js @@ -70,7 +70,7 @@ class EmptyLines extends _react.Component { const mapStateToProps = state => { const selectedSource = (0, _selectors.getSelectedSource)(state); - const foundEmptyLines = (0, _selectors.getEmptyLines)(state, selectedSource.toJS()); + const foundEmptyLines = (0, _selectors.getEmptyLines)(state, selectedSource.id); return { selectedSource, emptyLines: selectedSource ? foundEmptyLines : [] diff --git a/devtools/client/debugger/new/src/components/Editor/GutterMenu.js b/devtools/client/debugger/new/src/components/Editor/GutterMenu.js index 15b75300a7cf..4a953b8a35b5 100644 --- a/devtools/client/debugger/new/src/components/Editor/GutterMenu.js +++ b/devtools/client/debugger/new/src/components/Editor/GutterMenu.js @@ -169,7 +169,7 @@ const mapStateToProps = state => { breakpoints: (0, _selectors.getVisibleBreakpoints)(state), isPaused: (0, _selectors.isPaused)(state), contextMenu: (0, _selectors.getContextMenu)(state), - emptyLines: (0, _selectors.getEmptyLines)(state, selectedSource.toJS()) + emptyLines: (0, _selectors.getEmptyLines)(state, selectedSource.id) }; }; diff --git a/devtools/client/debugger/new/src/components/Editor/Tab.js b/devtools/client/debugger/new/src/components/Editor/Tab.js index 9fcc91ce23b3..7b1028752203 100644 --- a/devtools/client/debugger/new/src/components/Editor/Tab.js +++ b/devtools/client/debugger/new/src/components/Editor/Tab.js @@ -18,6 +18,8 @@ var _SourceIcon2 = _interopRequireDefault(_SourceIcon); var _Button = require("../shared/Button/index"); +var _text = require("../../utils/text"); + var _actions = require("../../actions/index"); var _actions2 = _interopRequireDefault(_actions); @@ -175,7 +177,7 @@ class Tab extends _react.PureComponent { shouldHide: icon => ["file", "javascript"].includes(icon) }), _react2.default.createElement("div", { className: "filename" - }, (0, _devtoolsModules.getUnicodeUrlPath)(filename)), _react2.default.createElement(_Button.CloseButton, { + }, (0, _text.truncateMiddleText)((0, _devtoolsModules.getUnicodeUrlPath)(filename), 30)), _react2.default.createElement(_Button.CloseButton, { handleClick: onClickClose, tooltip: L10N.getStr("sourceTabs.closeTabButtonTooltip") })); diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js index cafe4b0cdf30..05318cc39b4d 100644 --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js @@ -16,14 +16,14 @@ var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored[ var _classnames2 = _interopRequireDefault(_classnames); -var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js"); - var _Button = require("../shared/Button/index"); var _breakpoint = require("../../utils/breakpoint/index"); var _prefs = require("../../utils/prefs"); +var _pause = require("../../utils/pause/index"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* This Source Code Form is subject to the terms of the Mozilla Public @@ -39,19 +39,19 @@ function getBreakpointLocation(source, line, column) { function getBreakpointText(selectedSource, breakpoint) { const { condition, - text, - originalText + text } = breakpoint; + return condition || text; +} - if (condition) { - return condition; +function isCurrentlyPausedAtBreakpoint(breakpoint, frame, why) { + if (!frame || (0, _pause.isInterrupted)(why)) { + return false; } - if (!selectedSource || (0, _devtoolsSourceMap.isGeneratedId)(selectedSource.id) || originalText.length == 0) { - return text; - } - - return originalText; + const bpId = (0, _breakpoint.getLocationWithoutColumn)(breakpoint.location); + const pausedId = (0, _breakpoint.getLocationWithoutColumn)(frame.location); + return bpId === pausedId; } class Breakpoint extends _react.Component { @@ -74,7 +74,9 @@ class Breakpoint extends _react.Component { shouldComponentUpdate(nextProps) { const prevBreakpoint = this.props.breakpoint; const nextBreakpoint = nextProps.breakpoint; - return !prevBreakpoint || this.props.selectedSource != nextProps.selectedSource || prevBreakpoint.text != nextBreakpoint.text || prevBreakpoint.disabled != nextBreakpoint.disabled || prevBreakpoint.condition != nextBreakpoint.condition || prevBreakpoint.hidden != nextBreakpoint.hidden || prevBreakpoint.isCurrentlyPaused != nextBreakpoint.isCurrentlyPaused; + const wasPaused = isCurrentlyPausedAtBreakpoint(prevBreakpoint, this.props.frame, this.props.why); + const isPaused = isCurrentlyPausedAtBreakpoint(nextBreakpoint, nextProps.frame, nextProps.why); + return !prevBreakpoint || this.props.selectedSource != nextProps.selectedSource || prevBreakpoint.text != nextBreakpoint.text || prevBreakpoint.disabled != nextBreakpoint.disabled || prevBreakpoint.condition != nextBreakpoint.condition || prevBreakpoint.hidden != nextBreakpoint.hidden || prevBreakpoint.frame != nextBreakpoint.frame || wasPaused != isPaused; } destroyEditor() { @@ -144,22 +146,15 @@ class Breakpoint extends _react.Component { renderLineClose() { const { breakpoint, - onCloseClick, - selectedSource + onCloseClick } = this.props; const { location } = breakpoint; - let { + const { line, column } = location; - - if (selectedSource && (0, _devtoolsSourceMap.isGeneratedId)(selectedSource.id) && breakpoint.generatedLocation) { - line = breakpoint.generatedLocation.line; - column = breakpoint.generatedLocation.column; - } - return _react2.default.createElement("div", { className: "breakpoint-line-close" }, _react2.default.createElement("div", { @@ -174,10 +169,12 @@ class Breakpoint extends _react.Component { const { breakpoint, onClick, - onContextMenu + onContextMenu, + frame, + why } = this.props; const locationId = breakpoint.locationId; - const isCurrentlyPaused = breakpoint.isCurrentlyPaused; + const isCurrentlyPaused = isCurrentlyPausedAtBreakpoint(breakpoint, frame, why); const isDisabled = breakpoint.disabled; const isConditional = !!breakpoint.condition; return _react2.default.createElement("div", { diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js index 644d1c126b0a..d7946bcb22fd 100644 --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js @@ -18,8 +18,6 @@ var _immutable = require("devtools/client/shared/vendor/immutable"); var I = _interopRequireWildcard(_immutable); -var _reselect = require("devtools/client/debugger/new/dist/vendors").vendored["reselect"]; - var _lodash = require("devtools/client/shared/vendor/lodash"); var _Breakpoint = require("./Breakpoint"); @@ -38,10 +36,6 @@ var _source = require("../../utils/source"); var _selectors = require("../../selectors/index"); -var _pause = require("../../utils/pause/index"); - -var _breakpoint = require("../../utils/breakpoint/index"); - var _BreakpointsContextMenu = require("./BreakpointsContextMenu"); var _BreakpointsContextMenu2 = _interopRequireDefault(_BreakpointsContextMenu); @@ -54,16 +48,6 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -function isCurrentlyPausedAtBreakpoint(frame, why, breakpoint) { - if (!frame || !(0, _pause.isInterrupted)(why)) { - return false; - } - - const bpId = (0, _breakpoint.makeLocationId)(breakpoint.location); - const pausedId = (0, _breakpoint.makeLocationId)(frame.location); - return bpId === pausedId; -} - function createExceptionOption(label, value, onChange, className) { return _react2.default.createElement("div", { className: className, @@ -106,7 +90,7 @@ class Breakpoints extends _react.Component { } selectBreakpoint(breakpoint) { - this.props.selectLocation(breakpoint.location); + this.props.selectSpecificLocation(breakpoint.location); } removeBreakpoint(event, breakpoint) { @@ -116,12 +100,16 @@ class Breakpoints extends _react.Component { renderBreakpoint(breakpoint) { const { - selectedSource + selectedSource, + why, + frame } = this.props; return _react2.default.createElement(_Breakpoint2.default, { key: breakpoint.locationId, breakpoint: breakpoint, selectedSource: selectedSource, + why: why, + frame: frame, onClick: () => this.selectBreakpoint(breakpoint), onContextMenu: e => (0, _BreakpointsContextMenu2.default)(_objectSpread({}, this.props, { breakpoint, @@ -188,24 +176,10 @@ class Breakpoints extends _react.Component { } -function updateLocation(sources, frame, why, bp) { - const source = (0, _selectors.getSourceInSources)(sources, bp.location.sourceId); - const isCurrentlyPaused = isCurrentlyPausedAtBreakpoint(frame, why, bp); - const locationId = (0, _breakpoint.makeLocationId)(bp.location); - - const localBP = _objectSpread({}, bp, { - locationId, - isCurrentlyPaused, - source - }); - - return localBP; -} - -const _getBreakpoints = (0, _reselect.createSelector)(_selectors.getBreakpoints, _selectors.getSources, _selectors.getTopFrame, _selectors.getPauseReason, (breakpoints, sources, frame, why) => breakpoints.map(bp => updateLocation(sources, frame, why, bp)).filter(bp => bp.source && !bp.source.isBlackBoxed)); - const mapStateToProps = state => ({ - breakpoints: _getBreakpoints(state), + breakpoints: (0, _selectors.getMappedBreakpoints)(state), + frame: (0, _selectors.getTopFrame)(state), + why: (0, _selectors.getPauseReason)(state), selectedSource: (0, _selectors.getSelectedSource)(state) }); diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js new file mode 100644 index 000000000000..387d2522b365 --- /dev/null +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js @@ -0,0 +1,196 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require("devtools/client/shared/vendor/react"); + +var _react2 = _interopRequireDefault(_react); + +var _reactRedux = require("devtools/client/shared/vendor/react-redux"); + +var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js"); + +var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"]; + +var _classnames2 = _interopRequireDefault(_classnames); + +var _actions = require("../../../actions/index"); + +var _actions2 = _interopRequireDefault(_actions); + +var _BreakpointsContextMenu = require("./BreakpointsContextMenu"); + +var _BreakpointsContextMenu2 = _interopRequireDefault(_BreakpointsContextMenu); + +var _Button = require("../../shared/Button/index"); + +var _breakpoint = require("../../../utils/breakpoint/index"); + +var _prefs = require("../../../utils/prefs"); + +var _editor = require("../../../utils/editor/index"); + +var _selectors = require("../../../selectors/index"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function getMappedLocation(mappedLocation, selectedSource) { + return selectedSource && (0, _devtoolsSourceMap.isGeneratedId)(selectedSource.id) ? mappedLocation.generatedLocation : mappedLocation.location; +} + +class Breakpoint extends _react.PureComponent { + constructor(...args) { + var _temp; + + return _temp = super(...args), this.onContextMenu = e => { + (0, _BreakpointsContextMenu2.default)(_objectSpread({}, this.props, { + contextMenuEvent: e + })); + }, this.selectBreakpoint = () => { + const { + breakpoint, + selectSpecificLocation + } = this.props; + selectSpecificLocation(breakpoint.location); + }, this.removeBreakpoint = event => { + const { + breakpoint, + removeBreakpoint + } = this.props; + event.stopPropagation(); + removeBreakpoint(breakpoint.location); + }, this.handleBreakpointCheckbox = () => { + const { + breakpoint, + enableBreakpoint, + disableBreakpoint + } = this.props; + + if (breakpoint.loading) { + return; + } + + if (breakpoint.disabled) { + enableBreakpoint(breakpoint.location); + } else { + disableBreakpoint(breakpoint.location); + } + }, _temp; + } + + isCurrentlyPausedAtBreakpoint() { + const { + frame, + breakpoint, + selectedSource + } = this.props; + + if (!frame) { + return false; + } + + const bpId = (0, _breakpoint.getLocationWithoutColumn)(getMappedLocation(breakpoint, selectedSource)); + const frameId = (0, _breakpoint.getLocationWithoutColumn)(getMappedLocation(frame, selectedSource)); + return bpId == frameId; + } + + getBreakpointLocation() { + const { + breakpoint, + source, + selectedSource + } = this.props; + const { + column, + line + } = getMappedLocation(breakpoint, selectedSource); + const isWasm = source && source.isWasm; + const columnVal = _prefs.features.columnBreakpoints && column ? `:${column}` : ""; + const bpLocation = isWasm ? `0x${line.toString(16).toUpperCase()}` : `${line}${columnVal}`; + return bpLocation; + } + + getBreakpointText() { + const { + selectedSource, + breakpoint + } = this.props; + const { + condition + } = breakpoint; + + if (condition) { + return condition; + } + + if (selectedSource && (0, _devtoolsSourceMap.isGeneratedId)(selectedSource.id)) { + return breakpoint.text; + } + + return breakpoint.originalText; + } + + highlightText() { + const text = this.getBreakpointText(); + const sourceEditor = (0, _editor.getEditor)(); + + if (!text || !sourceEditor || !sourceEditor.editor) { + return { + __html: "" + }; + } + + const node = document.createElement("div"); + sourceEditor.editor.constructor.runMode(text, "application/javascript", node); + return { + __html: node.innerHTML + }; + } + + render() { + const { + breakpoint + } = this.props; + return _react2.default.createElement("div", { + className: (0, _classnames2.default)({ + breakpoint, + paused: this.isCurrentlyPausedAtBreakpoint(), + disabled: breakpoint.disabled, + "is-conditional": !!breakpoint.condition + }), + onClick: this.selectBreakpoint, + onContextMenu: this.onContextMenu + }, _react2.default.createElement("input", { + type: "checkbox", + className: "breakpoint-checkbox", + checked: !breakpoint.disabled, + onChange: this.handleBreakpointCheckbox, + onClick: ev => ev.stopPropagation() + }), _react2.default.createElement("label", { + className: "breakpoint-label cm-s-mozilla", + title: this.getBreakpointText(), + dangerouslySetInnerHTML: this.highlightText() + }), _react2.default.createElement("div", { + className: "breakpoint-line-close" + }, _react2.default.createElement("div", { + className: "breakpoint-line" + }, this.getBreakpointLocation()), _react2.default.createElement(_Button.CloseButton, { + handleClick: e => this.removeBreakpoint(e), + tooltip: L10N.getStr("breakpoints.removeBreakpointTooltip") + }))); + } + +} + +const mapStateToProps = state => ({ + frame: (0, _selectors.getTopFrame)(state), + selectedSource: (0, _selectors.getSelectedSource)(state) +}); + +exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Breakpoint); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js new file mode 100644 index 000000000000..32aa0d850938 --- /dev/null +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js @@ -0,0 +1,201 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = showContextMenu; + +var _devtoolsContextmenu = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-contextmenu"]; + +/* 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 . */ +function showContextMenu(props) { + const { + removeBreakpoint, + removeBreakpoints, + removeAllBreakpoints, + toggleBreakpoints, + toggleAllBreakpoints, + toggleDisabledBreakpoint, + selectLocation, + setBreakpointCondition, + openConditionalPanel, + breakpoints, + breakpoint, + contextMenuEvent + } = props; + contextMenuEvent.preventDefault(); + const deleteSelfLabel = L10N.getStr("breakpointMenuItem.deleteSelf2.label"); + const deleteAllLabel = L10N.getStr("breakpointMenuItem.deleteAll2.label"); + const deleteOthersLabel = L10N.getStr("breakpointMenuItem.deleteOthers2.label"); + const enableSelfLabel = L10N.getStr("breakpointMenuItem.enableSelf2.label"); + const enableAllLabel = L10N.getStr("breakpointMenuItem.enableAll2.label"); + const enableOthersLabel = L10N.getStr("breakpointMenuItem.enableOthers2.label"); + const disableSelfLabel = L10N.getStr("breakpointMenuItem.disableSelf2.label"); + const disableAllLabel = L10N.getStr("breakpointMenuItem.disableAll2.label"); + const disableOthersLabel = L10N.getStr("breakpointMenuItem.disableOthers2.label"); + const removeConditionLabel = L10N.getStr("breakpointMenuItem.removeCondition2.label"); + const addConditionLabel = L10N.getStr("breakpointMenuItem.addCondition2.label"); + const editConditionLabel = L10N.getStr("breakpointMenuItem.editCondition2.label"); + const deleteSelfKey = L10N.getStr("breakpointMenuItem.deleteSelf2.accesskey"); + const deleteAllKey = L10N.getStr("breakpointMenuItem.deleteAll2.accesskey"); + const deleteOthersKey = L10N.getStr("breakpointMenuItem.deleteOthers2.accesskey"); + const enableSelfKey = L10N.getStr("breakpointMenuItem.enableSelf2.accesskey"); + const enableAllKey = L10N.getStr("breakpointMenuItem.enableAll2.accesskey"); + const enableOthersKey = L10N.getStr("breakpointMenuItem.enableOthers2.accesskey"); + const disableSelfKey = L10N.getStr("breakpointMenuItem.disableSelf2.accesskey"); + const disableAllKey = L10N.getStr("breakpointMenuItem.disableAll2.accesskey"); + const disableOthersKey = L10N.getStr("breakpointMenuItem.disableOthers2.accesskey"); + const removeConditionKey = L10N.getStr("breakpointMenuItem.removeCondition2.accesskey"); + const editConditionKey = L10N.getStr("breakpointMenuItem.editCondition2.accesskey"); + const addConditionKey = L10N.getStr("breakpointMenuItem.addCondition2.accesskey"); + const otherBreakpoints = breakpoints.filter(b => b !== breakpoint); + const enabledBreakpoints = breakpoints.filter(b => !b.disabled); + const disabledBreakpoints = breakpoints.filter(b => b.disabled); + const otherEnabledBreakpoints = breakpoints.filter(b => !b.disabled && b !== breakpoint); + const otherDisabledBreakpoints = breakpoints.filter(b => b.disabled && b !== breakpoint); + const deleteSelfItem = { + id: "node-menu-delete-self", + label: deleteSelfLabel, + accesskey: deleteSelfKey, + disabled: false, + click: () => removeBreakpoint(breakpoint.location) + }; + const deleteAllItem = { + id: "node-menu-delete-all", + label: deleteAllLabel, + accesskey: deleteAllKey, + disabled: false, + click: () => removeAllBreakpoints() + }; + const deleteOthersItem = { + id: "node-menu-delete-other", + label: deleteOthersLabel, + accesskey: deleteOthersKey, + disabled: false, + click: () => removeBreakpoints(otherBreakpoints) + }; + const enableSelfItem = { + id: "node-menu-enable-self", + label: enableSelfLabel, + accesskey: enableSelfKey, + disabled: false, + click: () => toggleDisabledBreakpoint(breakpoint.location.line) + }; + const enableAllItem = { + id: "node-menu-enable-all", + label: enableAllLabel, + accesskey: enableAllKey, + disabled: false, + click: () => toggleAllBreakpoints(false) + }; + const enableOthersItem = { + id: "node-menu-enable-others", + label: enableOthersLabel, + accesskey: enableOthersKey, + disabled: false, + click: () => toggleBreakpoints(false, otherDisabledBreakpoints) + }; + const disableSelfItem = { + id: "node-menu-disable-self", + label: disableSelfLabel, + accesskey: disableSelfKey, + disabled: false, + click: () => toggleDisabledBreakpoint(breakpoint.location.line) + }; + const disableAllItem = { + id: "node-menu-disable-all", + label: disableAllLabel, + accesskey: disableAllKey, + disabled: false, + click: () => toggleAllBreakpoints(true) + }; + const disableOthersItem = { + id: "node-menu-disable-others", + label: disableOthersLabel, + accesskey: disableOthersKey, + click: () => toggleBreakpoints(true, otherEnabledBreakpoints) + }; + const removeConditionItem = { + id: "node-menu-remove-condition", + label: removeConditionLabel, + accesskey: removeConditionKey, + disabled: false, + click: () => setBreakpointCondition(breakpoint.location) + }; + const addConditionItem = { + id: "node-menu-add-condition", + label: addConditionLabel, + accesskey: addConditionKey, + click: () => { + selectLocation(breakpoint.location); + openConditionalPanel(breakpoint.location.line); + } + }; + const editConditionItem = { + id: "node-menu-edit-condition", + label: editConditionLabel, + accesskey: editConditionKey, + click: () => { + selectLocation(breakpoint.location); + openConditionalPanel(breakpoint.location.line); + } + }; + const hideEnableSelfItem = !breakpoint.disabled; + const hideEnableAllItem = disabledBreakpoints.size === 0; + const hideEnableOthersItem = otherDisabledBreakpoints.size === 0; + const hideDisableAllItem = enabledBreakpoints.size === 0; + const hideDisableOthersItem = otherEnabledBreakpoints.size === 0; + const hideDisableSelfItem = breakpoint.disabled; + const items = [{ + item: enableSelfItem, + hidden: () => hideEnableSelfItem + }, { + item: enableAllItem, + hidden: () => hideEnableAllItem + }, { + item: enableOthersItem, + hidden: () => hideEnableOthersItem + }, { + item: { + type: "separator" + }, + hidden: () => hideEnableSelfItem && hideEnableAllItem && hideEnableOthersItem + }, { + item: deleteSelfItem + }, { + item: deleteAllItem + }, { + item: deleteOthersItem, + hidden: () => breakpoints.size === 1 + }, { + item: { + type: "separator" + }, + hidden: () => hideDisableSelfItem && hideDisableAllItem && hideDisableOthersItem + }, { + item: disableSelfItem, + hidden: () => hideDisableSelfItem + }, { + item: disableAllItem, + hidden: () => hideDisableAllItem + }, { + item: disableOthersItem, + hidden: () => hideDisableOthersItem + }, { + item: { + type: "separator" + } + }, { + item: addConditionItem, + hidden: () => breakpoint.condition + }, { + item: editConditionItem, + hidden: () => !breakpoint.condition + }, { + item: removeConditionItem, + hidden: () => !breakpoint.condition + }]; + (0, _devtoolsContextmenu.showMenu)(contextMenuEvent, (0, _devtoolsContextmenu.buildMenu)(items)); +} \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js new file mode 100644 index 000000000000..3a4019fd3d23 --- /dev/null +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js @@ -0,0 +1,105 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require("devtools/client/shared/vendor/react"); + +var _react2 = _interopRequireDefault(_react); + +var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"]; + +var _classnames2 = _interopRequireDefault(_classnames); + +var _reactRedux = require("devtools/client/shared/vendor/react-redux"); + +var _Breakpoint = require("./Breakpoint"); + +var _Breakpoint2 = _interopRequireDefault(_Breakpoint); + +var _SourceIcon = require("../../shared/SourceIcon"); + +var _SourceIcon2 = _interopRequireDefault(_SourceIcon); + +var _actions = require("../../../actions/index"); + +var _actions2 = _interopRequireDefault(_actions); + +var _source = require("../../../utils/source"); + +var _breakpoint = require("../../../utils/breakpoint/index"); + +var _selectors = require("../../../selectors/index"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* 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 . */ +function createExceptionOption(label, value, onChange, className) { + return _react2.default.createElement("div", { + className: className, + onClick: onChange + }, _react2.default.createElement("input", { + type: "checkbox", + checked: value ? "checked" : "", + onChange: e => e.stopPropagation() && onChange() + }), _react2.default.createElement("div", { + className: "breakpoint-exceptions-label" + }, label)); +} + +class Breakpoints extends _react.Component { + renderExceptionsOptions() { + const { + breakpointSources, + shouldPauseOnExceptions, + shouldPauseOnCaughtExceptions, + pauseOnExceptions + } = this.props; + const isEmpty = breakpointSources.length == 0; + const exceptionsBox = createExceptionOption(L10N.getStr("pauseOnExceptionsItem2"), shouldPauseOnExceptions, () => pauseOnExceptions(!shouldPauseOnExceptions, false), "breakpoints-exceptions"); + const ignoreCaughtBox = createExceptionOption(L10N.getStr("pauseOnCaughtExceptionsItem"), shouldPauseOnCaughtExceptions, () => pauseOnExceptions(true, !shouldPauseOnCaughtExceptions), "breakpoints-exceptions-caught"); + return _react2.default.createElement("div", { + className: (0, _classnames2.default)("breakpoints-exceptions-options", { + empty: isEmpty + }) + }, exceptionsBox, shouldPauseOnExceptions ? ignoreCaughtBox : null); + } + + renderBreakpoints() { + const { + breakpointSources + } = this.props; + return [...breakpointSources.map(({ + source, + breakpoints + }) => [_react2.default.createElement("div", { + className: "breakpoint-heading", + title: source.url, + key: source.url, + onClick: () => this.props.selectSource(source.id) + }, _react2.default.createElement(_SourceIcon2.default, { + source: source + }), (0, _source.getFilename)(source)), ...breakpoints.map(breakpoint => _react2.default.createElement(_Breakpoint2.default, { + breakpoint: breakpoint, + source: source, + key: (0, _breakpoint.makeLocationId)(breakpoint.location) + }))])]; + } + + render() { + return _react2.default.createElement("div", { + className: "pane breakpoints-list" + }, this.renderExceptionsOptions(), this.renderBreakpoints()); + } + +} + +const mapStateToProps = state => ({ + breakpointSources: (0, _selectors.getBreakpointSources)(state), + selectedSource: (0, _selectors.getSelectedSource)(state) +}); + +exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Breakpoints); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/moz.build b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/moz.build new file mode 100644 index 000000000000..ee02a5734a20 --- /dev/null +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# 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/. + +DIRS += [ + +] + +DevToolsModules( + 'Breakpoint.js', + 'BreakpointsContextMenu.js', + 'index.js', +) diff --git a/devtools/client/debugger/new/src/reducers/ast.js b/devtools/client/debugger/new/src/reducers/ast.js index 1e089db735ae..9bf70204dfb7 100644 --- a/devtools/client/debugger/new/src/reducers/ast.js +++ b/devtools/client/debugger/new/src/reducers/ast.js @@ -162,17 +162,17 @@ function isSymbolsLoading(state, source) { return symbols.hasOwnProperty("loading"); } -function isEmptyLineInSource(state, line, selectedSource) { - const emptyLines = getEmptyLines(state, selectedSource); +function isEmptyLineInSource(state, line, selectedSourceId) { + const emptyLines = getEmptyLines(state, selectedSourceId); return emptyLines && emptyLines.includes(line); } -function getEmptyLines(state, source) { - if (!source) { +function getEmptyLines(state, sourceId) { + if (!sourceId) { return null; } - return state.ast.emptyLines.get(source.id); + return state.ast.emptyLines.get(sourceId); } function getPausePoints(state, sourceId) { diff --git a/devtools/client/debugger/new/src/selectors/breakpointSources.js b/devtools/client/debugger/new/src/selectors/breakpointSources.js new file mode 100644 index 000000000000..272c0d01191b --- /dev/null +++ b/devtools/client/debugger/new/src/selectors/breakpointSources.js @@ -0,0 +1,37 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getBreakpointSources = undefined; + +var _lodash = require("devtools/client/shared/vendor/lodash"); + +var _reselect = require("devtools/client/debugger/new/dist/vendors").vendored["reselect"]; + +var _selectors = require("../selectors/index"); + +var _source = require("../utils/source"); + +/* 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 . */ +function getBreakpointsForSource(source, breakpoints) { + return breakpoints.valueSeq().filter(bp => bp.location.sourceId == source.id && !bp.hidden && (bp.text || bp.condition)).sortBy(bp => bp.location.line).toJS(); +} + +function findBreakpointSources(sources, breakpoints) { + const sourceIds = (0, _lodash.uniq)(breakpoints.valueSeq().filter(bp => !bp.hidden).map(bp => bp.location.sourceId).toJS()); + const breakpointSources = sourceIds.map(id => (0, _selectors.getSourceInSources)(sources, id)).filter(source => source && !source.isBlackBoxed); + return (0, _lodash.sortBy)(breakpointSources, source => (0, _source.getFilenameFromURL)(source.url)); +} + +function _getBreakpointSources(breakpoints, sources, selectedSource) { + const breakpointSources = findBreakpointSources(sources, breakpoints); + return breakpointSources.map(source => ({ + source, + breakpoints: getBreakpointsForSource(source, breakpoints) + })); +} + +const getBreakpointSources = exports.getBreakpointSources = (0, _reselect.createSelector)(_selectors.getBreakpoints, _selectors.getSources, _getBreakpointSources); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/selectors/index.js b/devtools/client/debugger/new/src/selectors/index.js index 307922fe036e..93d96f600e8e 100644 --- a/devtools/client/debugger/new/src/selectors/index.js +++ b/devtools/client/debugger/new/src/selectors/index.js @@ -251,4 +251,13 @@ Object.defineProperty(exports, "getRelativeSources", { get: function () { return _getRelativeSources.getRelativeSources; } +}); + +var _mappedBreakpoints = require("./mappedBreakpoints"); + +Object.defineProperty(exports, "getMappedBreakpoints", { + enumerable: true, + get: function () { + return _mappedBreakpoints.getMappedBreakpoints; + } }); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/selectors/mappedBreakpoints.js b/devtools/client/debugger/new/src/selectors/mappedBreakpoints.js new file mode 100644 index 000000000000..9e63cba88dad --- /dev/null +++ b/devtools/client/debugger/new/src/selectors/mappedBreakpoints.js @@ -0,0 +1,57 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getMappedBreakpoints = undefined; + +var _breakpoint = require("../utils/breakpoint/index"); + +var _immutable = require("devtools/client/shared/vendor/immutable"); + +var _immutable2 = _interopRequireDefault(_immutable); + +var _selectors = require("../selectors/index"); + +var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js"); + +var _reselect = require("devtools/client/debugger/new/dist/vendors").vendored["reselect"]; + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* 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 . */ +function formatBreakpoint(sources, selectedSource, breakpoint) { + let location = breakpoint.location; + let text = breakpoint.originalText; + const condition = breakpoint.condition; + const disabled = breakpoint.disabled; + const locationId = (0, _breakpoint.makeLocationId)(location); + const source = (0, _selectors.getSourceInSources)(sources, location.sourceId); + + if ((0, _devtoolsSourceMap.isGeneratedId)(selectedSource.id)) { + location = breakpoint.generatedLocation || breakpoint.location; + text = breakpoint.text; + } + + const localBP = { + locationId, + location, + text, + source, + condition, + disabled + }; + return localBP; +} + +function _getMappedBreakpoints(breakpoints, sources, selectedSource) { + if (!selectedSource) { + return _immutable2.default.Map(); + } + + return breakpoints.map(bp => formatBreakpoint(sources, selectedSource, bp)).filter(bp => bp.source && !bp.source.isBlackBoxed); +} + +const getMappedBreakpoints = exports.getMappedBreakpoints = (0, _reselect.createSelector)(_selectors.getBreakpoints, _selectors.getSources, _selectors.getSelectedSource, _selectors.getTopFrame, _selectors.getPauseReason, _getMappedBreakpoints); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/selectors/moz.build b/devtools/client/debugger/new/src/selectors/moz.build index 05c9e2b43c2b..35cc3b177b73 100644 --- a/devtools/client/debugger/new/src/selectors/moz.build +++ b/devtools/client/debugger/new/src/selectors/moz.build @@ -14,6 +14,7 @@ DevToolsModules( 'inComponent.js', 'index.js', 'isSelectedFrameVisible.js', + 'mappedBreakpoints.js', 'visibleBreakpoints.js', 'visibleSelectedFrame.js', ) diff --git a/devtools/client/debugger/new/src/utils/breakpoint/index.js b/devtools/client/debugger/new/src/utils/breakpoint/index.js index 2e0345f2a29a..fe5fc7acb7da 100644 --- a/devtools/client/debugger/new/src/utils/breakpoint/index.js +++ b/devtools/client/debugger/new/src/utils/breakpoint/index.js @@ -31,6 +31,7 @@ Object.defineProperty(exports, "findScopeByName", { exports.firstString = firstString; exports.locationMoved = locationMoved; exports.makeLocationId = makeLocationId; +exports.getLocationWithoutColumn = getLocationWithoutColumn; exports.makePendingLocationId = makePendingLocationId; exports.assertBreakpoint = assertBreakpoint; exports.assertPendingBreakpoint = assertPendingBreakpoint; @@ -77,6 +78,14 @@ function makeLocationId(location) { return `${sourceId}:${line}:${columnString}`; } +function getLocationWithoutColumn(location) { + const { + sourceId, + line + } = location; + return `${sourceId}:${line}`; +} + function makePendingLocationId(location) { assertPendingLocation(location); const { diff --git a/devtools/client/debugger/new/src/utils/pause/mapScopes/index.js b/devtools/client/debugger/new/src/utils/pause/mapScopes/index.js index 596756ca7d51..ac60390c5f65 100644 --- a/devtools/client/debugger/new/src/utils/pause/mapScopes/index.js +++ b/devtools/client/debugger/new/src/utils/pause/mapScopes/index.js @@ -9,6 +9,8 @@ var _parser = require("../../../workers/parser/index"); var _locColumn = require("./locColumn"); +var _rangeMetadata = require("./rangeMetadata"); + var _findGeneratedBindingFromPosition = require("./findGeneratedBindingFromPosition"); var _buildGeneratedBindingList = require("./buildGeneratedBindingList"); @@ -30,10 +32,11 @@ async function buildMappedScopes(source, frame, scopes, sourceMaps, client) { } const generatedAstBindings = (0, _buildGeneratedBindingList.buildGeneratedBindingList)(scopes, generatedAstScopes, frame.this); + const originalRanges = await (0, _rangeMetadata.loadRangeMetadata)(source, frame, originalAstScopes, sourceMaps); const { mappedOriginalScopes, expressionLookup - } = await mapOriginalBindingsToGenerated(source, originalAstScopes, generatedAstBindings, client, sourceMaps); + } = await mapOriginalBindingsToGenerated(source, originalRanges, originalAstScopes, generatedAstBindings, client, sourceMaps); const mappedGeneratedScopes = generateClientScope(scopes, mappedOriginalScopes); return isReliableScope(mappedGeneratedScopes) ? { mappings: expressionLookup, @@ -41,7 +44,7 @@ async function buildMappedScopes(source, frame, scopes, sourceMaps, client) { } : null; } -async function mapOriginalBindingsToGenerated(source, originalAstScopes, generatedAstBindings, client, sourceMaps) { +async function mapOriginalBindingsToGenerated(source, originalRanges, originalAstScopes, generatedAstBindings, client, sourceMaps) { const expressionLookup = {}; const mappedOriginalScopes = []; const cachedSourceMaps = batchScopeMappings(originalAstScopes, source, sourceMaps); @@ -51,7 +54,7 @@ async function mapOriginalBindingsToGenerated(source, originalAstScopes, generat for (const name of Object.keys(item.bindings)) { const binding = item.bindings[name]; - const result = await findGeneratedBinding(cachedSourceMaps, client, source, name, binding, generatedAstBindings); + const result = await findGeneratedBinding(cachedSourceMaps, client, source, name, binding, originalRanges, generatedAstBindings); if (result) { generatedBindings[name] = result.grip; @@ -213,7 +216,14 @@ function generateClientScope(scopes, originalScopes) { return result; } -async function findGeneratedBinding(sourceMaps, client, source, name, originalBinding, generatedAstBindings) { +function hasValidIdent(range, pos) { + return range.type === "match" || // For declarations, we allow the range on the identifier to be a + // more general "contains" to increase the chances of a match. + pos.type !== "ref" && range.type === "contains"; +} // eslint-disable-next-line complexity + + +async function findGeneratedBinding(sourceMaps, client, source, name, originalBinding, originalRanges, generatedAstBindings) { // If there are no references to the implicits, then we have no way to // even attempt to map it back to the original since there is no location // data to use. Bail out instead of just showing it as unmapped. @@ -227,27 +237,43 @@ async function findGeneratedBinding(sourceMaps, client, source, name, originalBi let genContent = null; for (const pos of refs) { - if (originalBinding.type === "import") { - genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForImportBinding)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); - } else { - genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForStandardBinding)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + const range = (0, _rangeMetadata.findMatchingRange)(originalRanges, pos); + + if (range && hasValidIdent(range, pos)) { + if (originalBinding.type === "import") { + genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForImportBinding)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + } else { + genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForStandardBinding)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + } } if ((pos.type === "class-decl" || pos.type === "class-inner") && source.contentType && source.contentType.match(/\/typescript/)) { - // Resolve to first binding in the range - const declContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForNormalDeclaration)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + const declRange = (0, _rangeMetadata.findMatchingRange)(originalRanges, pos.declaration); - if (declContent) { - // Prefer the declaration mapping in this case because TS sometimes - // maps class declaration names to "export.Foo = Foo;" or to - // the decorator logic itself - genContent = declContent; + if (declRange && declRange.type !== "multiple") { + // Resolve to first binding in the range + const declContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForNormalDeclaration)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + + if (declContent) { + // Prefer the declaration mapping in this case because TS sometimes + // maps class declaration names to "export.Foo = Foo;" or to + // the decorator logic itself + genContent = declContent; + } } } if (!genContent && (pos.type === "import-decl" || pos.type === "import-ns-decl")) { - // match the import declaration location - genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForImportDeclaration)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + const declRange = (0, _rangeMetadata.findMatchingRange)(originalRanges, pos.declaration); // The import declaration should have an original position mapping, + // but otherwise we don't really have preferences on the range type + // because it can have multiple bindings, but we do want to make sure + // that all of the bindings that match the range are part of the same + // import declaration. + + if (declRange && declRange.singleDeclaration) { + // match the import declaration location + genContent = await (0, _findGeneratedBindingFromPosition.findGeneratedBindingForImportDeclaration)(sourceMaps, client, source, pos, name, originalBinding.type, generatedAstBindings); + } } if (genContent) { diff --git a/devtools/client/debugger/new/src/utils/pause/mapScopes/moz.build b/devtools/client/debugger/new/src/utils/pause/mapScopes/moz.build index b8c2b558d199..ac03adebea4e 100644 --- a/devtools/client/debugger/new/src/utils/pause/mapScopes/moz.build +++ b/devtools/client/debugger/new/src/utils/pause/mapScopes/moz.build @@ -16,4 +16,5 @@ DevToolsModules( 'locColumn.js', 'mappingContains.js', 'positionCmp.js', + 'rangeMetadata.js', ) diff --git a/devtools/client/debugger/new/src/utils/pause/mapScopes/rangeMetadata.js b/devtools/client/debugger/new/src/utils/pause/mapScopes/rangeMetadata.js new file mode 100644 index 000000000000..a7539cd804f8 --- /dev/null +++ b/devtools/client/debugger/new/src/utils/pause/mapScopes/rangeMetadata.js @@ -0,0 +1,92 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.loadRangeMetadata = loadRangeMetadata; +exports.findMatchingRange = findMatchingRange; + +var _locColumn = require("./locColumn"); + +var _positionCmp = require("./positionCmp"); + +var _filtering = require("./filtering"); + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +async function loadRangeMetadata(source, frame, originalAstScopes, sourceMaps) { + const originalRanges = await sourceMaps.getOriginalRanges(frame.location.sourceId, source.url); + const sortedOriginalAstBindings = []; + + for (const item of originalAstScopes) { + for (const name of Object.keys(item.bindings)) { + for (const ref of item.bindings[name].refs) { + sortedOriginalAstBindings.push(ref); + } + } + } + + sortedOriginalAstBindings.sort((a, b) => (0, _positionCmp.positionCmp)(a.start, b.start)); + let i = 0; + return originalRanges.map(range => { + const bindings = []; + + while (i < sortedOriginalAstBindings.length && (sortedOriginalAstBindings[i].start.line < range.line || sortedOriginalAstBindings[i].start.line === range.line && (0, _locColumn.locColumn)(sortedOriginalAstBindings[i].start) < range.columnStart)) { + i++; + } + + while (i < sortedOriginalAstBindings.length && sortedOriginalAstBindings[i].start.line === range.line && (0, _locColumn.locColumn)(sortedOriginalAstBindings[i].start) >= range.columnStart && (0, _locColumn.locColumn)(sortedOriginalAstBindings[i].start) < range.columnEnd) { + bindings.push(sortedOriginalAstBindings[i]); + i++; + } + + let type = "empty"; + let singleDeclaration = true; + + if (bindings.length === 1) { + const binding = bindings[0]; + + if (binding.start.line === range.line && binding.start.column === range.columnStart) { + type = "match"; + } else { + type = "contains"; + } + } else if (bindings.length > 1) { + type = "multiple"; + const binding = bindings[0]; + const declStart = binding.type !== "ref" ? binding.declaration.start : null; + singleDeclaration = bindings.every(b => { + return declStart && b.type !== "ref" && (0, _positionCmp.positionCmp)(declStart, b.declaration.start) === 0; + }); + } + + return _objectSpread({ + type, + singleDeclaration + }, range); + }); +} + +function findMatchingRange(sortedOriginalRanges, bindingRange) { + return (0, _filtering.filterSortedArray)(sortedOriginalRanges, range => { + if (range.line < bindingRange.start.line) { + return -1; + } + + if (range.line > bindingRange.start.line) { + return 1; + } + + if (range.columnEnd <= (0, _locColumn.locColumn)(bindingRange.start)) { + return -1; + } + + if (range.columnStart > (0, _locColumn.locColumn)(bindingRange.start)) { + return 1; + } + + return 0; + }).pop(); +} \ No newline at end of file diff --git a/devtools/client/debugger/new/src/utils/source.js b/devtools/client/debugger/new/src/utils/source.js index 3591525e8a23..d10f261d306e 100644 --- a/devtools/client/debugger/new/src/utils/source.js +++ b/devtools/client/debugger/new/src/utils/source.js @@ -466,5 +466,5 @@ function getSourceClassnames(source, sourceMetaData) { return "blackBox"; } - return sourceTypes[(0, _sourcesTree.getExtension)(source)] || defaultClassName; + return sourceTypes[(0, _sourcesTree.getExtension)(source.url)] || defaultClassName; } \ No newline at end of file diff --git a/devtools/client/debugger/new/src/utils/sources-tree/utils.js b/devtools/client/debugger/new/src/utils/sources-tree/utils.js index aee48530cf75..a3d7e1ba80ed 100644 --- a/devtools/client/debugger/new/src/utils/sources-tree/utils.js +++ b/devtools/client/debugger/new/src/utils/sources-tree/utils.js @@ -47,8 +47,7 @@ function isDirectory(url) { return (parts.length === 0 || url.path.slice(-1) === "/" || nodeHasChildren(url)) && url.name != "(index)"; } -function getExtension(source) { - const url = source.get ? source.get("url") : source.url; +function getExtension(url = "") { const parsedUrl = (0, _url.parse)(url).pathname; if (!parsedUrl) { @@ -59,7 +58,7 @@ function getExtension(source) { } function isNotJavaScript(source) { - return ["css", "svg", "png"].includes(getExtension(source)); + return ["css", "svg", "png"].includes(getExtension(source.url)); } function isInvalidUrl(url, source) { diff --git a/devtools/client/debugger/new/src/utils/text.js b/devtools/client/debugger/new/src/utils/text.js index aa5f7afe824c..23165c648fe3 100644 --- a/devtools/client/debugger/new/src/utils/text.js +++ b/devtools/client/debugger/new/src/utils/text.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.formatKeyShortcut = undefined; +exports.truncateMiddleText = exports.formatKeyShortcut = undefined; var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"]; @@ -40,5 +40,26 @@ function formatKeyShortcut(shortcut) { return shortcut.replace(/CommandOrControl\+|CmdOrCtrl\+/g, `${L10N.getStr("ctrl")} `).replace(/Shift\+/g, "Shift "); } +/** + * Truncates the received text to the maxLength in the format: + * Original: 'this is a very long text and ends here' + * Truncated: 'this is a ver...and ends here' + * @param {String} sourceText - Source text + * @param {Number} maxLength - Max allowed length + * @memberof utils/text + * @static + */ -exports.formatKeyShortcut = formatKeyShortcut; \ No newline at end of file + +function truncateMiddleText(sourceText, maxLength) { + let truncatedText = sourceText; + + if (sourceText.length > maxLength) { + truncatedText = `${sourceText.substring(0, Math.round(maxLength / 2) - 2)}...${sourceText.substring(sourceText.length - Math.round(maxLength / 2 - 1))}`; + } + + return truncatedText; +} + +exports.formatKeyShortcut = formatKeyShortcut; +exports.truncateMiddleText = truncateMiddleText; \ No newline at end of file diff --git a/devtools/client/debugger/new/src/workers/parser/getScopes/visitor.js b/devtools/client/debugger/new/src/workers/parser/getScopes/visitor.js index 92b0fa50296c..e500ff710931 100644 --- a/devtools/client/debugger/new/src/workers/parser/getScopes/visitor.js +++ b/devtools/client/debugger/new/src/workers/parser/getScopes/visitor.js @@ -89,7 +89,7 @@ function toParsedScopes(children, sourceId) { return { start: scope.loc.start, end: scope.loc.end, - type: scope.type === "module" ? "block" : scope.type, + type: scope.type === "module" || scope.type === "function-body" ? "block" : scope.type, displayName: scope.displayName, bindings: scope.bindings, children: toParsedScopes(scope.children, sourceId) @@ -190,10 +190,20 @@ function isLetOrConst(node) { } function hasLexicalDeclaration(node, parent) { + const nodes = []; + + if (t.isSwitchStatement(node)) { + for (const caseNode of node.cases) { + nodes.push(...caseNode.consequent); + } + } else { + nodes.push(...node.body); + } + const isFunctionBody = t.isFunction(parent, { body: node }); - return node.body.some(child => isLexicalVariable(child) || !isFunctionBody && child.type === "FunctionDeclaration" || child.type === "ClassDeclaration"); + return nodes.some(child => isLexicalVariable(child) || t.isClassDeclaration(child) || !isFunctionBody && t.isFunctionDeclaration(child)); } function isLexicalVariable(node) { @@ -257,19 +267,27 @@ const scopeCollectionVisitor = { // This ignores Annex B function declaration hoisting, which // is probably a fine assumption. state.declarationBindingIds.add(node.id); - const fnScope = getVarScope(scope); - scope.bindings[node.id.name] = { - type: fnScope === scope ? "var" : "let", - refs: [{ - type: "fn-decl", - start: fromBabelLocation(node.id.loc.start, state.sourceId), - end: fromBabelLocation(node.id.loc.end, state.sourceId), - declaration: { - start: fromBabelLocation(node.loc.start, state.sourceId), - end: fromBabelLocation(node.loc.end, state.sourceId) - } - }] - }; + const refs = [{ + type: "fn-decl", + start: fromBabelLocation(node.id.loc.start, state.sourceId), + end: fromBabelLocation(node.id.loc.end, state.sourceId), + declaration: { + start: fromBabelLocation(node.loc.start, state.sourceId), + end: fromBabelLocation(node.loc.end, state.sourceId) + } + }]; + + if (scope.type === "block") { + scope.bindings[node.id.name] = { + type: "let", + refs + }; + } else { + getVarScope(scope).bindings[node.id.name] = { + type: "var", + refs + }; + } } scope = pushTempScope(state, "function", (0, _getFunctionName2.default)(node, parentNode), { @@ -290,6 +308,13 @@ const scopeCollectionVisitor = { refs: [] }; } + + if (t.isBlockStatement(node.body) && hasLexicalDeclaration(node.body, node)) { + scope = pushTempScope(state, "function-body", "Function Body", { + start: fromBabelLocation(node.body.loc.start, state.sourceId), + end: fromBabelLocation(node.body.loc.end, state.sourceId) + }); + } } else if (t.isClass(node)) { if (t.isIdentifier(node.id)) { // For decorated classes, the AST considers the first the decorator @@ -359,7 +384,8 @@ const scopeCollectionVisitor = { end: fromBabelLocation(node.loc.end, state.sourceId) }); parseDeclarator(node.param, scope, "var", "catch", node, state); - } else if (t.isBlockStatement(node) && hasLexicalDeclaration(node, parentNode)) { + } else if (t.isBlockStatement(node) && // Function body's are handled in the function logic above. + !t.isFunction(parentNode) && hasLexicalDeclaration(node, parentNode)) { // Debugger will create new lexical environment for the block. pushTempScope(state, "block", "Block", { start: fromBabelLocation(node.loc.start, state.sourceId), @@ -489,7 +515,7 @@ const scopeCollectionVisitor = { type: "implicit", refs: [] }; - } else if (t.isSwitchStatement(node) && node.cases.some(caseNode => caseNode.consequent.some(child => isLexicalVariable(child)))) { + } else if (t.isSwitchStatement(node) && hasLexicalDeclaration(node, parentNode)) { pushTempScope(state, "block", "Switch", { start: fromBabelLocation(node.loc.start, state.sourceId), end: fromBabelLocation(node.loc.end, state.sourceId) diff --git a/devtools/client/debugger/new/test/mochitest/browser.ini b/devtools/client/debugger/new/test/mochitest/browser.ini index 5eeb81513049..e81239fb1096 100644 --- a/devtools/client/debugger/new/test/mochitest/browser.ini +++ b/devtools/client/debugger/new/test/mochitest/browser.ini @@ -31,6 +31,8 @@ support-files = examples/sourcemapped/fixtures/babel-for-loops/output.js.map examples/sourcemapped/fixtures/babel-functions/output.js examples/sourcemapped/fixtures/babel-functions/output.js.map + examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js + examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js.map examples/sourcemapped/fixtures/babel-type-module/output.js examples/sourcemapped/fixtures/babel-type-module/output.js.map examples/sourcemapped/fixtures/babel-type-script/output.js diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes-mutations.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes-mutations.js index 9b64e80b10b5..22902a31f43c 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes-mutations.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes-mutations.js @@ -25,7 +25,7 @@ add_task(async function() { let onPaused = waitForPaused(dbg); invokeInTab("mutate"); await onPaused; - await waitForLoadedSource(dbg, "script-mutate"); + await waitForSelectedSource(dbg, "script-mutate"); is( getScopeNodeLabel(dbg, 2), diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js index 9ffaea369eeb..8f54112e4bc6 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js @@ -46,7 +46,7 @@ add_task(async function() { "Block", ["three", "5"], ["two", "4"], - "Block", + "Function Body", ["three", "3"], ["two", "2"], "root", @@ -73,7 +73,7 @@ add_task(async function() { ["aConst", '"const2"'], ["aLet", '"let2"'], "Outer:_Outer()", - "Block", + "Function Body", ["aConst", '"const1"'], ["aLet", '"let1"'], "Outer()", @@ -86,7 +86,7 @@ add_task(async function() { "babel-line-start-bindings-es6", { line: 19, column: 4 }, [ - "Block", + "Function Body", ["", "{\u2026}"], ["one", "1"], ["two", "2"], @@ -102,7 +102,7 @@ add_task(async function() { "babel-this-arguments-bindings", { line: 4, column: 4 }, [ - "Block", + "Function Body", ["", '"this-value"'], ["arrow", "undefined"], "fn", @@ -123,7 +123,7 @@ add_task(async function() { "arrow", ["", '"this-value"'], ["argArrow", '"arrow-arg"'], - "Block", + "Function Body", "arrow()", "fn", ["arg", '"arg-value"'], @@ -158,12 +158,12 @@ add_task(async function() { ]); await breakpointScopes(dbg, "babel-classes", { line: 12, column: 6 }, [ - "Block", + "Function Body", ["three", "3"], ["two", "2"], "Class", "Another()", - "Block", + "Function Body", "Another()", ["one", "1"], "Thing()", @@ -174,7 +174,7 @@ add_task(async function() { await breakpointScopes(dbg, "babel-for-loops", { line: 5, column: 4 }, [ "For", ["i", "1"], - "Block", + "Function Body", ["i", "0"], "Module", "root()" @@ -183,7 +183,7 @@ add_task(async function() { await breakpointScopes(dbg, "babel-for-loops", { line: 9, column: 4 }, [ "For", ["i", '"2"'], - "Block", + "Function Body", ["i", "0"], "Module", "root()" @@ -192,7 +192,7 @@ add_task(async function() { await breakpointScopes(dbg, "babel-for-loops", { line: 13, column: 4 }, [ "For", ["i", "3"], - "Block", + "Function Body", ["i", "0"], "Module", "root()" @@ -201,13 +201,13 @@ add_task(async function() { await breakpointScopes(dbg, "babel-functions", { line: 6, column: 8 }, [ "arrow", ["p3", "undefined"], - "Block", + "Function Body", "arrow()", "inner", ["p2", "undefined"], "Function Expression", "inner()", - "Block", + "Function Body", "inner()", "decl", ["p1", "undefined"], @@ -268,7 +268,7 @@ add_task(async function() { await breakpointScopes(dbg, "babel-switches", { line: 7, column: 6 }, [ "Switch", ["val", "2"], - "Block", + "Function Body", ["val", "1"], "Module", "root()" @@ -279,7 +279,7 @@ add_task(async function() { ["val", "3"], "Switch", ["val", "2"], - "Block", + "Function Body", ["val", "1"], "Module", "root()" @@ -290,12 +290,21 @@ add_task(async function() { ["two", "2"], "Catch", ["err", '"AnError"'], - "Block", + "Function Body", ["one", "1"], "Module", "root()" ]); + await breakpointScopes(dbg, "babel-lex-and-nonlex", { line: 3, column: 4 }, [ + "Function Body", + "Thing()", + "root", + "someHelper()", + "Module", + "root()" + ]); + await breakpointScopes( dbg, "babel-modules-webpack", diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps3.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps3.js index 725dab3cbdda..4998590a8b26 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps3.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps3.js @@ -40,7 +40,7 @@ add_task(async function() { is(getScopeLabel(dbg, 2), "na"); is(getScopeLabel(dbg, 3), "nb"); - is(getScopeLabel(dbg, 4), "Block"); + is(getScopeLabel(dbg, 4), "Function Body"); await toggleScopeNode(dbg, 4); diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemapped.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemapped.html index 27c85734fddd..7674d43f9765 100644 --- a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemapped.html +++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemapped.html @@ -27,6 +27,8 @@ + + diff --git a/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/input.js b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/input.js new file mode 100644 index 000000000000..5704081677d6 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/input.js @@ -0,0 +1,9 @@ +export default function root() { + function someHelper(){ + console.log("pause here", root, Thing); + } + + class Thing {} + + someHelper(); +} diff --git a/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js new file mode 100644 index 000000000000..5946da6f9cf7 --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js @@ -0,0 +1,90 @@ +var babelLexAndNonlex = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); +/* harmony export (immutable) */ __webpack_exports__["default"] = root; +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function root() { + function someHelper() { + console.log("pause here", root, Thing); + } + + var Thing = function Thing() { + _classCallCheck(this, Thing); + }; + + someHelper(); +} + +/***/ }) +/******/ ])["default"]; +//# sourceMappingURL=output.js.map \ No newline at end of file diff --git a/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js.map b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js.map new file mode 100644 index 000000000000..2685157d69ee --- /dev/null +++ b/devtools/client/debugger/new/test/mochitest/examples/sourcemapped/fixtures/babel-lex-and-nonlex/output.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///webpack/bootstrap 683415abc3361bb5ff85","webpack:///./fixtures/babel-lex-and-nonlex/input.js"],"names":["root","someHelper","console","log","Thing"],"mappings":";;AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;;AC7De,SAASA,IAAT,GAAgB;AAC7B,WAASC,UAAT,GAAqB;AACnBC,YAAQC,GAAR,CAAY,YAAZ,EAA0BH,IAA1B,EAAgCI,KAAhC;AACD;;AAH4B,MAKvBA,KALuB;AAAA;AAAA;;AAO7BH;AACD,C","file":"fixtures/babel-lex-and-nonlex/output.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 683415abc3361bb5ff85","export default function root() {\n function someHelper(){\n console.log(\"pause here\", root, Thing);\n }\n\n class Thing {}\n\n someHelper();\n}\n\n\n\n// WEBPACK FOOTER //\n// ./fixtures/babel-lex-and-nonlex/input.js"],"sourceRoot":""} \ No newline at end of file diff --git a/devtools/client/shared/source-map/index.js b/devtools/client/shared/source-map/index.js index 45519e8a15ce..5c25d5953d12 100644 --- a/devtools/client/shared/source-map/index.js +++ b/devtools/client/shared/source-map/index.js @@ -437,6 +437,7 @@ const { const dispatcher = new WorkerDispatcher(); const getOriginalURLs = dispatcher.task("getOriginalURLs"); +const getOriginalRanges = dispatcher.task("getOriginalRanges"); const getGeneratedRanges = dispatcher.task("getGeneratedRanges", { queue: true }); @@ -460,6 +461,7 @@ module.exports = { isOriginalId, hasMappedSource, getOriginalURLs, + getOriginalRanges, getGeneratedRanges, getGeneratedLocation, getAllGeneratedLocations, diff --git a/devtools/client/shared/source-map/worker.js b/devtools/client/shared/source-map/worker.js index 4526735b6402..a47f7e44c8e2 100644 --- a/devtools/client/shared/source-map/worker.js +++ b/devtools/client/shared/source-map/worker.js @@ -1973,6 +1973,7 @@ exports.ArraySet = ArraySet; const { getOriginalURLs, + getOriginalRanges, getGeneratedRanges, getGeneratedLocation, getAllGeneratedLocations, @@ -1993,6 +1994,7 @@ const { // easier to unit test. self.onmessage = workerHandler({ getOriginalURLs, + getOriginalRanges, getGeneratedRanges, getGeneratedLocation, getAllGeneratedLocations, @@ -2046,6 +2048,57 @@ async function getOriginalURLs(generatedSource) { const COMPUTED_SPANS = new WeakSet(); +const SOURCE_MAPPINGS = new WeakMap(); +async function getOriginalRanges(sourceId, url) { + if (!isOriginalId(sourceId)) { + return []; + } + + const generatedSourceId = originalToGeneratedId(sourceId); + const map = await getSourceMap(generatedSourceId); + if (!map) { + return []; + } + + let mappings = SOURCE_MAPPINGS.get(map); + if (!mappings) { + mappings = new Map(); + SOURCE_MAPPINGS.set(map, mappings); + } + + let fileMappings = mappings.get(url); + if (!fileMappings) { + fileMappings = []; + mappings.set(url, fileMappings); + + const originalMappings = fileMappings; + map.eachMapping(mapping => { + if (mapping.source !== url) { + return; + } + + const last = originalMappings[originalMappings.length - 1]; + + if (last && last.line === mapping.originalLine) { + if (last.columnStart < mapping.originalColumn) { + last.columnEnd = mapping.originalColumn; + } else { + // Skip this duplicate original location, + return; + } + } + + originalMappings.push({ + line: mapping.originalLine, + columnStart: mapping.originalColumn, + columnEnd: Infinity + }); + }, null, SourceMapConsumer.ORIGINAL_ORDER); + } + + return fileMappings; +} + /** * Given an original location, find the ranges on the generated file that * are mapped from the original range containing the location. @@ -2234,6 +2287,7 @@ function applySourceMap(generatedId, url, code, mappings) { module.exports = { getOriginalURLs, + getOriginalRanges, getGeneratedRanges, getGeneratedLocation, getAllGeneratedLocations, diff --git a/devtools/client/shared/vendor/WasmDis.js b/devtools/client/shared/vendor/WasmDis.js index 0a73193e1cea..e84b2b975769 100644 --- a/devtools/client/shared/vendor/WasmDis.js +++ b/devtools/client/shared/vendor/WasmDis.js @@ -956,7 +956,9 @@ var WasmDisassembler = /** @class */ (function () { case 30 /* CODE_OPERATOR */: var operator = reader.result; if (operator.code == 11 /* end */ && this._indentLevel == 0) { - // reached of the function, skipping the operator + // reached of the function, closing function body + this.appendBuffer(" )"); + this.newLine(); break; } switch (operator.code) { @@ -980,8 +982,7 @@ var WasmDisassembler = /** @class */ (function () { case 31 /* END_FUNCTION_BODY */: this._funcIndex++; this._backrefLabels = null; - this.appendBuffer(" )"); - this.newLine(); + // See case BinaryReaderState.CODE_OPERATOR for closing of body break; default: throw new Error("Expectected state: " + reader.state);