CommandBar.js (12392B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 import React, { Component } from "devtools/client/shared/vendor/react"; 6 import { div, button } from "devtools/client/shared/vendor/react-dom-factories"; 7 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 8 9 import { connect } from "devtools/client/shared/vendor/react-redux"; 10 import { features, prefs } from "../../utils/prefs"; 11 import { 12 getIsWaitingOnBreak, 13 getSkipPausing, 14 getCurrentThread, 15 isTopFrameSelected, 16 getIsCurrentThreadPaused, 17 } from "../../selectors/index"; 18 import actions from "../../actions/index"; 19 import { debugBtn } from "../shared/Button/CommandBarButton"; 20 import DebuggerImage from "../shared/DebuggerImage"; 21 22 const { 23 stringifyFromElectronKey, 24 } = require("resource://devtools/client/shared/key-shortcuts.js"); 25 const classnames = require("resource://devtools/client/shared/classnames.js"); 26 const MenuButton = require("resource://devtools/client/shared/components/menu/MenuButton.js"); 27 const MenuItem = require("resource://devtools/client/shared/components/menu/MenuItem.js"); 28 const MenuList = require("resource://devtools/client/shared/components/menu/MenuList.js"); 29 30 const isMacOS = Services.appinfo.OS === "Darwin"; 31 32 // NOTE: the "resume" command will call either the resume or breakOnNext action 33 // depending on whether or not the debugger is paused or running 34 const COMMANDS = ["resume", "stepOver", "stepIn", "stepOut"]; 35 36 const KEYS = { 37 WINNT: { 38 resume: "F8", 39 stepOver: "F10", 40 stepIn: "F11", 41 stepOut: "Shift+F11", 42 trace: "Ctrl+Shift+5", 43 }, 44 Darwin: { 45 resume: "Cmd+\\", 46 stepOver: "Cmd+'", 47 stepIn: "Cmd+;", 48 stepOut: "Cmd+Shift+:", 49 stepOutDisplay: "Cmd+Shift+;", 50 trace: "Ctrl+Shift+5", 51 }, 52 Linux: { 53 resume: "F8", 54 stepOver: "F10", 55 stepIn: "F11", 56 stepOut: "Shift+F11", 57 trace: "Ctrl+Shift+5", 58 }, 59 }; 60 61 function getKey(action) { 62 return getKeyForOS(Services.appinfo.OS, action); 63 } 64 65 function getKeyForOS(os, action) { 66 const osActions = KEYS[os] || KEYS.Linux; 67 return osActions[action]; 68 } 69 70 function formatKey(action) { 71 const key = getKey(`${action}Display`) || getKey(action); 72 73 // On MacOS, we bind both Windows and MacOS/Darwin key shortcuts 74 // Display them both, but only when they are different 75 if (isMacOS) { 76 const winKey = 77 getKeyForOS("WINNT", `${action}Display`) || getKeyForOS("WINNT", action); 78 if (key != winKey) { 79 return stringifyFromElectronKey([key, winKey].join(" ")); 80 } 81 } 82 return stringifyFromElectronKey(key); 83 } 84 85 class CommandBar extends Component { 86 constructor() { 87 super(); 88 89 this.state = {}; 90 } 91 static get propTypes() { 92 return { 93 breakOnNext: PropTypes.func.isRequired, 94 horizontal: PropTypes.bool.isRequired, 95 isPaused: PropTypes.bool.isRequired, 96 isWaitingOnBreak: PropTypes.bool.isRequired, 97 javascriptEnabled: PropTypes.bool.isRequired, 98 resume: PropTypes.func.isRequired, 99 skipPausing: PropTypes.bool.isRequired, 100 stepIn: PropTypes.func.isRequired, 101 stepOut: PropTypes.func.isRequired, 102 stepOver: PropTypes.func.isRequired, 103 toggleEditorWrapping: PropTypes.func.isRequired, 104 toggleInlinePreview: PropTypes.func.isRequired, 105 toggleJavaScriptEnabled: PropTypes.func.isRequired, 106 toggleSkipPausing: PropTypes.any.isRequired, 107 toggleSourceMapsEnabled: PropTypes.func.isRequired, 108 topFrameSelected: PropTypes.bool.isRequired, 109 setHideOrShowIgnoredSources: PropTypes.func.isRequired, 110 toggleSourceMapIgnoreList: PropTypes.func.isRequired, 111 togglePausedOverlay: PropTypes.func.isRequired, 112 }; 113 } 114 115 componentWillUnmount() { 116 const { shortcuts } = this.context; 117 118 COMMANDS.forEach(action => shortcuts.off(getKey(action))); 119 120 if (isMacOS) { 121 COMMANDS.forEach(action => shortcuts.off(getKeyForOS("WINNT", action))); 122 } 123 } 124 125 componentDidMount() { 126 const { shortcuts } = this.context; 127 128 COMMANDS.forEach(action => 129 shortcuts.on(getKey(action), e => this.handleEvent(e, action)) 130 ); 131 132 if (isMacOS) { 133 // The Mac supports both the Windows Function keys 134 // as well as the Mac non-Function keys 135 COMMANDS.forEach(action => 136 shortcuts.on(getKeyForOS("WINNT", action), e => 137 this.handleEvent(e, action) 138 ) 139 ); 140 } 141 } 142 143 handleEvent(e, action) { 144 e.preventDefault(); 145 e.stopPropagation(); 146 if (action === "resume") { 147 this.props.isPaused ? this.props.resume() : this.props.breakOnNext(); 148 } else { 149 this.props[action](); 150 } 151 } 152 153 renderStepButtons() { 154 const { isPaused, topFrameSelected } = this.props; 155 const className = isPaused ? "active" : "disabled"; 156 const isDisabled = !isPaused; 157 158 return [ 159 this.renderPauseButton(), 160 debugBtn( 161 () => this.props.stepOver(), 162 "stepOver", 163 className, 164 L10N.getFormatStr("stepOverTooltip", formatKey("stepOver")), 165 isDisabled 166 ), 167 debugBtn( 168 () => this.props.stepIn(), 169 "stepIn", 170 className, 171 L10N.getFormatStr("stepInTooltip", formatKey("stepIn")), 172 isDisabled || !topFrameSelected 173 ), 174 debugBtn( 175 () => this.props.stepOut(), 176 "stepOut", 177 className, 178 L10N.getFormatStr("stepOutTooltip", formatKey("stepOut")), 179 isDisabled 180 ), 181 ]; 182 } 183 184 resume() { 185 this.props.resume(); 186 } 187 188 renderPauseButton() { 189 const { breakOnNext, isWaitingOnBreak } = this.props; 190 191 if (this.props.isPaused) { 192 return debugBtn( 193 () => this.resume(), 194 "resume", 195 "active", 196 L10N.getFormatStr("resumeButtonTooltip", formatKey("resume")) 197 ); 198 } 199 200 if (isWaitingOnBreak) { 201 return debugBtn( 202 null, 203 "pause", 204 "disabled", 205 L10N.getStr("pausePendingButtonTooltip"), 206 true 207 ); 208 } 209 210 return debugBtn( 211 () => breakOnNext(), 212 "pause", 213 "active", 214 L10N.getFormatStr("pauseButtonTooltip", formatKey("resume")) 215 ); 216 } 217 218 renderSkipPausingButton() { 219 const { skipPausing, toggleSkipPausing } = this.props; 220 return button( 221 { 222 className: classnames( 223 "command-bar-button", 224 "command-bar-skip-pausing", 225 { 226 active: skipPausing, 227 } 228 ), 229 title: skipPausing 230 ? L10N.getStr("undoSkipPausingTooltip.label") 231 : L10N.getStr("skipPausingTooltip.label"), 232 onClick: toggleSkipPausing, 233 }, 234 React.createElement(DebuggerImage, { 235 name: skipPausing ? "enable-pausing" : "disable-pausing", 236 }) 237 ); 238 } 239 240 renderSettingsButton() { 241 const { toolboxDoc } = this.context; 242 return React.createElement( 243 MenuButton, 244 { 245 menuId: "debugger-settings-menu-button", 246 toolboxDoc, 247 className: 248 "devtools-button command-bar-button debugger-settings-menu-button", 249 title: L10N.getStr("settings.button.label"), 250 }, 251 () => this.renderSettingsMenuItems() 252 ); 253 } 254 255 renderSettingsMenuItems() { 256 return React.createElement( 257 MenuList, 258 { 259 id: "debugger-settings-menu-list", 260 }, 261 React.createElement(MenuItem, { 262 key: "debugger-settings-menu-item-disable-javascript", 263 className: "menu-item debugger-settings-menu-item-disable-javascript", 264 checked: !this.props.javascriptEnabled, 265 label: L10N.getStr("settings.disableJavaScript.label"), 266 tooltip: L10N.getStr("settings.disableJavaScript.tooltip"), 267 onClick: () => { 268 this.props.toggleJavaScriptEnabled(!this.props.javascriptEnabled); 269 }, 270 }), 271 React.createElement(MenuItem, { 272 key: "debugger-settings-menu-item-disable-inline-previews", 273 checked: features.inlinePreview, 274 label: L10N.getStr("inlinePreview.toggle.label"), 275 tooltip: L10N.getStr("inlinePreview.toggle.tooltip"), 276 onClick: () => this.props.toggleInlinePreview(!features.inlinePreview), 277 }), 278 React.createElement(MenuItem, { 279 key: "debugger-settings-menu-item-disable-wrap-lines", 280 checked: prefs.editorWrapping, 281 label: L10N.getStr("editorWrapping.toggle.label"), 282 tooltip: L10N.getStr("editorWrapping.toggle.tooltip"), 283 onClick: () => this.props.toggleEditorWrapping(!prefs.editorWrapping), 284 }), 285 React.createElement(MenuItem, { 286 key: "debugger-settings-menu-item-disable-sourcemaps", 287 checked: prefs.clientSourceMapsEnabled, 288 label: L10N.getStr("settings.toggleSourceMaps.label"), 289 tooltip: L10N.getStr("settings.toggleSourceMaps.tooltip"), 290 onClick: () => 291 this.props.toggleSourceMapsEnabled(!prefs.clientSourceMapsEnabled), 292 }), 293 React.createElement(MenuItem, { 294 key: "debugger-settings-menu-item-hide-ignored-sources", 295 className: "menu-item debugger-settings-menu-item-hide-ignored-sources", 296 checked: prefs.hideIgnoredSources, 297 label: L10N.getStr("settings.hideIgnoredSources.label"), 298 tooltip: L10N.getStr("settings.hideIgnoredSources.tooltip"), 299 onClick: () => 300 this.props.setHideOrShowIgnoredSources(!prefs.hideIgnoredSources), 301 }), 302 React.createElement(MenuItem, { 303 key: "debugger-settings-menu-item-enable-sourcemap-ignore-list", 304 className: 305 "menu-item debugger-settings-menu-item-enable-sourcemap-ignore-list", 306 checked: prefs.sourceMapIgnoreListEnabled, 307 label: L10N.getStr("settings.enableSourceMapIgnoreList.label"), 308 tooltip: L10N.getStr("settings.enableSourceMapIgnoreList.tooltip"), 309 onClick: () => 310 this.props.toggleSourceMapIgnoreList( 311 !prefs.sourceMapIgnoreListEnabled 312 ), 313 }), 314 React.createElement(MenuItem, { 315 key: "debugger-settings-menu-item-toggle-pause-overlay", 316 className: "menu-item debugger-settings-menu-item-toggle-pause-overlay", 317 checked: prefs.pausedOverlayEnabled, 318 label: L10N.getStr("settings.showPausedOverlay.label"), 319 tooltip: L10N.getStr("settings.showPausedOverlay.tooltip"), 320 onClick: () => 321 this.props.togglePausedOverlay(!prefs.pausedOverlayEnabled), 322 }), 323 React.createElement(MenuItem, { 324 key: "debugger-settings-menu-item-toggle-auto-pretty-print", 325 className: 326 "menu-item debugger-settings-menu-item-toggle-auto-pretty-print", 327 checked: prefs.autoPrettyPrint, 328 label: L10N.getStr("settings.autoPrettyPrint.label"), 329 tooltip: L10N.getStr("settings.autoPrettyPrint.tooltip"), 330 onClick: () => { 331 prefs.autoPrettyPrint = !prefs.autoPrettyPrint; 332 }, 333 }) 334 ); 335 } 336 337 render() { 338 return div( 339 { 340 className: classnames("command-bar", { 341 vertical: !this.props.horizontal, 342 }), 343 }, 344 this.renderStepButtons(), 345 div({ 346 className: "filler", 347 }), 348 this.renderSkipPausingButton(), 349 div({ 350 className: "devtools-separator", 351 }), 352 this.renderSettingsButton() 353 ); 354 } 355 } 356 357 CommandBar.contextTypes = { 358 shortcuts: PropTypes.object, 359 toolboxDoc: PropTypes.object, 360 }; 361 362 const mapStateToProps = state => ({ 363 isWaitingOnBreak: getIsWaitingOnBreak(state, getCurrentThread(state)), 364 skipPausing: getSkipPausing(state), 365 topFrameSelected: isTopFrameSelected(state, getCurrentThread(state)), 366 javascriptEnabled: state.ui.javascriptEnabled, 367 isPaused: getIsCurrentThreadPaused(state), 368 }); 369 370 export default connect(mapStateToProps, { 371 resume: actions.resume, 372 stepIn: actions.stepIn, 373 stepOut: actions.stepOut, 374 stepOver: actions.stepOver, 375 breakOnNext: actions.breakOnNext, 376 pauseOnExceptions: actions.pauseOnExceptions, 377 toggleSkipPausing: actions.toggleSkipPausing, 378 toggleInlinePreview: actions.toggleInlinePreview, 379 toggleEditorWrapping: actions.toggleEditorWrapping, 380 toggleSourceMapsEnabled: actions.toggleSourceMapsEnabled, 381 toggleJavaScriptEnabled: actions.toggleJavaScriptEnabled, 382 setHideOrShowIgnoredSources: actions.setHideOrShowIgnoredSources, 383 toggleSourceMapIgnoreList: actions.toggleSourceMapIgnoreList, 384 togglePausedOverlay: actions.togglePausedOverlay, 385 })(CommandBar);