Source code
Revision control
Copy as Markdown
Other Tools
/* 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
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,
getSelectedSource,
getGeneratedFrameScope,
getOriginalFrameScope,
getPauseReason,
isMapScopesEnabled,
getLastExpandedScopes,
getIsCurrentThreadPaused,
} from "../../selectors/index";
import {
getScopesItemsForSelectedFrame,
getScopeItemPath,
} from "../../utils/pause/scopes";
import { clientCommands } from "../../client/firefox";
import * as objectInspector from "resource://devtools/client/shared/components/object-inspector/index.js";
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,
};
}
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,
onDOMNodeClick: grip => openElementInInspector(grip),
onInspectIconClick: grip => openElementInInspector(grip),
onDOMNodeMouseOver: grip => highlightDomElement(grip),
onDOMNodeMouseOut: grip => unHighlightDomElement(grip),
onContextMenu: this.onContextMenu,
preventBlur: true,
setExpanded: (path, expand) =>
setExpandedScope(selectedFrame, path, expand),
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);
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 = false;
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);