summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js')
-rw-r--r--devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js142
1 files changed, 142 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js b/devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js
new file mode 100644
index 0000000000..0789b82694
--- /dev/null
+++ b/devtools/client/debugger/src/components/Editor/Preview/ExceptionPopup.js
@@ -0,0 +1,142 @@
+/* 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, 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 Reps from "devtools/client/shared/components/reps/index";
+const {
+ REPS: { StringRep },
+} = Reps;
+
+import actions from "../../../actions/index";
+
+import AccessibleImage from "../../shared/AccessibleImage";
+const classnames = require("resource://devtools/client/shared/classnames.js");
+const ANONYMOUS_FN_NAME = "<anonymous>";
+
+// The exception popup works in two modes:
+// a. when the stacktrace is closed the exception popup
+// gets closed when the mouse leaves the popup.
+// b. when the stacktrace is opened the exception popup
+// gets closed only by clicking outside the popup.
+class ExceptionPopup extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isStacktraceExpanded: true,
+ };
+ }
+
+ static get propTypes() {
+ return {
+ mouseout: PropTypes.func.isRequired,
+ selectSourceURL: PropTypes.func.isRequired,
+ exception: PropTypes.object.isRequired,
+ };
+ }
+
+ onExceptionMessageClick() {
+ const isStacktraceExpanded = this.state.isStacktraceExpanded;
+ this.setState({ isStacktraceExpanded: !isStacktraceExpanded });
+ }
+
+ buildStackFrame(frame) {
+ const { filename, lineNumber } = frame;
+ const functionName = frame.functionName || ANONYMOUS_FN_NAME;
+ return div(
+ {
+ className: "frame",
+ onClick: () =>
+ this.props.selectSourceURL(filename, {
+ line: lineNumber,
+ }),
+ },
+ span(
+ {
+ className: "title",
+ },
+ functionName
+ ),
+ span(
+ {
+ className: "location",
+ },
+ span(
+ {
+ className: "filename",
+ },
+ filename
+ ),
+ ":",
+ span(
+ {
+ className: "line",
+ },
+ lineNumber
+ )
+ )
+ );
+ }
+
+ renderStacktrace(stacktrace) {
+ const isStacktraceExpanded = this.state.isStacktraceExpanded;
+
+ if (stacktrace.length && isStacktraceExpanded) {
+ return div(
+ {
+ className: "exception-stacktrace",
+ },
+ stacktrace.map(frame => this.buildStackFrame(frame))
+ );
+ }
+ return null;
+ }
+
+ renderArrowIcon(stacktrace) {
+ if (stacktrace.length) {
+ return React.createElement(AccessibleImage, {
+ className: classnames("arrow", {
+ expanded: this.state.isStacktraceExpanded,
+ }),
+ });
+ }
+ return null;
+ }
+
+ render() {
+ const {
+ exception: { stacktrace, errorMessage },
+ mouseout,
+ } = this.props;
+ return div(
+ {
+ className: "preview-popup exception-popup",
+ dir: "ltr",
+ onMouseLeave: () => mouseout(true, this.state.isStacktraceExpanded),
+ },
+ div(
+ {
+ className: "exception-message",
+ onClick: () => this.onExceptionMessageClick(),
+ },
+ this.renderArrowIcon(stacktrace),
+ StringRep.rep({
+ object: errorMessage,
+ useQuotes: false,
+ className: "exception-text",
+ })
+ ),
+ this.renderStacktrace(stacktrace)
+ );
+ }
+}
+
+const mapDispatchToProps = {
+ selectSourceURL: actions.selectSourceURL,
+};
+
+export default connect(null, mapDispatchToProps)(ExceptionPopup);