diff options
Diffstat (limited to 'devtools/client/debugger/src/components/Editor')
15 files changed, 479 insertions, 143 deletions
diff --git a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js index 8ff84c287a..97876f2f00 100644 --- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js +++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js @@ -121,7 +121,7 @@ export class ConditionalPanel extends PureComponent { return this.clearConditionalPanel(); } - componentDidUpdate(prevProps) { + componentDidUpdate() { this.keepFocusOnInput(); } diff --git a/devtools/client/debugger/src/components/Editor/Footer.css b/devtools/client/debugger/src/components/Editor/Footer.css index 4a3272879b..f3382e94b5 100644 --- a/devtools/client/debugger/src/components/Editor/Footer.css +++ b/devtools/client/debugger/src/components/Editor/Footer.css @@ -67,6 +67,45 @@ opacity: 0.6; } +.devtools-button.debugger-source-map-button { + display: inline-flex; + align-items: center; + margin: 0; + --menuitem-icon-image: url("chrome://devtools/content/debugger/images/sourcemap.svg"); + + &.not-mapped { + --icon-color: var(--theme-icon-dimmed-color); + } + + &.original { + --icon-color: var(--theme-icon-checked-color); + --menuitem-icon-image: url("chrome://devtools/content/debugger/images/sourcemap-active.svg"); + } + + &.error { + --icon-color: var(--theme-icon-warning-color); + } + + &.disabled { + --icon-color: var(--theme-icon-dimmed-color); + --menuitem-icon-image: url("chrome://devtools/content/debugger/images/sourcemap-disabled.svg"); + } + + &.loading { + --menuitem-icon-image: url("chrome://devtools/content/debugger/images/loader.svg"); + } + + &::before { + /* override default style to have similar left and right margins */ + margin-inline-end: 3px; + color: var(--icon-color, currentColor); + } + + &.loading::before { + animation: spin 2s linear infinite; + } +} + .source-footer .mapped-source, .source-footer .cursor-position { color: var(--theme-body-color); diff --git a/devtools/client/debugger/src/components/Editor/Footer.js b/devtools/client/debugger/src/components/Editor/Footer.js index c4ff02caf4..69c7b52b68 100644 --- a/devtools/client/debugger/src/components/Editor/Footer.js +++ b/devtools/client/debugger/src/components/Editor/Footer.js @@ -7,6 +7,7 @@ import { div, button, span, + hr, } from "devtools/client/shared/vendor/react-dom-factories"; import PropTypes from "devtools/client/shared/vendor/react-prop-types"; import { connect } from "devtools/client/shared/vendor/react-redux"; @@ -23,14 +24,23 @@ import { isSourceOnSourceMapIgnoreList, isSourceMapIgnoreListEnabled, getSelectedMappedSource, + getSourceMapErrorForSourceActor, + areSourceMapsEnabled, + getShouldSelectOriginalLocation, + isSourceActorWithSourceMap, + getSourceMapResolvedURL, + isSelectedMappedSourceLoading, } from "../../selectors/index"; -import { isPretty, getFilename, shouldBlackbox } from "../../utils/source"; +import { isPretty, shouldBlackbox } from "../../utils/source"; import { PaneToggleButton } from "../shared/Button/index"; import AccessibleImage from "../shared/AccessibleImage"; const classnames = require("resource://devtools/client/shared/classnames.js"); +const MenuButton = require("resource://devtools/client/shared/components/menu/MenuButton.js"); +const MenuItem = require("resource://devtools/client/shared/components/menu/MenuItem.js"); +const MenuList = require("resource://devtools/client/shared/components/menu/MenuList.js"); class SourceFooter extends PureComponent { static get propTypes() { @@ -155,9 +165,11 @@ class SourceFooter extends PureComponent { } renderCommands() { - const commands = [this.blackBoxButton(), this.prettyPrintButton()].filter( - Boolean - ); + const commands = [ + this.blackBoxButton(), + this.prettyPrintButton(), + this.renderSourceMapButton(), + ].filter(Boolean); return commands.length ? div( @@ -169,7 +181,7 @@ class SourceFooter extends PureComponent { : null; } - renderSourceSummary() { + renderMappedSource() { const { mappedSource, jumpToMappedLocation, selectedLocation } = this.props; if (!mappedSource) { @@ -182,12 +194,11 @@ class SourceFooter extends PureComponent { : "sourceFooter.mappedGeneratedSource.tooltip", mappedSource.url ); - const filename = getFilename(mappedSource); const label = L10N.getFormatStr( mappedSource.isOriginal ? "sourceFooter.mappedOriginalSource.title" : "sourceFooter.mappedGeneratedSource.title", - filename + mappedSource.shortName ); return button( { @@ -227,6 +238,148 @@ class SourceFooter extends PureComponent { ); } + getSourceMapLabel() { + if (!this.props.selectedLocation) { + return undefined; + } + if (!this.props.areSourceMapsEnabled) { + return L10N.getStr("sourceFooter.sourceMapButton.disabled"); + } + if (this.props.sourceMapError) { + return undefined; + } + if (!this.props.isSourceActorWithSourceMap) { + return L10N.getStr("sourceFooter.sourceMapButton.sourceNotMapped"); + } + if (this.props.selectedLocation.source.isOriginal) { + return L10N.getStr("sourceFooter.sourceMapButton.isOriginalSource"); + } + return L10N.getStr("sourceFooter.sourceMapButton.isBundleSource"); + } + + getSourceMapTitle() { + if (this.props.sourceMapError) { + return L10N.getFormatStr( + "sourceFooter.sourceMapButton.errorTitle", + this.props.sourceMapError + ); + } + if (this.props.isSourceMapLoading) { + return L10N.getStr("sourceFooter.sourceMapButton.loadingTitle"); + } + return L10N.getStr("sourceFooter.sourceMapButton.title"); + } + + renderSourceMapButton() { + const { toolboxDoc } = this.context; + + return React.createElement( + MenuButton, + { + menuId: "debugger-source-map-button", + toolboxDoc, + className: classnames("devtools-button", "debugger-source-map-button", { + error: !!this.props.sourceMapError, + loading: this.props.isSourceMapLoading, + disabled: !this.props.areSourceMapsEnabled, + "not-mapped": + !this.props.selectedLocation?.source.isOriginal && + !this.props.isSourceActorWithSourceMap, + original: this.props.selectedLocation?.source.isOriginal, + }), + title: this.getSourceMapTitle(), + label: this.getSourceMapLabel(), + icon: true, + }, + () => this.renderSourceMapMenuItems() + ); + } + + renderSourceMapMenuItems() { + const items = [ + React.createElement(MenuItem, { + className: "menu-item debugger-source-map-enabled", + checked: this.props.areSourceMapsEnabled, + label: L10N.getStr("sourceFooter.sourceMapButton.enable"), + onClick: this.toggleSourceMaps, + }), + hr(), + React.createElement(MenuItem, { + className: "menu-item debugger-source-map-open-original", + checked: this.props.shouldSelectOriginalLocation, + label: L10N.getStr( + "sourceFooter.sourceMapButton.showOriginalSourceByDefault" + ), + onClick: this.toggleSelectOriginalByDefault, + }), + ]; + + if (this.props.mappedSource) { + items.push( + React.createElement(MenuItem, { + className: "menu-item debugger-jump-mapped-source", + label: this.props.mappedSource.isOriginal + ? L10N.getStr("sourceFooter.sourceMapButton.jumpToGeneratedSource") + : L10N.getStr("sourceFooter.sourceMapButton.jumpToOriginalSource"), + tooltip: this.props.mappedSource.url, + onClick: () => + this.props.jumpToMappedLocation(this.props.selectedLocation), + }) + ); + } + + if (this.props.resolvedSourceMapURL) { + items.push( + React.createElement(MenuItem, { + className: "menu-item debugger-source-map-link", + label: L10N.getStr( + "sourceFooter.sourceMapButton.openSourceMapInNewTab" + ), + onClick: this.openSourceMap, + }) + ); + } + return React.createElement( + MenuList, + { + id: "debugger-source-map-list", + }, + items + ); + } + + openSourceMap = () => { + let line, column; + if ( + this.props.sourceMapError && + this.props.sourceMapError.includes("JSON.parse") + ) { + const match = this.props.sourceMapError.match( + /at line (\d+) column (\d+)/ + ); + if (match) { + line = match[1]; + column = match[2]; + } + } + this.props.openSourceMap( + this.props.resolvedSourceMapURL || this.props.selectedLocation.source.url, + line, + column + ); + }; + + toggleSourceMaps = () => { + this.props.toggleSourceMapsEnabled(!this.props.areSourceMapsEnabled); + }; + + toggleSelectOriginalByDefault = () => { + this.props.setDefaultSelectedLocation( + !this.props.shouldSelectOriginalLocation + ); + this.props.jumpToMappedSelectedLocation(); + }; + render() { return div( { @@ -242,19 +395,40 @@ class SourceFooter extends PureComponent { { className: "source-footer-end", }, - this.renderSourceSummary(), + this.renderMappedSource(), this.renderCursorPosition(), this.renderToggleButton() ) ); } } +SourceFooter.contextTypes = { + toolboxDoc: PropTypes.object, +}; const mapStateToProps = state => { const selectedSource = getSelectedSource(state); const selectedLocation = getSelectedLocation(state); const sourceTextContent = getSelectedSourceTextContent(state); + const areSourceMapsEnabledProp = areSourceMapsEnabled(state); + const isSourceActorWithSourceMapProp = isSourceActorWithSourceMap( + state, + selectedLocation?.sourceActor.id + ); + const sourceMapError = selectedLocation?.sourceActor + ? getSourceMapErrorForSourceActor(state, selectedLocation.sourceActor.id) + : null; + const mappedSource = getSelectedMappedSource(state); + + const isSourceMapLoading = + areSourceMapsEnabledProp && + isSourceActorWithSourceMapProp && + // `mappedSource` will be null while loading, we need another way to know when it is done computing + !mappedSource && + isSelectedMappedSourceLoading(state) && + !sourceMapError; + return { selectedSource, selectedLocation, @@ -265,7 +439,8 @@ const mapStateToProps = state => { isSourceMapIgnoreListEnabled(state) && isSourceOnSourceMapIgnoreList(state, selectedSource), sourceLoaded: !!sourceTextContent, - mappedSource: getSelectedMappedSource(state), + mappedSource, + isSourceMapLoading, prettySource: getPrettySource( state, selectedSource ? selectedSource.id : null @@ -277,6 +452,17 @@ const mapStateToProps = state => { prettyPrintMessage: selectedLocation ? getPrettyPrintMessage(state, selectedLocation) : null, + + sourceMapError, + resolvedSourceMapURL: selectedLocation?.sourceActor + ? getSourceMapResolvedURL(state, selectedLocation.sourceActor.id) + : null, + isSourceActorWithSourceMap: isSourceActorWithSourceMapProp, + + sourceMapURL: selectedLocation?.sourceActor.sourceMapURL, + + areSourceMapsEnabled: areSourceMapsEnabledProp, + shouldSelectOriginalLocation: getShouldSelectOriginalLocation(state), }; }; @@ -285,4 +471,8 @@ export default connect(mapStateToProps, { toggleBlackBox: actions.toggleBlackBox, jumpToMappedLocation: actions.jumpToMappedLocation, togglePaneCollapse: actions.togglePaneCollapse, + toggleSourceMapsEnabled: actions.toggleSourceMapsEnabled, + setDefaultSelectedLocation: actions.setDefaultSelectedLocation, + jumpToMappedSelectedLocation: actions.jumpToMappedSelectedLocation, + openSourceMap: actions.openSourceMap, })(SourceFooter); diff --git a/devtools/client/debugger/src/components/Editor/InlinePreview.js b/devtools/client/debugger/src/components/Editor/InlinePreview.js index 552143dcf2..60303e38b5 100644 --- a/devtools/client/debugger/src/components/Editor/InlinePreview.js +++ b/devtools/client/debugger/src/components/Editor/InlinePreview.js @@ -27,7 +27,7 @@ class InlinePreview extends PureComponent { }; } - showInScopes(variable) { + showInScopes() { // TODO: focus on variable value in the scopes sidepanel // we will need more info from parent comp } diff --git a/devtools/client/debugger/src/components/Editor/InlinePreviewRow.js b/devtools/client/debugger/src/components/Editor/InlinePreviewRow.js index bc54fc5b4d..18fe98ff17 100644 --- a/devtools/client/debugger/src/components/Editor/InlinePreviewRow.js +++ b/devtools/client/debugger/src/components/Editor/InlinePreviewRow.js @@ -67,13 +67,13 @@ class InlinePreviewRow extends PureComponent { null, previews.map(preview => React.createElement(InlinePreview, { - line: line, + line, key: `${line}-${preview.name}`, variable: preview.name, value: preview.value, - openElementInInspector: openElementInInspector, - highlightDomElement: highlightDomElement, - unHighlightDomElement: unHighlightDomElement, + openElementInInspector, + highlightDomElement, + unHighlightDomElement, }) ) ), diff --git a/devtools/client/debugger/src/components/Editor/InlinePreviews.js b/devtools/client/debugger/src/components/Editor/InlinePreviews.js index 18616ae3ed..ba8b08669a 100644 --- a/devtools/client/debugger/src/components/Editor/InlinePreviews.js +++ b/devtools/client/debugger/src/components/Editor/InlinePreviews.js @@ -49,7 +49,7 @@ class InlinePreviews extends Component { inlinePreviewRows = Object.keys(previewsObj).map(line => { const lineNum = parseInt(line, 10); return React.createElement(InlinePreviewRow, { - editor: editor, + editor, key: line, line: lineNum, previews: previewsObj[line], diff --git a/devtools/client/debugger/src/components/Editor/Preview/Popup.js b/devtools/client/debugger/src/components/Editor/Preview/Popup.js index a010358dc1..431cb52729 100644 --- a/devtools/client/debugger/src/components/Editor/Preview/Popup.js +++ b/devtools/client/debugger/src/components/Editor/Preview/Popup.js @@ -100,7 +100,7 @@ export class Popup extends Component { renderExceptionPreview(exception) { return React.createElement(ExceptionPopup, { - exception: exception, + exception, clearPreview: this.props.clearPreview, }); } @@ -182,8 +182,8 @@ export class Popup extends Component { Popover, { targetPosition: cursorPos, - type: type, - editorRef: editorRef, + type, + editorRef, target: this.props.preview.target, mouseout: this.props.clearPreview, }, diff --git a/devtools/client/debugger/src/components/Editor/Preview/index.js b/devtools/client/debugger/src/components/Editor/Preview/index.js index 218d33007f..10c600670e 100644 --- a/devtools/client/debugger/src/components/Editor/Preview/index.js +++ b/devtools/client/debugger/src/components/Editor/Preview/index.js @@ -44,7 +44,7 @@ class Preview extends PureComponent { codeMirrorWrapper.removeEventListener("mousedown", this.onMouseDown); } - updateListeners(prevProps) { + updateListeners() { const { codeMirror } = this.props.editor; const codeMirrorWrapper = codeMirror.getWrapperElement(); codeMirror.on("tokenenter", this.onTokenEnter); @@ -107,7 +107,7 @@ class Preview extends PureComponent { return null; } return React.createElement(Popup, { - preview: preview, + preview, editor: this.props.editor, editorRef: this.props.editorRef, clearPreview: this.clearPreview, diff --git a/devtools/client/debugger/src/components/Editor/SearchInFileBar.js b/devtools/client/debugger/src/components/Editor/SearchInFileBar.js index a3491a3fef..26f95ce75d 100644 --- a/devtools/client/debugger/src/components/Editor/SearchInFileBar.js +++ b/devtools/client/debugger/src/components/Editor/SearchInFileBar.js @@ -97,7 +97,7 @@ class SearchInFileBar extends Component { shortcuts.on("Escape", this.onEscape); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate() { if (this.refs.resultList && this.refs.resultList.refs) { scrollList(this.refs.resultList.refs, this.state.selectedResultIndex); } @@ -111,7 +111,7 @@ class SearchInFileBar extends Component { const { editor: ed } = this.props; if (ed) { const ctx = { ed, cm: ed.codeMirror }; - removeOverlay(ctx, this.state.query); + removeOverlay(ctx); } }; @@ -165,7 +165,7 @@ class SearchInFileBar extends Component { const ctx = { ed: editor, cm: editor.codeMirror }; if (!query) { - clearSearch(ctx.cm, query); + clearSearch(ctx.cm); return; } @@ -249,11 +249,11 @@ class SearchInFileBar extends Component { return this.doSearch(e.target.value); }; - onFocus = e => { + onFocus = () => { this.setState({ inputFocused: true }); }; - onBlur = e => { + onBlur = () => { this.setState({ inputFocused: false }); }; @@ -321,7 +321,7 @@ class SearchInFileBar extends Component { }, React.createElement(SearchInput, { query: this.state.query, - count: count, + count, placeholder: L10N.getStr("sourceSearch.search.placeholder2"), summaryMsg: this.buildSummaryMsg(), isLoading: false, @@ -349,7 +349,7 @@ SearchInFileBar.contextTypes = { shortcuts: PropTypes.object, }; -const mapStateToProps = (state, p) => { +const mapStateToProps = state => { const selectedSource = getSelectedSource(state); return { diff --git a/devtools/client/debugger/src/components/Editor/Tab.js b/devtools/client/debugger/src/components/Editor/Tab.js index ba5e1c1934..98aca90cd2 100644 --- a/devtools/client/debugger/src/components/Editor/Tab.js +++ b/devtools/client/debugger/src/components/Editor/Tab.js @@ -15,7 +15,6 @@ import actions from "../../actions/index"; import { getDisplayPath, getFileURL, - getSourceQueryString, getTruncatedFileName, isPretty, } from "../../utils/source"; @@ -87,14 +86,13 @@ class Tab extends PureComponent { }); const path = getDisplayPath(source, tabSources); - const query = getSourceQueryString(source); return div( { draggable: true, - onDragOver: onDragOver, - onDragStart: onDragStart, - onDragEnd: onDragEnd, - className: className, + onDragOver, + onDragStart, + onDragEnd, + className, "data-index": index, "data-source-id": sourceId, onClick: handleTabClick, @@ -115,7 +113,7 @@ class Tab extends PureComponent { { className: "filename", }, - getTruncatedFileName(source, query), + getTruncatedFileName(source), path && span(null, `../${path}/..`) ), React.createElement(CloseButton, { diff --git a/devtools/client/debugger/src/components/Editor/Tabs.js b/devtools/client/debugger/src/components/Editor/Tabs.js index 3577a4909c..d93f7d3b18 100644 --- a/devtools/client/debugger/src/components/Editor/Tabs.js +++ b/devtools/client/debugger/src/components/Editor/Tabs.js @@ -23,7 +23,7 @@ import { import { isVisible } from "../../utils/ui"; import { getHiddenTabs } from "../../utils/tabs"; -import { getFilename, isPretty, getFileURL } from "../../utils/source"; +import { isPretty, getFileURL } from "../../utils/source"; import actions from "../../actions/index"; import Tab from "./Tab"; @@ -144,13 +144,12 @@ class Tabs extends PureComponent { renderDropdownSource = source => { const { selectSource } = this.props; - const filename = getFilename(source); const onClick = () => selectSource(source); return li( { key: source.id, - onClick: onClick, + onClick, title: getFileURL(source, false), }, React.createElement(AccessibleImage, { @@ -160,7 +159,7 @@ class Tabs extends PureComponent { { className: "dropdown-label", }, - filename + source.shortName ) ); }; diff --git a/devtools/client/debugger/src/components/Editor/index.js b/devtools/client/debugger/src/components/Editor/index.js index c659de77d2..ae9bde7657 100644 --- a/devtools/client/debugger/src/components/Editor/index.js +++ b/devtools/client/debugger/src/components/Editor/index.js @@ -12,6 +12,7 @@ import { connect } from "devtools/client/shared/vendor/react-redux"; import { getLineText, isLineBlackboxed } from "./../../utils/source"; import { createLocation } from "./../../utils/location"; import { getIndentation } from "../../utils/indentation"; +import { features } from "../../utils/prefs"; import { getActiveSearch, @@ -50,10 +51,9 @@ import BlackboxLines from "./BlackboxLines"; import { showSourceText, - showLoading, - showErrorMessage, + setDocument, + resetLineNumberFormat, getEditor, - clearEditor, getCursorLine, getCursorColumn, lineAtHeight, @@ -143,37 +143,45 @@ class Editor extends PureComponent { this.props.selectedSourceTextContent?.value || nextProps.symbols !== this.props.symbols; - const shouldUpdateSize = - nextProps.startPanelSize !== this.props.startPanelSize || - nextProps.endPanelSize !== this.props.endPanelSize; - - const shouldScroll = - nextProps.selectedLocation && - this.shouldScrollToLocation(nextProps, editor); + if (!features.codemirrorNext) { + const shouldUpdateSize = + nextProps.startPanelSize !== this.props.startPanelSize || + nextProps.endPanelSize !== this.props.endPanelSize; + + const shouldScroll = + nextProps.selectedLocation && + this.shouldScrollToLocation(nextProps, editor); + + if (shouldUpdateText || shouldUpdateSize || shouldScroll) { + startOperation(); + if (shouldUpdateText) { + this.setText(nextProps, editor); + } + if (shouldUpdateSize) { + editor.codeMirror.setSize(); + } + if (shouldScroll) { + this.scrollToLocation(nextProps, editor); + } + endOperation(); + } - if (shouldUpdateText || shouldUpdateSize || shouldScroll) { - startOperation(); + if (this.props.selectedSource != nextProps.selectedSource) { + this.props.updateViewport(); + resizeBreakpointGutter(editor.codeMirror); + resizeToggleButton(editor.codeMirror); + } + } else { + // For codemirror 6 + // eslint-disable-next-line no-lonely-if if (shouldUpdateText) { this.setText(nextProps, editor); } - if (shouldUpdateSize) { - editor.codeMirror.setSize(); - } - if (shouldScroll) { - this.scrollToLocation(nextProps, editor); - } - endOperation(); - } - - if (this.props.selectedSource != nextProps.selectedSource) { - this.props.updateViewport(); - resizeBreakpointGutter(editor.codeMirror); - resizeToggleButton(editor.codeMirror); } } setupEditor() { - const editor = getEditor(); + const editor = getEditor(features.codemirrorNext); // disables the default search shortcuts editor._initShortcuts = () => {}; @@ -183,71 +191,84 @@ class Editor extends PureComponent { editor.appendToLocalElement(node.querySelector(".editor-mount")); } - const { codeMirror } = editor; - - this.abortController = new window.AbortController(); - - // CodeMirror refreshes its internal state on window resize, but we need to also - // refresh it when the side panels are resized. - // We could have a ResizeObserver instead, but we wouldn't be able to differentiate - // between window resize and side panel resize and as a result, might refresh - // codeMirror twice, which is wasteful. - window.document - .querySelector(".editor-pane") - .addEventListener("resizeend", () => codeMirror.refresh(), { - signal: this.abortController.signal, - }); - - codeMirror.on("gutterClick", this.onGutterClick); - codeMirror.on("cursorActivity", this.onCursorChange); - - const codeMirrorWrapper = codeMirror.getWrapperElement(); - // Set code editor wrapper to be focusable - codeMirrorWrapper.tabIndex = 0; - codeMirrorWrapper.addEventListener("keydown", e => this.onKeyDown(e)); - codeMirrorWrapper.addEventListener("click", e => this.onClick(e)); - codeMirrorWrapper.addEventListener("mouseover", onMouseOver(codeMirror)); - - const toggleFoldMarkerVisibility = e => { - if (node instanceof HTMLElement) { - node - .querySelectorAll(".CodeMirror-guttermarker-subtle") - .forEach(elem => { - elem.classList.toggle("visible"); - }); - } - }; + if (!features.codemirrorNext) { + const { codeMirror } = editor; + + this.abortController = new window.AbortController(); + + // CodeMirror refreshes its internal state on window resize, but we need to also + // refresh it when the side panels are resized. + // We could have a ResizeObserver instead, but we wouldn't be able to differentiate + // between window resize and side panel resize and as a result, might refresh + // codeMirror twice, which is wasteful. + window.document + .querySelector(".editor-pane") + .addEventListener("resizeend", () => codeMirror.refresh(), { + signal: this.abortController.signal, + }); + + codeMirror.on("gutterClick", this.onGutterClick); + codeMirror.on("cursorActivity", this.onCursorChange); + + const codeMirrorWrapper = codeMirror.getWrapperElement(); + // Set code editor wrapper to be focusable + codeMirrorWrapper.tabIndex = 0; + codeMirrorWrapper.addEventListener("keydown", e => this.onKeyDown(e)); + codeMirrorWrapper.addEventListener("click", e => this.onClick(e)); + codeMirrorWrapper.addEventListener("mouseover", onMouseOver(codeMirror)); + + const toggleFoldMarkerVisibility = () => { + if (node instanceof HTMLElement) { + node + .querySelectorAll(".CodeMirror-guttermarker-subtle") + .forEach(elem => { + elem.classList.toggle("visible"); + }); + } + }; - const codeMirrorGutter = codeMirror.getGutterElement(); - codeMirrorGutter.addEventListener("mouseleave", toggleFoldMarkerVisibility); - codeMirrorGutter.addEventListener("mouseenter", toggleFoldMarkerVisibility); - codeMirrorWrapper.addEventListener("contextmenu", event => - this.openMenu(event) - ); + const codeMirrorGutter = codeMirror.getGutterElement(); + codeMirrorGutter.addEventListener( + "mouseleave", + toggleFoldMarkerVisibility + ); + codeMirrorGutter.addEventListener( + "mouseenter", + toggleFoldMarkerVisibility + ); + codeMirrorWrapper.addEventListener("contextmenu", event => + this.openMenu(event) + ); - codeMirror.on("scroll", this.onEditorScroll); - this.onEditorScroll(); + codeMirror.on("scroll", this.onEditorScroll); + this.onEditorScroll(); + } this.setState({ editor }); return editor; } componentDidMount() { - const { shortcuts } = this.context; + if (!features.codemirrorNext) { + const { shortcuts } = this.context; - shortcuts.on(L10N.getStr("toggleBreakpoint.key"), this.onToggleBreakpoint); - shortcuts.on( - L10N.getStr("toggleCondPanel.breakpoint.key"), - this.onToggleConditionalPanel - ); - shortcuts.on( - L10N.getStr("toggleCondPanel.logPoint.key"), - this.onToggleConditionalPanel - ); - shortcuts.on( - L10N.getStr("sourceTabs.closeTab.key"), - this.onCloseShortcutPress - ); - shortcuts.on("Esc", this.onEscape); + shortcuts.on( + L10N.getStr("toggleBreakpoint.key"), + this.onToggleBreakpoint + ); + shortcuts.on( + L10N.getStr("toggleCondPanel.breakpoint.key"), + this.onToggleConditionalPanel + ); + shortcuts.on( + L10N.getStr("toggleCondPanel.logPoint.key"), + this.onToggleConditionalPanel + ); + shortcuts.on( + L10N.getStr("sourceTabs.closeTab.key"), + this.onCloseShortcutPress + ); + shortcuts.on("Esc", this.onEscape); + } } onCloseShortcutPress = e => { @@ -260,22 +281,24 @@ class Editor extends PureComponent { }; componentWillUnmount() { - const { editor } = this.state; - if (editor) { - editor.destroy(); - editor.codeMirror.off("scroll", this.onEditorScroll); - this.setState({ editor: null }); - } + if (!features.codemirrorNext) { + const { editor } = this.state; + if (editor) { + editor.destroy(); + editor.codeMirror.off("scroll", this.onEditorScroll); + this.setState({ editor: null }); + } - const { shortcuts } = this.context; - shortcuts.off(L10N.getStr("sourceTabs.closeTab.key")); - shortcuts.off(L10N.getStr("toggleBreakpoint.key")); - shortcuts.off(L10N.getStr("toggleCondPanel.breakpoint.key")); - shortcuts.off(L10N.getStr("toggleCondPanel.logPoint.key")); + const { shortcuts } = this.context; + shortcuts.off(L10N.getStr("sourceTabs.closeTab.key")); + shortcuts.off(L10N.getStr("toggleBreakpoint.key")); + shortcuts.off(L10N.getStr("toggleCondPanel.breakpoint.key")); + shortcuts.off(L10N.getStr("toggleCondPanel.logPoint.key")); - if (this.abortController) { - this.abortController.abort(); - this.abortController = null; + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } } } @@ -542,7 +565,7 @@ class Editor extends PureComponent { } } - shouldScrollToLocation(nextProps, editor) { + shouldScrollToLocation(nextProps) { if ( !nextProps.selectedLocation?.line || !nextProps.selectedSourceTextContent @@ -583,12 +606,14 @@ class Editor extends PureComponent { // check if we previously had a selected source if (!selectedSource) { - this.clearEditor(); + if (!features.codemirrorNext) { + this.clearEditor(); + } return; } if (!selectedSourceTextContent?.value) { - showLoading(editor); + this.showLoadingMessage(editor); return; } @@ -602,7 +627,16 @@ class Editor extends PureComponent { return; } - showSourceText(editor, selectedSource, selectedSourceTextContent, symbols); + if (!features.codemirrorNext) { + showSourceText( + editor, + selectedSource, + selectedSourceTextContent, + symbols + ); + } else { + editor.setText(selectedSourceTextContent.value.value); + } } clearEditor() { @@ -611,7 +645,9 @@ class Editor extends PureComponent { return; } - clearEditor(editor); + const doc = editor.createDocument("", { name: "text" }); + editor.replaceDocument(doc); + resetLineNumberFormat(editor); } showErrorMessage(msg) { @@ -620,7 +656,37 @@ class Editor extends PureComponent { return; } - showErrorMessage(editor, msg); + let error; + if (msg.includes("WebAssembly binary source is not available")) { + error = L10N.getStr("wasmIsNotAvailable"); + } else { + error = L10N.getFormatStr("errorLoadingText3", msg); + } + if (!features.codemirrorNext) { + const doc = editor.createDocument(error, { name: "text" }); + editor.replaceDocument(doc); + resetLineNumberFormat(editor); + } else { + editor.setText(error); + } + } + + showLoadingMessage(editor) { + if (!features.codemirrorNext) { + // Create the "loading message" document only once + let doc = getDocument("loading"); + if (!doc) { + doc = editor.createDocument(L10N.getStr("loadingText"), { + name: "text", + }); + setDocument("loading", doc); + } + // `createDocument` won't be used right away in the editor, we still need to + // explicitely update it + editor.replaceDocument(doc); + } else { + editor.setText(L10N.getStr("loadingText")); + } } getInlineEditorStyles() { diff --git a/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js b/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js index 767dde9e6d..38bb318611 100644 --- a/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js +++ b/devtools/client/debugger/src/components/Editor/tests/DebugLine.spec.js @@ -15,7 +15,7 @@ function createMockDocument(clear) { addLineClass: jest.fn(), removeLineClass: jest.fn(), markText: jest.fn(() => ({ clear })), - getLine: line => "", + getLine: () => "", }; return doc; diff --git a/devtools/client/debugger/src/components/Editor/tests/__snapshots__/ConditionalPanel.spec.js.snap b/devtools/client/debugger/src/components/Editor/tests/__snapshots__/ConditionalPanel.spec.js.snap index 58e86f5009..86d848ec3e 100644 --- a/devtools/client/debugger/src/components/Editor/tests/__snapshots__/ConditionalPanel.spec.js.snap +++ b/devtools/client/debugger/src/components/Editor/tests/__snapshots__/ConditionalPanel.spec.js.snap @@ -22,6 +22,8 @@ exports[`ConditionalPanel should render at location of selected breakpoint 1`] = "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -44,6 +46,8 @@ exports[`ConditionalPanel should render at location of selected breakpoint 1`] = "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -217,6 +221,8 @@ exports[`ConditionalPanel should render at location of selected breakpoint 1`] = "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -239,6 +245,8 @@ exports[`ConditionalPanel should render at location of selected breakpoint 1`] = "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", } @@ -268,6 +276,8 @@ exports[`ConditionalPanel should render with condition at selected breakpoint lo "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -290,6 +300,8 @@ exports[`ConditionalPanel should render with condition at selected breakpoint lo "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -467,6 +479,8 @@ exports[`ConditionalPanel should render with condition at selected breakpoint lo "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -489,6 +503,8 @@ exports[`ConditionalPanel should render with condition at selected breakpoint lo "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", } @@ -518,6 +534,8 @@ exports[`ConditionalPanel should render with logpoint at selected breakpoint loc "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -540,6 +558,8 @@ exports[`ConditionalPanel should render with logpoint at selected breakpoint loc "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -717,6 +737,8 @@ exports[`ConditionalPanel should render with logpoint at selected breakpoint loc "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", }, @@ -739,6 +761,8 @@ exports[`ConditionalPanel should render with logpoint at selected breakpoint loc "isOriginal": false, "isPrettyPrinted": false, "isWasm": false, + "longName": "url", + "shortName": "url", "thread": "FakeThread", "url": "url", } diff --git a/devtools/client/debugger/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap b/devtools/client/debugger/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap index a453b034ff..6c56ad33e7 100644 --- a/devtools/client/debugger/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap +++ b/devtools/client/debugger/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap @@ -31,6 +31,16 @@ exports[`SourceFooter Component default case should render 1`] = ` className="prettyPrint" /> </button> + <MenuButton + className="devtools-button debugger-source-map-button disabled not-mapped" + icon={true} + menuId="debugger-source-map-button" + menuOffset={-5} + menuPosition="bottom" + title="Source Map status" + > + <Component /> + </MenuButton> </div> </div> <div @@ -77,6 +87,16 @@ exports[`SourceFooter Component move cursor should render new cursor position 1` className="prettyPrint" /> </button> + <MenuButton + className="devtools-button debugger-source-map-button disabled not-mapped" + icon={true} + menuId="debugger-source-map-button" + menuOffset={-5} + menuPosition="bottom" + title="Source Map status" + > + <Component /> + </MenuButton> </div> </div> <div |