summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/debugger/src/components/SecondaryPanes/Breakpoints
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/components/SecondaryPanes/Breakpoints')
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoint.js235
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointHeading.js84
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoints.css235
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/ExceptionOption.js40
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/index.js172
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/moz.build13
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/ExceptionOption.spec.js22
-rw-r--r--devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/ExceptionOption.spec.js.snap18
8 files changed, 819 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoint.js b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoint.js
new file mode 100644
index 0000000000..c55088e411
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoint.js
@@ -0,0 +1,235 @@
+/* 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,
+ input,
+ span,
+} 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 { createSelector } from "devtools/client/shared/vendor/reselect";
+import actions from "../../../actions/index";
+
+import { CloseButton } from "../../shared/Button/index";
+
+import {
+ getSelectedText,
+ makeBreakpointId,
+} from "../../../utils/breakpoint/index";
+import { getSelectedLocation } from "../../../utils/selected-location";
+import { isLineBlackboxed } from "../../../utils/source";
+
+import {
+ getSelectedFrame,
+ getSelectedSource,
+ getCurrentThread,
+ isSourceMapIgnoreListEnabled,
+ isSourceOnSourceMapIgnoreList,
+ getBlackBoxRanges,
+} from "../../../selectors/index";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class Breakpoint extends PureComponent {
+ static get propTypes() {
+ return {
+ breakpoint: PropTypes.object.isRequired,
+ disableBreakpoint: PropTypes.func.isRequired,
+ editor: PropTypes.object.isRequired,
+ enableBreakpoint: PropTypes.func.isRequired,
+ frame: PropTypes.object,
+ openConditionalPanel: PropTypes.func.isRequired,
+ removeBreakpoint: PropTypes.func.isRequired,
+ selectSpecificLocation: PropTypes.func.isRequired,
+ selectedSource: PropTypes.object,
+ source: PropTypes.object.isRequired,
+ blackboxedRangesForSource: PropTypes.array.isRequired,
+ checkSourceOnIgnoreList: PropTypes.func.isRequired,
+ isBreakpointLineBlackboxed: PropTypes.bool,
+ showBreakpointContextMenu: PropTypes.func.isRequired,
+ };
+ }
+
+ onContextMenu = event => {
+ event.preventDefault();
+
+ this.props.showBreakpointContextMenu(
+ event,
+ this.props.breakpoint,
+ this.props.source
+ );
+ };
+
+ get selectedLocation() {
+ const { breakpoint, selectedSource } = this.props;
+ return getSelectedLocation(breakpoint, selectedSource);
+ }
+
+ stopClicks = event => event.stopPropagation();
+
+ onDoubleClick = () => {
+ const { breakpoint, openConditionalPanel } = this.props;
+ if (breakpoint.options.condition) {
+ openConditionalPanel(this.selectedLocation);
+ } else if (breakpoint.options.logValue) {
+ openConditionalPanel(this.selectedLocation, true);
+ }
+ };
+
+ selectBreakpoint = event => {
+ event.preventDefault();
+ const { selectSpecificLocation } = this.props;
+ selectSpecificLocation(this.selectedLocation);
+ };
+
+ removeBreakpoint = event => {
+ const { removeBreakpoint, breakpoint } = this.props;
+ event.stopPropagation();
+ removeBreakpoint(breakpoint);
+ };
+
+ handleBreakpointCheckbox = () => {
+ const { breakpoint, enableBreakpoint, disableBreakpoint } = this.props;
+ if (breakpoint.disabled) {
+ enableBreakpoint(breakpoint);
+ } else {
+ disableBreakpoint(breakpoint);
+ }
+ };
+
+ isCurrentlyPausedAtBreakpoint() {
+ const { frame } = this.props;
+ if (!frame) {
+ return false;
+ }
+
+ const bpId = makeBreakpointId(this.selectedLocation);
+ const frameId = makeBreakpointId(frame.selectedLocation);
+ return bpId == frameId;
+ }
+
+ getBreakpointLocation() {
+ const { source } = this.props;
+ const { column, line } = this.selectedLocation;
+
+ const isWasm = source?.isWasm;
+ // column is 0-based everywhere, but we want to display 1-based to the user.
+ const columnVal = column ? `:${column + 1}` : "";
+ const bpLocation = isWasm
+ ? `0x${line.toString(16).toUpperCase()}`
+ : `${line}${columnVal}`;
+
+ return bpLocation;
+ }
+
+ getBreakpointText() {
+ const { breakpoint, selectedSource } = this.props;
+ const { condition, logValue } = breakpoint.options;
+ return logValue || condition || getSelectedText(breakpoint, selectedSource);
+ }
+
+ highlightText(text = "", editor) {
+ const node = document.createElement("div");
+ editor.CodeMirror.runMode(text, "application/javascript", node);
+ return { __html: node.innerHTML };
+ }
+
+ render() {
+ const { breakpoint, editor, isBreakpointLineBlackboxed } = this.props;
+ const text = this.getBreakpointText();
+ const labelId = `${breakpoint.id}-label`;
+ return div(
+ {
+ className: classnames({
+ breakpoint,
+ paused: this.isCurrentlyPausedAtBreakpoint(),
+ disabled: breakpoint.disabled,
+ "is-conditional": !!breakpoint.options.condition,
+ "is-log": !!breakpoint.options.logValue,
+ }),
+ onClick: this.selectBreakpoint,
+ onDoubleClick: this.onDoubleClick,
+ onContextMenu: this.onContextMenu,
+ },
+ input({
+ id: breakpoint.id,
+ type: "checkbox",
+ className: "breakpoint-checkbox",
+ checked: !breakpoint.disabled,
+ disabled: isBreakpointLineBlackboxed,
+ onChange: this.handleBreakpointCheckbox,
+ onClick: this.stopClicks,
+ "aria-labelledby": labelId,
+ }),
+ span(
+ {
+ id: labelId,
+ className: "breakpoint-label cm-s-mozilla devtools-monospace",
+ onClick: this.selectBreakpoint,
+ title: text,
+ },
+ span({
+ dangerouslySetInnerHTML: this.highlightText(text, editor),
+ })
+ ),
+ div(
+ {
+ className: "breakpoint-line-close",
+ },
+ div(
+ {
+ className: "breakpoint-line devtools-monospace",
+ },
+ this.getBreakpointLocation()
+ ),
+ React.createElement(CloseButton, {
+ handleClick: this.removeBreakpoint,
+ tooltip: L10N.getStr("breakpoints.removeBreakpointTooltip"),
+ })
+ )
+ );
+ }
+}
+
+const getFormattedFrame = createSelector(
+ getSelectedSource,
+ getSelectedFrame,
+ (selectedSource, frame) => {
+ if (!frame) {
+ return null;
+ }
+
+ return {
+ ...frame,
+ selectedLocation: getSelectedLocation(frame, selectedSource),
+ };
+ }
+);
+
+const mapStateToProps = (state, props) => {
+ const blackboxedRangesForSource = getBlackBoxRanges(state)[props.source.url];
+ const isSourceOnIgnoreList =
+ isSourceMapIgnoreListEnabled(state) &&
+ isSourceOnSourceMapIgnoreList(state, props.source);
+ return {
+ selectedSource: getSelectedSource(state),
+ isBreakpointLineBlackboxed: isLineBlackboxed(
+ blackboxedRangesForSource,
+ props.breakpoint.location.line,
+ isSourceOnIgnoreList
+ ),
+ frame: getFormattedFrame(state, getCurrentThread(state)),
+ };
+};
+
+export default connect(mapStateToProps, {
+ enableBreakpoint: actions.enableBreakpoint,
+ removeBreakpoint: actions.removeBreakpoint,
+ disableBreakpoint: actions.disableBreakpoint,
+ selectSpecificLocation: actions.selectSpecificLocation,
+ openConditionalPanel: actions.openConditionalPanel,
+ showBreakpointContextMenu: actions.showBreakpointContextMenu,
+})(Breakpoint);
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointHeading.js b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointHeading.js
new file mode 100644
index 0000000000..78cc530cff
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/BreakpointHeading.js
@@ -0,0 +1,84 @@
+/* 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, span } 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 actions from "../../../actions/index";
+
+import {
+ getTruncatedFileName,
+ getDisplayPath,
+ getSourceQueryString,
+ getFileURL,
+} from "../../../utils/source";
+import { createLocation } from "../../../utils/location";
+import { getFirstSourceActorForGeneratedSource } from "../../../selectors/index";
+
+import SourceIcon from "../../shared/SourceIcon";
+
+class BreakpointHeading extends PureComponent {
+ static get propTypes() {
+ return {
+ sources: PropTypes.array.isRequired,
+ source: PropTypes.object.isRequired,
+ firstSourceActor: PropTypes.object,
+ selectSource: PropTypes.func.isRequired,
+ showBreakpointHeadingContextMenu: PropTypes.func.isRequired,
+ };
+ }
+ onContextMenu = event => {
+ event.preventDefault();
+
+ this.props.showBreakpointHeadingContextMenu(event, this.props.source);
+ };
+
+ render() {
+ const { sources, source, selectSource } = this.props;
+
+ const path = getDisplayPath(source, sources);
+ const query = getSourceQueryString(source);
+ return div(
+ {
+ className: "breakpoint-heading",
+ title: getFileURL(source, false),
+ onClick: () => selectSource(source),
+ onContextMenu: this.onContextMenu,
+ },
+ React.createElement(
+ SourceIcon,
+ // Breakpoints are displayed per source and may relate to many source actors.
+ // Arbitrarily pick the first source actor to compute the matching source icon
+ // The source actor is used to pick one specific source text content and guess
+ // the related framework icon.
+ {
+ location: createLocation({
+ source,
+ sourceActor: this.props.firstSourceActor,
+ }),
+ modifier: icon =>
+ ["file", "javascript"].includes(icon) ? null : icon,
+ }
+ ),
+ div(
+ {
+ className: "filename",
+ },
+ getTruncatedFileName(source, query),
+ path && span(null, `../${path}/..`)
+ )
+ );
+ }
+}
+
+const mapStateToProps = (state, { source }) => ({
+ firstSourceActor: getFirstSourceActorForGeneratedSource(state, source.id),
+});
+
+export default connect(mapStateToProps, {
+ selectSource: actions.selectSource,
+ showBreakpointHeadingContextMenu: actions.showBreakpointHeadingContextMenu,
+})(BreakpointHeading);
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoints.css b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
new file mode 100644
index 0000000000..c05dd0b53f
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
@@ -0,0 +1,235 @@
+/* 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/>. */
+
+.breakpoints-pane > ._content {
+ overflow-x: auto;
+}
+
+.breakpoints-options *,
+.breakpoints-list * {
+ user-select: none;
+}
+
+.breakpoints-list {
+ padding: 4px 0;
+}
+
+.breakpoints-list .breakpoint-heading {
+ text-overflow: ellipsis;
+ width: 100%;
+ font-size: 12px;
+ line-height: 16px;
+}
+
+.breakpoint-heading:not(:first-child) {
+ margin-top: 2px;
+}
+
+.breakpoints-list .breakpoint-heading .filename {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.breakpoints-list .breakpoint-heading .filename span {
+ opacity: 0.7;
+ padding-left: 4px;
+}
+
+.breakpoints-list .breakpoint-heading,
+.breakpoints-list .breakpoint {
+ color: var(--theme-text-color-strong);
+ position: relative;
+ cursor: pointer;
+}
+
+.breakpoints-list .breakpoint-heading,
+.breakpoints-list .breakpoint,
+.breakpoints-options > * {
+ display: flex;
+ align-items: center;
+ overflow: hidden;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-inline-start: 16px;
+ padding-inline-end: 12px;
+}
+
+.breakpoints-exceptions-caught {
+ padding-bottom: 3px;
+ padding-top: 3px;
+ padding-inline-start: 36px;
+}
+
+.breakpoints-options {
+ padding-top: 4px;
+ padding-bottom: 4px;
+}
+
+.xhr-breakpoints-pane .breakpoints-options {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.breakpoints-options:not(.empty) {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.breakpoints-options input {
+ padding-inline-start: 2px;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-inline-start: 0;
+ margin-inline-end: 2px;
+ vertical-align: text-bottom;
+}
+
+.breakpoint-exceptions-label {
+ line-height: 14px;
+ padding-inline-end: 8px;
+ cursor: default;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.breakpoints-list .breakpoint,
+.breakpoints-list .breakpoint-heading,
+.breakpoints-options {
+ border-inline-start: 4px solid transparent
+}
+
+html .breakpoints-list .breakpoint.is-conditional {
+ border-inline-start-color: var(--theme-graphs-yellow);
+}
+
+html .breakpoints-list .breakpoint.is-log {
+ border-inline-start-color: var(--theme-graphs-purple);
+}
+
+html .breakpoints-list .breakpoint.paused {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--breakpoint-active-color);
+}
+
+.breakpoints-list .breakpoint:hover {
+ background-color: var(--search-overlays-semitransparent);
+}
+
+.breakpoint-line-close {
+ margin-inline-start: 4px;
+}
+
+.breakpoints-list .breakpoint .breakpoint-line {
+ font-size: 11px;
+ color: var(--theme-comment);
+ min-width: 16px;
+ text-align: end;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.breakpoints-list .breakpoint:hover .breakpoint-line,
+.breakpoints-list .breakpoint-line-close:focus-within .breakpoint-line {
+ color: transparent;
+}
+
+.breakpoints-list .breakpoint.paused:hover {
+ border-color: var(--breakpoint-active-color-hover);
+}
+
+.breakpoints-list .breakpoint-label {
+ display: inline-block;
+ cursor: pointer;
+ flex-grow: 1;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ font-size: 11px;
+}
+
+.breakpoints-list .breakpoint-label span,
+.breakpoint-line-close {
+ display: inline;
+ line-height: 14px;
+}
+
+.breakpoint-checkbox {
+ margin-inline-start: 0px;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ vertical-align: text-bottom;
+}
+
+.breakpoint-label .location {
+ width: 100%;
+ display: inline-block;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+ padding: 1px 0;
+ vertical-align: bottom;
+}
+
+.breakpoints-list .pause-indicator {
+ flex: 0 1 content;
+ order: 3;
+}
+
+.breakpoint .close-btn {
+ position: absolute;
+ /* hide button outside of row until hovered or focused */
+ top: -100px;
+}
+
+[dir="ltr"] .breakpoint .close-btn {
+ right: 12px;
+}
+
+[dir="rtl"] .breakpoint .close-btn {
+ left: 12px;
+}
+
+/* Reveal the remove button on hover/focus */
+.breakpoint:hover .close-btn,
+.breakpoint .close-btn:focus {
+ top: calc(50% - 8px);
+}
+
+/* Hide the line number when revealing the remove button (since they're overlayed) */
+.breakpoint-line-close:focus-within .breakpoint-line,
+.breakpoint:hover .breakpoint-line {
+ visibility: hidden;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint {
+ cursor: pointer;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-lines {
+ padding: 0;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-sizer {
+ min-width: initial !important;
+}
+
+.breakpoints-list .breakpoint .CodeMirror.cm-s-mozilla-breakpoint {
+ transition: opacity 0.15s linear;
+}
+
+.breakpoints-list .breakpoint.disabled .CodeMirror.cm-s-mozilla-breakpoint {
+ opacity: 0.5;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-line span[role="presentation"] {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-code,
+.CodeMirror.cm-s-mozilla-breakpoint .CodeMirror-scroll {
+ pointer-events: none;
+}
+
+.CodeMirror.cm-s-mozilla-breakpoint {
+ padding-top: 1px;
+}
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/ExceptionOption.js b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/ExceptionOption.js
new file mode 100644
index 0000000000..31ff3f44a3
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/ExceptionOption.js
@@ -0,0 +1,40 @@
+/* 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 {
+ div,
+ input,
+ label,
+} from "devtools/client/shared/vendor/react-dom-factories";
+import PropTypes from "devtools/client/shared/vendor/react-prop-types";
+
+export default function ExceptionOption({
+ className,
+ isChecked = false,
+ label: inputLabel,
+ onChange,
+}) {
+ return label(
+ {
+ className,
+ },
+ input({
+ type: "checkbox",
+ checked: isChecked,
+ onChange: onChange,
+ }),
+ div(
+ {
+ className: "breakpoint-exceptions-label",
+ },
+ inputLabel
+ )
+ );
+}
+
+ExceptionOption.propTypes = {
+ className: PropTypes.string.isRequired,
+ isChecked: PropTypes.bool.isRequired,
+ label: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/index.js b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/index.js
new file mode 100644
index 0000000000..0f5d6f7ae3
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/index.js
@@ -0,0 +1,172 @@
+/* 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 } 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 ExceptionOption from "./ExceptionOption";
+
+import Breakpoint from "./Breakpoint";
+import BreakpointHeading from "./BreakpointHeading";
+
+import actions from "../../../actions/index";
+import { getSelectedLocation } from "../../../utils/selected-location";
+import { createHeadlessEditor } from "../../../utils/editor/create-editor";
+
+import { makeBreakpointId } from "../../../utils/breakpoint/index";
+
+import {
+ getSelectedSource,
+ getBreakpointSources,
+ getShouldPauseOnDebuggerStatement,
+ getShouldPauseOnExceptions,
+ getShouldPauseOnCaughtExceptions,
+} from "../../../selectors/index";
+
+const classnames = require("resource://devtools/client/shared/classnames.js");
+
+class Breakpoints extends Component {
+ static get propTypes() {
+ return {
+ breakpointSources: PropTypes.array.isRequired,
+ pauseOnExceptions: PropTypes.func.isRequired,
+ selectedSource: PropTypes.object,
+ shouldPauseOnDebuggerStatement: PropTypes.bool.isRequired,
+ shouldPauseOnCaughtExceptions: PropTypes.bool.isRequired,
+ shouldPauseOnExceptions: PropTypes.bool.isRequired,
+ };
+ }
+
+ componentWillUnmount() {
+ this.removeEditor();
+ }
+
+ getEditor() {
+ if (!this.headlessEditor) {
+ this.headlessEditor = createHeadlessEditor();
+ }
+ return this.headlessEditor;
+ }
+
+ removeEditor() {
+ if (!this.headlessEditor) {
+ return;
+ }
+ this.headlessEditor.destroy();
+ this.headlessEditor = null;
+ }
+
+ togglePauseOnDebuggerStatement = () => {
+ this.props.pauseOnDebuggerStatement(
+ !this.props.shouldPauseOnDebuggerStatement
+ );
+ };
+
+ togglePauseOnException = () => {
+ this.props.pauseOnExceptions(!this.props.shouldPauseOnExceptions, false);
+ };
+
+ togglePauseOnCaughtException = () => {
+ this.props.pauseOnExceptions(
+ true,
+ !this.props.shouldPauseOnCaughtExceptions
+ );
+ };
+
+ renderExceptionsOptions() {
+ const {
+ breakpointSources,
+ shouldPauseOnDebuggerStatement,
+ shouldPauseOnExceptions,
+ shouldPauseOnCaughtExceptions,
+ } = this.props;
+
+ const isEmpty = !breakpointSources.length;
+ return div(
+ {
+ className: classnames("breakpoints-options", {
+ empty: isEmpty,
+ }),
+ },
+ React.createElement(ExceptionOption, {
+ className: "breakpoints-debugger-statement",
+ label: L10N.getStr("pauseOnDebuggerStatement"),
+ isChecked: shouldPauseOnDebuggerStatement,
+ onChange: this.togglePauseOnDebuggerStatement,
+ }),
+ React.createElement(ExceptionOption, {
+ className: "breakpoints-exceptions",
+ label: L10N.getStr("pauseOnExceptionsItem2"),
+ isChecked: shouldPauseOnExceptions,
+ onChange: this.togglePauseOnException,
+ }),
+ shouldPauseOnExceptions &&
+ React.createElement(ExceptionOption, {
+ className: "breakpoints-exceptions-caught",
+ label: L10N.getStr("pauseOnCaughtExceptionsItem"),
+ isChecked: shouldPauseOnCaughtExceptions,
+ onChange: this.togglePauseOnCaughtException,
+ })
+ );
+ }
+
+ renderBreakpoints() {
+ const { breakpointSources, selectedSource } = this.props;
+ if (!breakpointSources.length) {
+ return null;
+ }
+
+ const editor = this.getEditor();
+ const sources = breakpointSources.map(({ source }) => source);
+ return div(
+ {
+ className: "pane breakpoints-list",
+ },
+ breakpointSources.map(({ source, breakpoints }) => {
+ return [
+ React.createElement(BreakpointHeading, {
+ key: source.id,
+ source,
+ sources,
+ }),
+ breakpoints.map(breakpoint =>
+ React.createElement(Breakpoint, {
+ breakpoint,
+ source,
+ editor,
+ key: makeBreakpointId(
+ getSelectedLocation(breakpoint, selectedSource)
+ ),
+ })
+ ),
+ ];
+ })
+ );
+ }
+
+ render() {
+ return div(
+ {
+ className: "pane",
+ },
+ this.renderExceptionsOptions(),
+ this.renderBreakpoints()
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ breakpointSources: getBreakpointSources(state),
+ selectedSource: getSelectedSource(state),
+ shouldPauseOnDebuggerStatement: getShouldPauseOnDebuggerStatement(state),
+ shouldPauseOnExceptions: getShouldPauseOnExceptions(state),
+ shouldPauseOnCaughtExceptions: getShouldPauseOnCaughtExceptions(state),
+});
+
+export default connect(mapStateToProps, {
+ pauseOnDebuggerStatement: actions.pauseOnDebuggerStatement,
+ pauseOnExceptions: actions.pauseOnExceptions,
+})(Breakpoints);
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/moz.build b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/moz.build
new file mode 100644
index 0000000000..85716d122c
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/moz.build
@@ -0,0 +1,13 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += []
+
+CompiledModules(
+ "Breakpoint.js",
+ "BreakpointHeading.js",
+ "ExceptionOption.js",
+ "index.js",
+)
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/ExceptionOption.spec.js b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/ExceptionOption.spec.js
new file mode 100644
index 0000000000..51f0b1e948
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/ExceptionOption.spec.js
@@ -0,0 +1,22 @@
+/* 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 from "devtools/client/shared/vendor/react";
+import { shallow } from "enzyme";
+
+import ExceptionOption from "../ExceptionOption";
+
+describe("ExceptionOption renders", () => {
+ it("with values", () => {
+ const component = shallow(
+ React.createElement(ExceptionOption, {
+ label: "testLabel",
+ isChecked: true,
+ onChange: () => null,
+ className: "testClassName",
+ })
+ );
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/ExceptionOption.spec.js.snap b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/ExceptionOption.spec.js.snap
new file mode 100644
index 0000000000..3ed80783b6
--- /dev/null
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/ExceptionOption.spec.js.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ExceptionOption renders with values 1`] = `
+<label
+ className="testClassName"
+>
+ <input
+ checked={true}
+ onChange={[Function]}
+ type="checkbox"
+ />
+ <div
+ className="breakpoint-exceptions-label"
+ >
+ testLabel
+ </div>
+</label>
+`;