summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js')
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js502
1 files changed, 502 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js b/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js
new file mode 100644
index 0000000000..deae156a40
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js
@@ -0,0 +1,502 @@
+/* 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, { Component } from "devtools/client/shared/vendor/react";
+import { div, button } 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";
+import { features, prefs } from "../../utils/prefs";
+import {
+ getIsWaitingOnBreak,
+ getSkipPausing,
+ getCurrentThread,
+ isTopFrameSelected,
+ getIsCurrentThreadPaused,
+ getIsJavascriptTracingEnabled,
+ getIsThreadCurrentlyTracing,
+ getJavascriptTracingLogMethod,
+ getJavascriptTracingValues,
+ getJavascriptTracingOnNextInteraction,
+ getJavascriptTracingOnNextLoad,
+ getJavascriptTracingFunctionReturn,
+} from "../../selectors/index";
+import { formatKeyShortcut } from "../../utils/text";
+import actions from "../../actions/index";
+import { debugBtn } from "../shared/Button/CommandBarButton";
+import AccessibleImage from "../shared/AccessibleImage";
+import { showMenu } from "../../context-menu/menu";
+
+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");
+
+const isMacOS = Services.appinfo.OS === "Darwin";
+
+// NOTE: the "resume" command will call either the resume or breakOnNext action
+// depending on whether or not the debugger is paused or running
+const COMMANDS = ["resume", "stepOver", "stepIn", "stepOut"];
+
+const KEYS = {
+ WINNT: {
+ resume: "F8",
+ stepOver: "F10",
+ stepIn: "F11",
+ stepOut: "Shift+F11",
+ trace: "Ctrl+Shift+5",
+ },
+ Darwin: {
+ resume: "Cmd+\\",
+ stepOver: "Cmd+'",
+ stepIn: "Cmd+;",
+ stepOut: "Cmd+Shift+:",
+ stepOutDisplay: "Cmd+Shift+;",
+ trace: "Ctrl+Shift+5",
+ },
+ Linux: {
+ resume: "F8",
+ stepOver: "F10",
+ stepIn: "F11",
+ stepOut: "Shift+F11",
+ trace: "Ctrl+Shift+5",
+ },
+};
+
+const LOG_METHODS = {
+ CONSOLE: "console",
+ STDOUT: "stdout",
+};
+
+function getKey(action) {
+ return getKeyForOS(Services.appinfo.OS, action);
+}
+
+function getKeyForOS(os, action) {
+ const osActions = KEYS[os] || KEYS.Linux;
+ return osActions[action];
+}
+
+function formatKey(action) {
+ const key = getKey(`${action}Display`) || getKey(action);
+
+ // On MacOS, we bind both Windows and MacOS/Darwin key shortcuts
+ // Display them both, but only when they are different
+ if (isMacOS) {
+ const winKey =
+ getKeyForOS("WINNT", `${action}Display`) || getKeyForOS("WINNT", action);
+ if (key != winKey) {
+ return formatKeyShortcut([key, winKey].join(" "));
+ }
+ }
+ return formatKeyShortcut(key);
+}
+
+class CommandBar extends Component {
+ constructor() {
+ super();
+
+ this.state = {};
+ }
+ static get propTypes() {
+ return {
+ breakOnNext: PropTypes.func.isRequired,
+ horizontal: PropTypes.bool.isRequired,
+ isPaused: PropTypes.bool.isRequired,
+ isTracingEnabled: PropTypes.bool.isRequired,
+ isWaitingOnBreak: PropTypes.bool.isRequired,
+ javascriptEnabled: PropTypes.bool.isRequired,
+ trace: PropTypes.func.isRequired,
+ resume: PropTypes.func.isRequired,
+ skipPausing: PropTypes.bool.isRequired,
+ stepIn: PropTypes.func.isRequired,
+ stepOut: PropTypes.func.isRequired,
+ stepOver: PropTypes.func.isRequired,
+ toggleEditorWrapping: PropTypes.func.isRequired,
+ toggleInlinePreview: PropTypes.func.isRequired,
+ toggleJavaScriptEnabled: PropTypes.func.isRequired,
+ toggleSkipPausing: PropTypes.any.isRequired,
+ toggleSourceMapsEnabled: PropTypes.func.isRequired,
+ topFrameSelected: PropTypes.bool.isRequired,
+ toggleTracing: PropTypes.func.isRequired,
+ logMethod: PropTypes.string.isRequired,
+ logValues: PropTypes.bool.isRequired,
+ traceOnNextInteraction: PropTypes.bool.isRequired,
+ setJavascriptTracingLogMethod: PropTypes.func.isRequired,
+ setHideOrShowIgnoredSources: PropTypes.func.isRequired,
+ toggleSourceMapIgnoreList: PropTypes.func.isRequired,
+ };
+ }
+
+ componentWillUnmount() {
+ const { shortcuts } = this.context;
+
+ COMMANDS.forEach(action => shortcuts.off(getKey(action)));
+
+ if (isMacOS) {
+ COMMANDS.forEach(action => shortcuts.off(getKeyForOS("WINNT", action)));
+ }
+ }
+
+ componentDidMount() {
+ const { shortcuts } = this.context;
+
+ COMMANDS.forEach(action =>
+ shortcuts.on(getKey(action), e => this.handleEvent(e, action))
+ );
+
+ if (isMacOS) {
+ // The Mac supports both the Windows Function keys
+ // as well as the Mac non-Function keys
+ COMMANDS.forEach(action =>
+ shortcuts.on(getKeyForOS("WINNT", action), e =>
+ this.handleEvent(e, action)
+ )
+ );
+ }
+ }
+
+ handleEvent(e, action) {
+ e.preventDefault();
+ e.stopPropagation();
+ if (action === "resume") {
+ this.props.isPaused ? this.props.resume() : this.props.breakOnNext();
+ } else {
+ this.props[action]();
+ }
+ }
+
+ renderStepButtons() {
+ const { isPaused, topFrameSelected } = this.props;
+ const className = isPaused ? "active" : "disabled";
+ const isDisabled = !isPaused;
+
+ return [
+ this.renderPauseButton(),
+ debugBtn(
+ () => this.props.stepOver(),
+ "stepOver",
+ className,
+ L10N.getFormatStr("stepOverTooltip", formatKey("stepOver")),
+ isDisabled
+ ),
+ debugBtn(
+ () => this.props.stepIn(),
+ "stepIn",
+ className,
+ L10N.getFormatStr("stepInTooltip", formatKey("stepIn")),
+ isDisabled || !topFrameSelected
+ ),
+ debugBtn(
+ () => this.props.stepOut(),
+ "stepOut",
+ className,
+ L10N.getFormatStr("stepOutTooltip", formatKey("stepOut")),
+ isDisabled
+ ),
+ ];
+ }
+
+ resume() {
+ this.props.resume();
+ }
+
+ renderTraceButton() {
+ if (!features.javascriptTracing) {
+ return null;
+ }
+
+ // The button is highlighted in blue as soon as the user requested to start the trace
+ const isActive = this.props.isTracingEnabled;
+ // But it will only be active once the tracer actually started.
+ // This may come later when using "on next user interaction" feature.
+ const isPending = isActive && !this.props.isTracingActive;
+
+ let className = "";
+ if (isPending) {
+ className = "pending";
+ } else if (isActive) {
+ className = "active";
+ }
+ // Display a button which:
+ // - on left click, would toggle on/off javascript tracing
+ // - on right click, would display a context menu to configure the tracer settings
+ return button({
+ className: `devtools-button command-bar-button debugger-trace-menu-button ${className}`,
+ title: this.props.isTracingEnabled
+ ? L10N.getFormatStr("stopTraceButtonTooltip2", formatKey("trace"))
+ : L10N.getFormatStr(
+ "startTraceButtonTooltip2",
+ formatKey("trace"),
+ this.props.logMethod
+ ),
+ onClick: event => {
+ this.props.toggleTracing();
+ },
+ onContextMenu: event => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ // Avoid showing the menu to avoid having to support changing tracing config "live"
+ if (this.props.isTracingEnabled) {
+ return;
+ }
+ const items = [
+ {
+ id: "debugger-trace-menu-item-console",
+ label: L10N.getStr("traceInWebConsole"),
+ checked: this.props.logMethod == LOG_METHODS.CONSOLE,
+ type: "radio",
+ click: () => {
+ this.props.setJavascriptTracingLogMethod(LOG_METHODS.CONSOLE);
+ },
+ },
+ {
+ id: "debugger-trace-menu-item-stdout",
+ label: L10N.getStr("traceInStdout"),
+ type: "radio",
+ checked: this.props.logMethod == LOG_METHODS.STDOUT,
+ click: () => {
+ this.props.setJavascriptTracingLogMethod(LOG_METHODS.STDOUT);
+ },
+ },
+ { type: "separator" },
+ {
+ id: "debugger-trace-menu-item-next-interaction",
+ label: L10N.getStr("traceOnNextInteraction"),
+ type: "checkbox",
+ checked: this.props.traceOnNextInteraction,
+ click: () => {
+ this.props.toggleJavascriptTracingOnNextInteraction();
+ },
+ },
+ {
+ id: "debugger-trace-menu-item-next-load",
+ label: L10N.getStr("traceOnNextLoad"),
+ type: "checkbox",
+ checked: this.props.traceOnNextLoad,
+ click: () => {
+ this.props.toggleJavascriptTracingOnNextLoad();
+ },
+ },
+ { type: "separator" },
+ {
+ id: "debugger-trace-menu-item-log-values",
+ label: L10N.getStr("traceValues"),
+ type: "checkbox",
+ checked: this.props.logValues,
+ click: () => {
+ this.props.toggleJavascriptTracingValues();
+ },
+ },
+ {
+ id: "debugger-trace-menu-item-function-return",
+ label: L10N.getStr("traceFunctionReturn"),
+ type: "checkbox",
+ checked: this.props.traceFunctionReturn,
+ click: () => {
+ this.props.toggleJavascriptTracingFunctionReturn();
+ },
+ },
+ ];
+ showMenu(event, items);
+ },
+ });
+ }
+ renderPauseButton() {
+ const { breakOnNext, isWaitingOnBreak } = this.props;
+
+ if (this.props.isPaused) {
+ return debugBtn(
+ () => this.resume(),
+ "resume",
+ "active",
+ L10N.getFormatStr("resumeButtonTooltip", formatKey("resume"))
+ );
+ }
+
+ if (isWaitingOnBreak) {
+ return debugBtn(
+ null,
+ "pause",
+ "disabled",
+ L10N.getStr("pausePendingButtonTooltip"),
+ true
+ );
+ }
+
+ return debugBtn(
+ () => breakOnNext(),
+ "pause",
+ "active",
+ L10N.getFormatStr("pauseButtonTooltip", formatKey("resume"))
+ );
+ }
+
+ renderSkipPausingButton() {
+ const { skipPausing, toggleSkipPausing } = this.props;
+ return button(
+ {
+ className: classnames(
+ "command-bar-button",
+ "command-bar-skip-pausing",
+ {
+ active: skipPausing,
+ }
+ ),
+ title: skipPausing
+ ? L10N.getStr("undoSkipPausingTooltip.label")
+ : L10N.getStr("skipPausingTooltip.label"),
+ onClick: toggleSkipPausing,
+ },
+ React.createElement(AccessibleImage, {
+ className: skipPausing ? "enable-pausing" : "disable-pausing",
+ })
+ );
+ }
+
+ renderSettingsButton() {
+ const { toolboxDoc } = this.context;
+ return React.createElement(
+ MenuButton,
+ {
+ menuId: "debugger-settings-menu-button",
+ toolboxDoc: toolboxDoc,
+ className:
+ "devtools-button command-bar-button debugger-settings-menu-button",
+ title: L10N.getStr("settings.button.label"),
+ },
+ () => this.renderSettingsMenuItems()
+ );
+ }
+
+ renderSettingsMenuItems() {
+ return React.createElement(
+ MenuList,
+ {
+ id: "debugger-settings-menu-list",
+ },
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-disable-javascript",
+ className: "menu-item debugger-settings-menu-item-disable-javascript",
+ checked: !this.props.javascriptEnabled,
+ label: L10N.getStr("settings.disableJavaScript.label"),
+ tooltip: L10N.getStr("settings.disableJavaScript.tooltip"),
+ onClick: () => {
+ this.props.toggleJavaScriptEnabled(!this.props.javascriptEnabled);
+ },
+ }),
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-disable-inline-previews",
+ checked: features.inlinePreview,
+ label: L10N.getStr("inlinePreview.toggle.label"),
+ tooltip: L10N.getStr("inlinePreview.toggle.tooltip"),
+ onClick: () => this.props.toggleInlinePreview(!features.inlinePreview),
+ }),
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-disable-wrap-lines",
+ checked: prefs.editorWrapping,
+ label: L10N.getStr("editorWrapping.toggle.label"),
+ tooltip: L10N.getStr("editorWrapping.toggle.tooltip"),
+ onClick: () => this.props.toggleEditorWrapping(!prefs.editorWrapping),
+ }),
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-disable-sourcemaps",
+ checked: prefs.clientSourceMapsEnabled,
+ label: L10N.getStr("settings.toggleSourceMaps.label"),
+ tooltip: L10N.getStr("settings.toggleSourceMaps.tooltip"),
+ onClick: () =>
+ this.props.toggleSourceMapsEnabled(!prefs.clientSourceMapsEnabled),
+ }),
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-hide-ignored-sources",
+ className: "menu-item debugger-settings-menu-item-hide-ignored-sources",
+ checked: prefs.hideIgnoredSources,
+ label: L10N.getStr("settings.hideIgnoredSources.label"),
+ tooltip: L10N.getStr("settings.hideIgnoredSources.tooltip"),
+ onClick: () =>
+ this.props.setHideOrShowIgnoredSources(!prefs.hideIgnoredSources),
+ }),
+ React.createElement(MenuItem, {
+ key: "debugger-settings-menu-item-enable-sourcemap-ignore-list",
+ className:
+ "menu-item debugger-settings-menu-item-enable-sourcemap-ignore-list",
+ checked: prefs.sourceMapIgnoreListEnabled,
+ label: L10N.getStr("settings.enableSourceMapIgnoreList.label"),
+ tooltip: L10N.getStr("settings.enableSourceMapIgnoreList.tooltip"),
+ onClick: () =>
+ this.props.toggleSourceMapIgnoreList(
+ !prefs.sourceMapIgnoreListEnabled
+ ),
+ })
+ );
+ }
+
+ render() {
+ return div(
+ {
+ className: classnames("command-bar", {
+ vertical: !this.props.horizontal,
+ }),
+ },
+ this.renderStepButtons(),
+ div({
+ className: "filler",
+ }),
+ this.renderTraceButton(),
+ this.renderSkipPausingButton(),
+ div({
+ className: "devtools-separator",
+ }),
+ this.renderSettingsButton()
+ );
+ }
+}
+
+CommandBar.contextTypes = {
+ shortcuts: PropTypes.object,
+ toolboxDoc: PropTypes.object,
+};
+
+const mapStateToProps = state => ({
+ isWaitingOnBreak: getIsWaitingOnBreak(state, getCurrentThread(state)),
+ skipPausing: getSkipPausing(state),
+ topFrameSelected: isTopFrameSelected(state, getCurrentThread(state)),
+ javascriptEnabled: state.ui.javascriptEnabled,
+ isPaused: getIsCurrentThreadPaused(state),
+ isTracingEnabled: getIsJavascriptTracingEnabled(
+ state,
+ getCurrentThread(state)
+ ),
+ isTracingActive: getIsThreadCurrentlyTracing(state, getCurrentThread(state)),
+ logMethod: getJavascriptTracingLogMethod(state),
+ logValues: getJavascriptTracingValues(state),
+ traceOnNextInteraction: getJavascriptTracingOnNextInteraction(state),
+ traceOnNextLoad: getJavascriptTracingOnNextLoad(state),
+ traceFunctionReturn: getJavascriptTracingFunctionReturn(state),
+});
+
+export default connect(mapStateToProps, {
+ toggleTracing: actions.toggleTracing,
+ setJavascriptTracingLogMethod: actions.setJavascriptTracingLogMethod,
+ toggleJavascriptTracingValues: actions.toggleJavascriptTracingValues,
+ toggleJavascriptTracingOnNextInteraction:
+ actions.toggleJavascriptTracingOnNextInteraction,
+ toggleJavascriptTracingOnNextLoad: actions.toggleJavascriptTracingOnNextLoad,
+ toggleJavascriptTracingFunctionReturn:
+ actions.toggleJavascriptTracingFunctionReturn,
+ resume: actions.resume,
+ stepIn: actions.stepIn,
+ stepOut: actions.stepOut,
+ stepOver: actions.stepOver,
+ breakOnNext: actions.breakOnNext,
+ pauseOnExceptions: actions.pauseOnExceptions,
+ toggleSkipPausing: actions.toggleSkipPausing,
+ toggleInlinePreview: actions.toggleInlinePreview,
+ toggleEditorWrapping: actions.toggleEditorWrapping,
+ toggleSourceMapsEnabled: actions.toggleSourceMapsEnabled,
+ toggleJavaScriptEnabled: actions.toggleJavaScriptEnabled,
+ setHideOrShowIgnoredSources: actions.setHideOrShowIgnoredSources,
+ toggleSourceMapIgnoreList: actions.toggleSourceMapIgnoreList,
+})(CommandBar);