diff options
Diffstat (limited to 'devtools/client/debugger/src/components/SecondaryPanes/Scopes.js')
-rw-r--r-- | devtools/client/debugger/src/components/SecondaryPanes/Scopes.js | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js new file mode 100644 index 0000000000..135decd254 --- /dev/null +++ b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js @@ -0,0 +1,413 @@ +/* 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/>. */ + +import React, { PureComponent } from "devtools/client/shared/vendor/react"; +import { + div, + button, + span, +} from "devtools/client/shared/vendor/react-dom-factories"; +import PropTypes from "devtools/client/shared/vendor/react-prop-types"; +import AccessibleImage from "../shared/AccessibleImage"; +import { showMenu } from "../../context-menu/menu"; +import { connect } from "devtools/client/shared/vendor/react-redux"; +import actions from "../../actions/index"; + +import { + getSelectedFrame, + getCurrentThread, + getSelectedSource, + getGeneratedFrameScope, + getOriginalFrameScope, + getPauseReason, + isMapScopesEnabled, + getLastExpandedScopes, + getIsCurrentThreadPaused, +} from "../../selectors/index"; +import { + getScopesItemsForSelectedFrame, + getScopeItemPath, +} from "../../utils/pause/scopes"; +import { clientCommands } from "../../client/firefox"; + +import { objectInspector } from "devtools/client/shared/components/reps/index"; +const { ObjectInspector } = objectInspector; + +class Scopes extends PureComponent { + constructor(props) { + const { why, selectedFrame, originalFrameScopes, generatedFrameScopes } = + props; + + super(props); + + this.state = { + originalScopes: getScopesItemsForSelectedFrame( + why, + selectedFrame, + originalFrameScopes + ), + generatedScopes: getScopesItemsForSelectedFrame( + why, + selectedFrame, + generatedFrameScopes + ), + }; + } + + static get propTypes() { + return { + addWatchpoint: PropTypes.func.isRequired, + expandedScopes: PropTypes.array.isRequired, + generatedFrameScopes: PropTypes.object, + highlightDomElement: PropTypes.func.isRequired, + isLoading: PropTypes.bool.isRequired, + isPaused: PropTypes.bool.isRequired, + mapScopesEnabled: PropTypes.bool.isRequired, + openElementInInspector: PropTypes.func.isRequired, + openLink: PropTypes.func.isRequired, + originalFrameScopes: PropTypes.object, + removeWatchpoint: PropTypes.func.isRequired, + setExpandedScope: PropTypes.func.isRequired, + unHighlightDomElement: PropTypes.func.isRequired, + why: PropTypes.object.isRequired, + selectedFrame: PropTypes.object, + toggleMapScopes: PropTypes.func.isRequired, + selectedSource: PropTypes.object.isRequired, + }; + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillReceiveProps(nextProps) { + const { + selectedFrame, + originalFrameScopes, + generatedFrameScopes, + isPaused, + selectedSource, + } = this.props; + const isPausedChanged = isPaused !== nextProps.isPaused; + const selectedFrameChanged = selectedFrame !== nextProps.selectedFrame; + const originalFrameScopesChanged = + originalFrameScopes !== nextProps.originalFrameScopes; + const generatedFrameScopesChanged = + generatedFrameScopes !== nextProps.generatedFrameScopes; + const selectedSourceChanged = selectedSource !== nextProps.selectedSource; + + if ( + isPausedChanged || + selectedFrameChanged || + originalFrameScopesChanged || + generatedFrameScopesChanged || + selectedSourceChanged + ) { + this.setState({ + originalScopes: getScopesItemsForSelectedFrame( + nextProps.why, + nextProps.selectedFrame, + nextProps.originalFrameScopes + ), + generatedScopes: getScopesItemsForSelectedFrame( + nextProps.why, + nextProps.selectedFrame, + nextProps.generatedFrameScopes + ), + }); + } + } + + onContextMenu = (event, item) => { + const { addWatchpoint, removeWatchpoint } = this.props; + + if (!item.parent || !item.contents.configurable) { + return; + } + + if (!item.contents || item.contents.watchpoint) { + const removeWatchpointLabel = L10N.getStr("watchpoints.removeWatchpoint"); + + const removeWatchpointItem = { + id: "node-menu-remove-watchpoint", + label: removeWatchpointLabel, + disabled: false, + click: () => removeWatchpoint(item), + }; + + const menuItems = [removeWatchpointItem]; + showMenu(event, menuItems); + return; + } + + const addSetWatchpointLabel = L10N.getStr("watchpoints.setWatchpoint"); + const addGetWatchpointLabel = L10N.getStr("watchpoints.getWatchpoint"); + const addGetOrSetWatchpointLabel = L10N.getStr( + "watchpoints.getOrSetWatchpoint" + ); + const watchpointsSubmenuLabel = L10N.getStr("watchpoints.submenu"); + + const addSetWatchpointItem = { + id: "node-menu-add-set-watchpoint", + label: addSetWatchpointLabel, + disabled: false, + click: () => addWatchpoint(item, "set"), + }; + + const addGetWatchpointItem = { + id: "node-menu-add-get-watchpoint", + label: addGetWatchpointLabel, + disabled: false, + click: () => addWatchpoint(item, "get"), + }; + + const addGetOrSetWatchpointItem = { + id: "node-menu-add-get-watchpoint", + label: addGetOrSetWatchpointLabel, + disabled: false, + click: () => addWatchpoint(item, "getorset"), + }; + + const watchpointsSubmenuItem = { + id: "node-menu-watchpoints", + label: watchpointsSubmenuLabel, + disabled: false, + click: () => addWatchpoint(item, "set"), + submenu: [ + addSetWatchpointItem, + addGetWatchpointItem, + addGetOrSetWatchpointItem, + ], + }; + + const menuItems = [watchpointsSubmenuItem]; + showMenu(event, menuItems); + }; + + renderWatchpointButton = item => { + const { removeWatchpoint } = this.props; + + if ( + !item || + !item.contents || + !item.contents.watchpoint || + typeof L10N === "undefined" + ) { + return null; + } + + const { watchpoint } = item.contents; + return button({ + className: `remove-watchpoint-${watchpoint}`, + title: L10N.getStr("watchpoints.removeWatchpointTooltip"), + onClick: e => { + e.stopPropagation(); + removeWatchpoint(item); + }, + }); + }; + + renderScopesList() { + const { + isLoading, + openLink, + openElementInInspector, + highlightDomElement, + unHighlightDomElement, + mapScopesEnabled, + selectedFrame, + setExpandedScope, + expandedScopes, + selectedSource, + } = this.props; + + if (!selectedSource) { + return div( + { className: "pane scopes-list" }, + div({ className: "pane-info" }, L10N.getStr("scopes.notAvailable")) + ); + } + + const { originalScopes, generatedScopes } = this.state; + let scopes = null; + + if ( + selectedSource.isOriginal && + !selectedSource.isPrettyPrinted && + !selectedFrame.generatedLocation?.source.isWasm + ) { + if (!mapScopesEnabled) { + return div( + { className: "pane scopes-list" }, + div( + { + className: "pane-info no-original-scopes-info", + "aria-role": "status", + }, + span( + { className: "info icon" }, + React.createElement(AccessibleImage, { className: "sourcemap" }) + ), + L10N.getFormatStr( + "scopes.noOriginalScopes", + L10N.getStr("scopes.showOriginalScopes") + ) + ) + ); + } + if (isLoading) { + return div( + { + className: "pane scopes-list", + }, + div( + { className: "pane-info" }, + span( + { className: "info icon" }, + React.createElement(AccessibleImage, { className: "loader" }) + ), + L10N.getStr("scopes.loadingOriginalScopes") + ) + ); + } + scopes = originalScopes; + } else { + if (isLoading) { + return div( + { + className: "pane scopes-list", + }, + div( + { className: "pane-info" }, + span( + { className: "info icon" }, + React.createElement(AccessibleImage, { className: "loader" }) + ), + L10N.getStr("loadingText") + ) + ); + } + scopes = generatedScopes; + } + + function initiallyExpanded(item) { + return expandedScopes.some(path => path == getScopeItemPath(item)); + } + + if (scopes && !!scopes.length) { + return div( + { + className: "pane scopes-list", + }, + React.createElement(ObjectInspector, { + roots: scopes, + autoExpandAll: false, + autoExpandDepth: 1, + client: clientCommands, + createElement: tagName => document.createElement(tagName), + disableWrap: true, + dimTopLevelWindow: true, + frame: selectedFrame, + mayUseCustomFormatter: true, + openLink: openLink, + onDOMNodeClick: grip => openElementInInspector(grip), + onInspectIconClick: grip => openElementInInspector(grip), + onDOMNodeMouseOver: grip => highlightDomElement(grip), + onDOMNodeMouseOut: grip => unHighlightDomElement(grip), + onContextMenu: this.onContextMenu, + setExpanded: (path, expand) => + setExpandedScope(selectedFrame, path, expand), + initiallyExpanded: initiallyExpanded, + renderItemActions: this.renderWatchpointButton, + shouldRenderTooltip: true, + }) + ); + } + + return div( + { + className: "pane scopes-list", + }, + div( + { + className: "pane-info", + }, + L10N.getStr("scopes.notAvailable") + ) + ); + } + + render() { + return div( + { + className: "scopes-content", + }, + this.renderScopesList() + ); + } +} + +const mapStateToProps = state => { + // This component doesn't need any prop when we are not paused + const selectedFrame = getSelectedFrame(state, getCurrentThread(state)); + if (!selectedFrame) { + return {}; + } + const why = getPauseReason(state, selectedFrame.thread); + const expandedScopes = getLastExpandedScopes(state, selectedFrame.thread); + const isPaused = getIsCurrentThreadPaused(state); + const selectedSource = getSelectedSource(state); + + let originalFrameScopes; + let generatedFrameScopes; + let isLoading; + let mapScopesEnabled; + + if ( + selectedSource?.isOriginal && + !selectedSource?.isPrettyPrinted && + !selectedFrame.generatedLocation?.source.isWasm + ) { + const { scope, pending: originalPending } = getOriginalFrameScope( + state, + selectedFrame + ) || { + scope: null, + pending: false, + }; + originalFrameScopes = scope; + isLoading = originalPending; + mapScopesEnabled = isMapScopesEnabled(state); + } else { + const { scope, pending: generatedPending } = getGeneratedFrameScope( + state, + selectedFrame + ) || { + scope: null, + pending: false, + }; + generatedFrameScopes = scope; + isLoading = generatedPending; + } + + return { + originalFrameScopes, + generatedFrameScopes, + mapScopesEnabled, + selectedFrame, + isLoading, + why, + expandedScopes, + isPaused, + selectedSource, + }; +}; + +export default connect(mapStateToProps, { + openLink: actions.openLink, + openElementInInspector: actions.openElementInInspectorCommand, + highlightDomElement: actions.highlightDomElement, + unHighlightDomElement: actions.unHighlightDomElement, + setExpandedScope: actions.setExpandedScope, + addWatchpoint: actions.addWatchpoint, + removeWatchpoint: actions.removeWatchpoint, + toggleMapScopes: actions.toggleMapScopes, +})(Scopes); |