summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/new-console-output/components
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/new-console-output/components')
-rw-r--r--devtools/client/webconsole/new-console-output/components/collapse-button.js50
-rw-r--r--devtools/client/webconsole/new-console-output/components/console-output.js125
-rw-r--r--devtools/client/webconsole/new-console-output/components/console-table.js202
-rw-r--r--devtools/client/webconsole/new-console-output/components/filter-bar.js170
-rw-r--r--devtools/client/webconsole/new-console-output/components/filter-button.js46
-rw-r--r--devtools/client/webconsole/new-console-output/components/grip-message-body.js102
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-container.js92
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-icon.js32
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-indent.js37
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-repeat.js36
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js132
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-command.js57
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js22
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js64
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/moz.build13
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js63
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/page-error.js69
-rw-r--r--devtools/client/webconsole/new-console-output/components/message.js176
-rw-r--r--devtools/client/webconsole/new-console-output/components/moz.build23
-rw-r--r--devtools/client/webconsole/new-console-output/components/variables-view-link.js34
20 files changed, 1545 insertions, 0 deletions
diff --git a/devtools/client/webconsole/new-console-output/components/collapse-button.js b/devtools/client/webconsole/new-console-output/components/collapse-button.js
new file mode 100644
index 000000000..ab72fcf4d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/collapse-button.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ DOM: dom,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+const CollapseButton = createClass({
+
+ displayName: "CollapseButton",
+
+ propTypes: {
+ open: PropTypes.bool.isRequired,
+ title: PropTypes.string,
+ },
+
+ getDefaultProps: function () {
+ return {
+ title: l10n.getStr("messageToggleDetails")
+ };
+ },
+
+ render: function () {
+ const { open, onClick, title } = this.props;
+
+ let classes = ["theme-twisty"];
+
+ if (open) {
+ classes.push("open");
+ }
+
+ return dom.a({
+ className: classes.join(" "),
+ onClick,
+ title: title,
+ });
+ }
+});
+
+module.exports = CollapseButton;
diff --git a/devtools/client/webconsole/new-console-output/components/console-output.js b/devtools/client/webconsole/new-console-output/components/console-output.js
new file mode 100644
index 000000000..1ba7f8dda
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -0,0 +1,125 @@
+/* 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/. */
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const {
+ getAllMessages,
+ getAllMessagesUiById,
+ getAllMessagesTableDataById,
+ getAllGroupsById,
+} = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getScrollSetting } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
+
+const ConsoleOutput = createClass({
+
+ displayName: "ConsoleOutput",
+
+ propTypes: {
+ messages: PropTypes.object.isRequired,
+ messagesUi: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ attachRefToHud: PropTypes.func.isRequired,
+ }),
+ autoscroll: PropTypes.bool.isRequired,
+ },
+
+ componentDidMount() {
+ scrollToBottom(this.outputNode);
+ this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
+ },
+
+ componentWillUpdate(nextProps, nextState) {
+ if (!this.outputNode) {
+ return;
+ }
+
+ const outputNode = this.outputNode;
+
+ // Figure out if we are at the bottom. If so, then any new message should be scrolled
+ // into view.
+ if (this.props.autoscroll && outputNode.lastChild) {
+ this.shouldScrollBottom = isScrolledToBottom(outputNode.lastChild, outputNode);
+ }
+ },
+
+ componentDidUpdate() {
+ if (this.shouldScrollBottom) {
+ scrollToBottom(this.outputNode);
+ }
+ },
+
+ render() {
+ let {
+ dispatch,
+ autoscroll,
+ messages,
+ messagesUi,
+ messagesTableData,
+ serviceContainer,
+ groups,
+ } = this.props;
+
+ let messageNodes = messages.map((message) => {
+ const parentGroups = message.groupId ? (
+ (groups.get(message.groupId) || [])
+ .concat([message.groupId])
+ ) : [];
+
+ return (
+ MessageContainer({
+ dispatch,
+ message,
+ key: message.id,
+ serviceContainer,
+ open: messagesUi.includes(message.id),
+ tableData: messagesTableData.get(message.id),
+ autoscroll,
+ indent: parentGroups.length,
+ })
+ );
+ });
+ return (
+ dom.div({
+ className: "webconsole-output",
+ ref: node => {
+ this.outputNode = node;
+ },
+ }, messageNodes
+ )
+ );
+ }
+});
+
+function scrollToBottom(node) {
+ node.scrollTop = node.scrollHeight;
+}
+
+function isScrolledToBottom(outputNode, scrollNode) {
+ let lastNodeHeight = outputNode.lastChild ?
+ outputNode.lastChild.clientHeight : 0;
+ return scrollNode.scrollTop + scrollNode.clientHeight >=
+ scrollNode.scrollHeight - lastNodeHeight / 2;
+}
+
+function mapStateToProps(state, props) {
+ return {
+ messages: getAllMessages(state),
+ messagesUi: getAllMessagesUiById(state),
+ messagesTableData: getAllMessagesTableDataById(state),
+ autoscroll: getScrollSetting(state),
+ groups: getAllGroupsById(state),
+ };
+}
+
+module.exports = connect(mapStateToProps)(ConsoleOutput);
diff --git a/devtools/client/webconsole/new-console-output/components/console-table.js b/devtools/client/webconsole/new-console-output/components/console-table.js
new file mode 100644
index 000000000..bf8fdcbd8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/console-table.js
@@ -0,0 +1,202 @@
+/* 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/. */
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { ObjectClient } = require("devtools/shared/client/main");
+const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+
+const TABLE_ROW_MAX_ITEMS = 1000;
+const TABLE_COLUMN_MAX_ITEMS = 10;
+
+const ConsoleTable = createClass({
+
+ displayName: "ConsoleTable",
+
+ propTypes: {
+ dispatch: PropTypes.func.isRequired,
+ parameters: PropTypes.array.isRequired,
+ serviceContainer: PropTypes.shape({
+ hudProxyClient: PropTypes.object.isRequired,
+ }),
+ id: PropTypes.string.isRequired,
+ },
+
+ componentWillMount: function () {
+ const {id, dispatch, serviceContainer, parameters} = this.props;
+
+ if (!Array.isArray(parameters) || parameters.length === 0) {
+ return;
+ }
+
+ const client = new ObjectClient(serviceContainer.hudProxyClient, parameters[0]);
+ let dataType = getParametersDataType(parameters);
+
+ // Get all the object properties.
+ dispatch(actions.messageTableDataGet(id, client, dataType));
+ },
+
+ getHeaders: function (columns) {
+ let headerItems = [];
+ columns.forEach((value, key) => headerItems.push(dom.th({}, value)));
+ return headerItems;
+ },
+
+ getRows: function (columns, items) {
+ return items.map(item => {
+ let cells = [];
+ columns.forEach((value, key) => {
+ cells.push(
+ dom.td(
+ {},
+ GripMessageBody({
+ grip: item[key]
+ })
+ )
+ );
+ });
+ return dom.tr({}, cells);
+ });
+ },
+
+ render: function () {
+ const {parameters, tableData} = this.props;
+ const headersGrip = parameters[1];
+ const headers = headersGrip && headersGrip.preview ? headersGrip.preview.items : null;
+
+ // if tableData is nullable, we don't show anything.
+ if (!tableData) {
+ return null;
+ }
+
+ const {columns, items} = getTableItems(
+ tableData,
+ getParametersDataType(parameters),
+ headers
+ );
+
+ return (
+ dom.table({className: "new-consoletable devtools-monospace"},
+ dom.thead({}, this.getHeaders(columns)),
+ dom.tbody({}, this.getRows(columns, items))
+ )
+ );
+ }
+});
+
+function getParametersDataType(parameters = null) {
+ if (!Array.isArray(parameters) || parameters.length === 0) {
+ return null;
+ }
+ return parameters[0].class;
+}
+
+function getTableItems(data = {}, type, headers = null) {
+ const INDEX_NAME = "_index";
+ const VALUE_NAME = "_value";
+ const namedIndexes = {
+ [INDEX_NAME]: (
+ ["Object", "Array"].includes(type) ?
+ l10n.getStr("table.index") : l10n.getStr("table.iterationIndex")
+ ),
+ [VALUE_NAME]: l10n.getStr("table.value"),
+ key: l10n.getStr("table.key")
+ };
+
+ let columns = new Map();
+ let items = [];
+
+ let addItem = function (item) {
+ items.push(item);
+ Object.keys(item).forEach(key => addColumn(key));
+ };
+
+ let addColumn = function (columnIndex) {
+ let columnExists = columns.has(columnIndex);
+ let hasMaxColumns = columns.size == TABLE_COLUMN_MAX_ITEMS;
+ let hasCustomHeaders = Array.isArray(headers);
+
+ if (
+ !columnExists &&
+ !hasMaxColumns && (
+ !hasCustomHeaders ||
+ headers.includes(columnIndex) ||
+ columnIndex === INDEX_NAME
+ )
+ ) {
+ columns.set(columnIndex, namedIndexes[columnIndex] || columnIndex);
+ }
+ };
+
+ for (let index of Object.keys(data)) {
+ if (type !== "Object" && index == parseInt(index, 10)) {
+ index = parseInt(index, 10);
+ }
+
+ let item = {
+ [INDEX_NAME]: index
+ };
+
+ let property = data[index].value;
+
+ if (property.preview) {
+ let {preview} = property;
+ let entries = preview.ownProperties || preview.items;
+ if (entries) {
+ for (let key of Object.keys(entries)) {
+ let entry = entries[key];
+ item[key] = entry.value || entry;
+ }
+ } else {
+ if (preview.key) {
+ item.key = preview.key;
+ }
+
+ item[VALUE_NAME] = preview.value || property;
+ }
+ } else {
+ item[VALUE_NAME] = property;
+ }
+
+ addItem(item);
+
+ if (items.length === TABLE_ROW_MAX_ITEMS) {
+ break;
+ }
+ }
+
+ // Some headers might not be present in the items, so we make sure to
+ // return all the headers set by the user.
+ if (Array.isArray(headers)) {
+ headers.forEach(header => addColumn(header));
+ }
+
+ // We want to always have the index column first
+ if (columns.has(INDEX_NAME)) {
+ let index = columns.get(INDEX_NAME);
+ columns.delete(INDEX_NAME);
+ columns = new Map([[INDEX_NAME, index], ...columns.entries()]);
+ }
+
+ // We want to always have the values column last
+ if (columns.has(VALUE_NAME)) {
+ let index = columns.get(VALUE_NAME);
+ columns.delete(VALUE_NAME);
+ columns.set(VALUE_NAME, index);
+ }
+
+ return {
+ columns,
+ items
+ };
+}
+
+module.exports = ConsoleTable;
diff --git a/devtools/client/webconsole/new-console-output/components/filter-bar.js b/devtools/client/webconsole/new-console-output/components/filter-bar.js
new file mode 100644
index 000000000..a386a414a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -0,0 +1,170 @@
+/* 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/. */
+"use strict";
+
+const {
+ createFactory,
+ createClass,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const { filterTextSet, filtersClear } = require("devtools/client/webconsole/new-console-output/actions/index");
+const { messagesClear } = require("devtools/client/webconsole/new-console-output/actions/index");
+const uiActions = require("devtools/client/webconsole/new-console-output/actions/index");
+const {
+ MESSAGE_LEVEL
+} = require("../constants");
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
+
+const FilterBar = createClass({
+
+ displayName: "FilterBar",
+
+ propTypes: {
+ filter: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ attachRefToHud: PropTypes.func.isRequired,
+ }).isRequired,
+ ui: PropTypes.object.isRequired
+ },
+
+ componentDidMount() {
+ this.props.serviceContainer.attachRefToHud("filterBox",
+ this.wrapperNode.querySelector(".text-filter"));
+ },
+
+ onClickMessagesClear: function () {
+ this.props.dispatch(messagesClear());
+ },
+
+ onClickFilterBarToggle: function () {
+ this.props.dispatch(uiActions.filterBarToggle());
+ },
+
+ onClickFiltersClear: function () {
+ this.props.dispatch(filtersClear());
+ },
+
+ onSearchInput: function (e) {
+ this.props.dispatch(filterTextSet(e.target.value));
+ },
+
+ render() {
+ const {dispatch, filter, ui} = this.props;
+ let filterBarVisible = ui.filterBarVisible;
+ let children = [];
+
+ children.push(dom.div({className: "devtools-toolbar webconsole-filterbar-primary"},
+ dom.button({
+ className: "devtools-button devtools-clear-icon",
+ title: "Clear output",
+ onClick: this.onClickMessagesClear
+ }),
+ dom.button({
+ className: "devtools-button devtools-filter-icon" + (
+ filterBarVisible ? " checked" : ""),
+ title: "Toggle filter bar",
+ onClick: this.onClickFilterBarToggle
+ }),
+ dom.input({
+ className: "devtools-plaininput text-filter",
+ type: "search",
+ value: filter.text,
+ placeholder: "Filter output",
+ onInput: this.onSearchInput
+ })
+ ));
+
+ if (filterBarVisible) {
+ children.push(
+ dom.div({className: "devtools-toolbar webconsole-filterbar-secondary"},
+ FilterButton({
+ active: filter.error,
+ label: "Errors",
+ filterKey: MESSAGE_LEVEL.ERROR,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.warn,
+ label: "Warnings",
+ filterKey: MESSAGE_LEVEL.WARN,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.log,
+ label: "Logs",
+ filterKey: MESSAGE_LEVEL.LOG,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.info,
+ label: "Info",
+ filterKey: MESSAGE_LEVEL.INFO,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.debug,
+ label: "Debug",
+ filterKey: MESSAGE_LEVEL.DEBUG,
+ dispatch
+ }),
+ dom.span({
+ className: "devtools-separator",
+ }),
+ FilterButton({
+ active: filter.netxhr,
+ label: "XHR",
+ filterKey: "netxhr",
+ dispatch
+ }),
+ FilterButton({
+ active: filter.net,
+ label: "Requests",
+ filterKey: "net",
+ dispatch
+ })
+ )
+ );
+ }
+
+ if (ui.filteredMessageVisible) {
+ children.push(
+ dom.div({className: "devtools-toolbar"},
+ dom.span({
+ className: "clear"},
+ "You have filters set that may hide some results. " +
+ "Learn more about our filtering syntax ",
+ dom.a({}, "here"),
+ "."),
+ dom.button({
+ className: "menu-filter-button",
+ onClick: this.onClickFiltersClear
+ }, "Remove filters")
+ )
+ );
+ }
+
+ return (
+ dom.div({
+ className: "webconsole-filteringbar-wrapper",
+ ref: node => {
+ this.wrapperNode = node;
+ }
+ }, ...children
+ )
+ );
+ }
+});
+
+function mapStateToProps(state) {
+ return {
+ filter: getAllFilters(state),
+ ui: getAllUi(state)
+ };
+}
+
+module.exports = connect(mapStateToProps)(FilterBar);
diff --git a/devtools/client/webconsole/new-console-output/components/filter-button.js b/devtools/client/webconsole/new-console-output/components/filter-button.js
new file mode 100644
index 000000000..4116bb524
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-button.js
@@ -0,0 +1,46 @@
+/* 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/. */
+"use strict";
+
+const {
+ createClass,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+
+const FilterButton = createClass({
+
+ displayName: "FilterButton",
+
+ propTypes: {
+ label: PropTypes.string.isRequired,
+ filterKey: PropTypes.string.isRequired,
+ active: PropTypes.bool.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ },
+
+ onClick: function () {
+ this.props.dispatch(actions.filterToggle(this.props.filterKey));
+ },
+
+ render() {
+ const {active, label, filterKey} = this.props;
+
+ let classList = [
+ "menu-filter-button",
+ filterKey,
+ ];
+ if (active) {
+ classList.push("checked");
+ }
+
+ return dom.button({
+ className: classList.join(" "),
+ onClick: this.onClick
+ }, label);
+ }
+});
+
+module.exports = FilterButton;
diff --git a/devtools/client/webconsole/new-console-output/components/grip-message-body.js b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
new file mode 100644
index 000000000..29c2e6a4f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// If this is being run from Mocha, then the browser loader hasn't set up
+// define. We need to do that before loading Rep.
+if (typeof define === "undefined") {
+ require("amd-loader");
+}
+
+// React
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+const StringRep = createFactories(require("devtools/client/shared/components/reps/string").StringRep).rep;
+const VariablesViewLink = createFactory(require("devtools/client/webconsole/new-console-output/components/variables-view-link"));
+const { Grip } = require("devtools/client/shared/components/reps/grip");
+
+GripMessageBody.displayName = "GripMessageBody";
+
+GripMessageBody.propTypes = {
+ grip: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.object,
+ ]).isRequired,
+ serviceContainer: PropTypes.shape({
+ createElement: PropTypes.func.isRequired,
+ }),
+ userProvidedStyle: PropTypes.string,
+};
+
+function GripMessageBody(props) {
+ const { grip, userProvidedStyle, serviceContainer } = props;
+
+ let styleObject;
+ if (userProvidedStyle && userProvidedStyle !== "") {
+ styleObject = cleanupStyle(userProvidedStyle, serviceContainer.createElement);
+ }
+
+ return (
+ // @TODO once there is a longString rep, also turn off quotes for those.
+ typeof grip === "string"
+ ? StringRep({
+ object: grip,
+ useQuotes: false,
+ mode: props.mode,
+ style: styleObject
+ })
+ : Rep({
+ object: grip,
+ objectLink: VariablesViewLink,
+ defaultRep: Grip,
+ mode: props.mode,
+ })
+ );
+}
+
+function cleanupStyle(userProvidedStyle, createElement) {
+ // Regular expression that matches the allowed CSS property names.
+ const allowedStylesRegex = new RegExp(
+ "^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|" +
+ "margin|padding|text|transition|outline|white-space|word|writing|" +
+ "(?:min-|max-)?width|(?:min-|max-)?height)"
+ );
+
+ // Regular expression that matches the forbidden CSS property values.
+ const forbiddenValuesRegexs = [
+ // url(), -moz-element()
+ /\b(?:url|(?:-moz-)?element)[\s('"]+/gi,
+
+ // various URL protocols
+ /['"(]*(?:chrome|resource|about|app|data|https?|ftp|file):+\/*/gi,
+ ];
+
+ // Use a dummy element to parse the style string.
+ let dummy = createElement("div");
+ dummy.style = userProvidedStyle;
+
+ // Return a style object as expected by React DOM components, e.g.
+ // {color: "red"}
+ // without forbidden properties and values.
+ return [...dummy.style]
+ .filter(name => {
+ return allowedStylesRegex.test(name)
+ && !forbiddenValuesRegexs.some(regex => regex.test(dummy.style[name]));
+ })
+ .reduce((object, name) => {
+ return Object.assign({
+ [name]: dummy.style[name]
+ }, object);
+ }, {});
+}
+
+module.exports = GripMessageBody;
diff --git a/devtools/client/webconsole/new-console-output/components/message-container.js b/devtools/client/webconsole/new-console-output/components/message-container.js
new file mode 100644
index 000000000..115e9e291
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+
+const {
+ MESSAGE_SOURCE,
+ MESSAGE_TYPE
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const componentMap = new Map([
+ ["ConsoleApiCall", require("./message-types/console-api-call")],
+ ["ConsoleCommand", require("./message-types/console-command")],
+ ["DefaultRenderer", require("./message-types/default-renderer")],
+ ["EvaluationResult", require("./message-types/evaluation-result")],
+ ["NetworkEventMessage", require("./message-types/network-event-message")],
+ ["PageError", require("./message-types/page-error")]
+]);
+
+const MessageContainer = createClass({
+ displayName: "MessageContainer",
+
+ propTypes: {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool.isRequired,
+ serviceContainer: PropTypes.object.isRequired,
+ autoscroll: PropTypes.bool.isRequired,
+ indent: PropTypes.number.isRequired,
+ },
+
+ getDefaultProps: function () {
+ return {
+ open: false,
+ indent: 0,
+ };
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ const repeatChanged = this.props.message.repeat !== nextProps.message.repeat;
+ const openChanged = this.props.open !== nextProps.open;
+ const tableDataChanged = this.props.tableData !== nextProps.tableData;
+ return repeatChanged || openChanged || tableDataChanged;
+ },
+
+ render() {
+ const { message } = this.props;
+
+ let MessageComponent = createFactory(getMessageComponent(message));
+ return MessageComponent(this.props);
+ }
+});
+
+function getMessageComponent(message) {
+ switch (message.source) {
+ case MESSAGE_SOURCE.CONSOLE_API:
+ return componentMap.get("ConsoleApiCall");
+ case MESSAGE_SOURCE.NETWORK:
+ return componentMap.get("NetworkEventMessage");
+ case MESSAGE_SOURCE.JAVASCRIPT:
+ switch (message.type) {
+ case MESSAGE_TYPE.COMMAND:
+ return componentMap.get("ConsoleCommand");
+ case MESSAGE_TYPE.RESULT:
+ return componentMap.get("EvaluationResult");
+ // @TODO this is probably not the right behavior, but works for now.
+ // Chrome doesn't distinguish between page errors and log messages. We
+ // may want to remove the PageError component and just handle errors
+ // with ConsoleApiCall.
+ case MESSAGE_TYPE.LOG:
+ return componentMap.get("PageError");
+ default:
+ return componentMap.get("DefaultRenderer");
+ }
+ }
+
+ return componentMap.get("DefaultRenderer");
+}
+
+module.exports.MessageContainer = MessageContainer;
+
+// Exported so we can test it with unit tests.
+module.exports.getMessageComponent = getMessageComponent;
diff --git a/devtools/client/webconsole/new-console-output/components/message-icon.js b/devtools/client/webconsole/new-console-output/components/message-icon.js
new file mode 100644
index 000000000..b4c32fda0
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-icon.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+MessageIcon.displayName = "MessageIcon";
+
+MessageIcon.propTypes = {
+ level: PropTypes.string.isRequired,
+};
+
+function MessageIcon(props) {
+ const { level } = props;
+
+ const title = l10n.getStr("level." + level);
+ return dom.div({
+ className: "icon",
+ title
+ });
+}
+
+module.exports = MessageIcon;
diff --git a/devtools/client/webconsole/new-console-output/components/message-indent.js b/devtools/client/webconsole/new-console-output/components/message-indent.js
new file mode 100644
index 000000000..354e13589
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-indent.js
@@ -0,0 +1,37 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ DOM: dom,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const INDENT_WIDTH = 12;
+const MessageIndent = createClass({
+
+ displayName: "MessageIndent",
+
+ propTypes: {
+ indent: PropTypes.number.isRequired,
+ },
+
+ render: function () {
+ const { indent } = this.props;
+ return dom.span({
+ className: "indent",
+ style: {"width": indent * INDENT_WIDTH}
+ });
+ }
+});
+
+module.exports.MessageIndent = MessageIndent;
+
+// Exported so we can test it with unit tests.
+module.exports.INDENT_WIDTH = INDENT_WIDTH;
diff --git a/devtools/client/webconsole/new-console-output/components/message-repeat.js b/devtools/client/webconsole/new-console-output/components/message-repeat.js
new file mode 100644
index 000000000..1820340ea
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-repeat.js
@@ -0,0 +1,36 @@
+
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { PluralForm } = require("devtools/shared/plural-form");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+MessageRepeat.displayName = "MessageRepeat";
+
+MessageRepeat.propTypes = {
+ repeat: PropTypes.number.isRequired
+};
+
+function MessageRepeat(props) {
+ const { repeat } = props;
+ const visibility = repeat > 1 ? "visible" : "hidden";
+
+ return dom.span({
+ className: "message-repeats",
+ style: {visibility},
+ title: PluralForm.get(repeat, l10n.getStr("messageRepeats.tooltip2"))
+ .replace("#1", repeat)
+ }, repeat);
+}
+
+module.exports = MessageRepeat;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
new file mode 100644
index 000000000..7200648fa
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -0,0 +1,132 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+const ConsoleTable = createFactory(require("devtools/client/webconsole/new-console-output/components/console-table"));
+const {isGroupType, l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+ConsoleApiCall.displayName = "ConsoleApiCall";
+
+ConsoleApiCall.propTypes = {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool,
+ serviceContainer: PropTypes.object.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+ConsoleApiCall.defaultProps = {
+ open: false,
+ indent: 0,
+};
+
+function ConsoleApiCall(props) {
+ const {
+ dispatch,
+ message,
+ open,
+ tableData,
+ serviceContainer,
+ indent,
+ } = props;
+ const {
+ id: messageId,
+ source,
+ type,
+ level,
+ repeat,
+ stacktrace,
+ frame,
+ parameters,
+ messageText,
+ userProvidedStyles,
+ } = message;
+
+ let messageBody;
+ if (type === "trace") {
+ messageBody = dom.span({className: "cm-variable"}, "console.trace()");
+ } else if (type === "assert") {
+ let reps = formatReps(parameters);
+ messageBody = dom.span({ className: "cm-variable" }, "Assertion failed: ", reps);
+ } else if (type === "table") {
+ // TODO: Chrome does not output anything, see if we want to keep this
+ messageBody = dom.span({className: "cm-variable"}, "console.table()");
+ } else if (parameters) {
+ messageBody = formatReps(parameters, userProvidedStyles, serviceContainer);
+ } else {
+ messageBody = messageText;
+ }
+
+ let attachment = null;
+ if (type === "table") {
+ attachment = ConsoleTable({
+ dispatch,
+ id: message.id,
+ serviceContainer,
+ parameters: message.parameters,
+ tableData
+ });
+ }
+
+ let collapseTitle = null;
+ if (isGroupType(type)) {
+ collapseTitle = l10n.getStr("groupToggle");
+ }
+
+ const collapsible = isGroupType(type)
+ || (type === "error" && Array.isArray(stacktrace));
+ const topLevelClasses = ["cm-s-mozilla"];
+
+ return Message({
+ messageId,
+ open,
+ collapsible,
+ collapseTitle,
+ source,
+ type,
+ level,
+ topLevelClasses,
+ messageBody,
+ repeat,
+ frame,
+ stacktrace,
+ attachment,
+ serviceContainer,
+ dispatch,
+ indent,
+ });
+}
+
+function formatReps(parameters, userProvidedStyles, serviceContainer) {
+ return (
+ parameters
+ // Get all the grips.
+ .map((grip, key) => GripMessageBody({
+ grip,
+ key,
+ userProvidedStyle: userProvidedStyles ? userProvidedStyles[key] : null,
+ serviceContainer
+ }))
+ // Interleave spaces.
+ .reduce((arr, v, i) => {
+ return i + 1 < parameters.length
+ ? arr.concat(v, dom.span({}, " "))
+ : arr.concat(v);
+ }, [])
+ );
+}
+
+module.exports = ConsoleApiCall;
+
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
new file mode 100644
index 000000000..d87229fa9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+ConsoleCommand.displayName = "ConsoleCommand";
+
+ConsoleCommand.propTypes = {
+ message: PropTypes.object.isRequired,
+ autoscroll: PropTypes.bool.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+ConsoleCommand.defaultProps = {
+ indent: 0,
+};
+
+/**
+ * Displays input from the console.
+ */
+function ConsoleCommand(props) {
+ const { autoscroll, indent, message } = props;
+ const {
+ source,
+ type,
+ level,
+ messageText: messageBody,
+ } = message;
+
+ const {
+ serviceContainer,
+ } = props;
+
+ const childProps = {
+ source,
+ type,
+ level,
+ topLevelClasses: [],
+ messageBody,
+ scrollToMessage: autoscroll,
+ serviceContainer,
+ indent: indent,
+ };
+ return Message(childProps);
+}
+
+module.exports = ConsoleCommand;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
new file mode 100644
index 000000000..d07089531
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+} = require("devtools/client/shared/vendor/react");
+
+DefaultRenderer.displayName = "DefaultRenderer";
+
+function DefaultRenderer(props) {
+ return dom.div({},
+ "This message type is not supported yet."
+ );
+}
+
+module.exports = DefaultRenderer;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
new file mode 100644
index 000000000..992dc62cf
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+
+EvaluationResult.displayName = "EvaluationResult";
+
+EvaluationResult.propTypes = {
+ message: PropTypes.object.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+EvaluationResult.defaultProps = {
+ indent: 0,
+};
+
+function EvaluationResult(props) {
+ const { message, serviceContainer, indent } = props;
+ const {
+ source,
+ type,
+ level,
+ id: messageId,
+ exceptionDocURL,
+ frame,
+ } = message;
+
+ let messageBody;
+ if (message.messageText) {
+ messageBody = message.messageText;
+ } else {
+ messageBody = GripMessageBody({grip: message.parameters});
+ }
+
+ const topLevelClasses = ["cm-s-mozilla"];
+
+ const childProps = {
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ messageId,
+ scrollToMessage: props.autoscroll,
+ serviceContainer,
+ exceptionDocURL,
+ frame,
+ };
+ return Message(childProps);
+}
+
+module.exports = EvaluationResult;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/moz.build b/devtools/client/webconsole/new-console-output/components/message-types/moz.build
new file mode 100644
index 000000000..9b9f72017
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/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/.
+
+DevToolsModules(
+ 'console-api-call.js',
+ 'console-command.js',
+ 'default-renderer.js',
+ 'evaluation-result.js',
+ 'network-event-message.js',
+ 'page-error.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
new file mode 100644
index 000000000..e3c81a487
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+NetworkEventMessage.displayName = "NetworkEventMessage";
+
+NetworkEventMessage.propTypes = {
+ message: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ openNetworkPanel: PropTypes.func.isRequired,
+ }),
+ indent: PropTypes.number.isRequired,
+};
+
+NetworkEventMessage.defaultProps = {
+ indent: 0,
+};
+
+function NetworkEventMessage(props) {
+ const { message, serviceContainer, indent } = props;
+ const { actor, source, type, level, request, isXHR } = message;
+
+ const topLevelClasses = [ "cm-s-mozilla" ];
+
+ function onUrlClick() {
+ serviceContainer.openNetworkPanel(actor);
+ }
+
+ const method = dom.span({className: "method" }, request.method);
+ const xhr = isXHR
+ ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
+ : null;
+ const url = dom.a({ className: "url", title: request.url, onClick: onUrlClick },
+ request.url.replace(/\?.+/, ""));
+
+ const messageBody = dom.span({}, method, xhr, url);
+
+ const childProps = {
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ serviceContainer,
+ };
+ return Message(childProps);
+}
+
+module.exports = NetworkEventMessage;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
new file mode 100644
index 000000000..77ea75ff7
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+PageError.displayName = "PageError";
+
+PageError.propTypes = {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool,
+ indent: PropTypes.number.isRequired,
+};
+
+PageError.defaultProps = {
+ open: false,
+ indent: 0,
+};
+
+function PageError(props) {
+ const {
+ dispatch,
+ message,
+ open,
+ serviceContainer,
+ indent,
+ } = props;
+ const {
+ id: messageId,
+ source,
+ type,
+ level,
+ messageText: messageBody,
+ repeat,
+ stacktrace,
+ frame,
+ exceptionDocURL,
+ } = message;
+
+ const childProps = {
+ dispatch,
+ messageId,
+ open,
+ collapsible: Array.isArray(stacktrace),
+ source,
+ type,
+ level,
+ topLevelClasses: [],
+ indent,
+ messageBody,
+ repeat,
+ frame,
+ stacktrace,
+ serviceContainer,
+ exceptionDocURL,
+ };
+ return Message(childProps);
+}
+
+module.exports = PageError;
diff --git a/devtools/client/webconsole/new-console-output/components/message.js b/devtools/client/webconsole/new-console-output/components/message.js
new file mode 100644
index 000000000..f36bff7e4
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -0,0 +1,176 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const CollapseButton = createFactory(require("devtools/client/webconsole/new-console-output/components/collapse-button"));
+const MessageIndent = createFactory(require("devtools/client/webconsole/new-console-output/components/message-indent").MessageIndent);
+const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon"));
+const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat"));
+const FrameView = createFactory(require("devtools/client/shared/components/frame"));
+const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
+
+const Message = createClass({
+ displayName: "Message",
+
+ propTypes: {
+ open: PropTypes.bool,
+ collapsible: PropTypes.bool,
+ collapseTitle: PropTypes.string,
+ source: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ level: PropTypes.string.isRequired,
+ indent: PropTypes.number.isRequired,
+ topLevelClasses: PropTypes.array.isRequired,
+ messageBody: PropTypes.any.isRequired,
+ repeat: PropTypes.any,
+ frame: PropTypes.any,
+ attachment: PropTypes.any,
+ stacktrace: PropTypes.any,
+ messageId: PropTypes.string,
+ scrollToMessage: PropTypes.bool,
+ exceptionDocURL: PropTypes.string,
+ serviceContainer: PropTypes.shape({
+ emitNewMessage: PropTypes.func.isRequired,
+ onViewSourceInDebugger: PropTypes.func.isRequired,
+ sourceMapService: PropTypes.any,
+ }),
+ },
+
+ getDefaultProps: function () {
+ return {
+ indent: 0
+ };
+ },
+
+ componentDidMount() {
+ if (this.messageNode) {
+ if (this.props.scrollToMessage) {
+ this.messageNode.scrollIntoView();
+ }
+ // Event used in tests. Some message types don't pass it in because existing tests
+ // did not emit for them.
+ if (this.props.serviceContainer) {
+ this.props.serviceContainer.emitNewMessage(this.messageNode, this.props.messageId);
+ }
+ }
+ },
+
+ onLearnMoreClick: function () {
+ let {exceptionDocURL} = this.props;
+ this.props.serviceContainer.openLink(exceptionDocURL);
+ },
+
+ render() {
+ const {
+ messageId,
+ open,
+ collapsible,
+ collapseTitle,
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ frame,
+ stacktrace,
+ serviceContainer,
+ dispatch,
+ exceptionDocURL,
+ } = this.props;
+
+ topLevelClasses.push("message", source, type, level);
+ if (open) {
+ topLevelClasses.push("open");
+ }
+
+ const icon = MessageIcon({level});
+
+ // Figure out if there is an expandable part to the message.
+ let attachment = null;
+ if (this.props.attachment) {
+ attachment = this.props.attachment;
+ } else if (stacktrace) {
+ const child = open ? StackTrace({
+ stacktrace: stacktrace,
+ onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger
+ }) : null;
+ attachment = dom.div({ className: "stacktrace devtools-monospace" }, child);
+ }
+
+ // If there is an expandable part, make it collapsible.
+ let collapse = null;
+ if (collapsible) {
+ collapse = CollapseButton({
+ open,
+ title: collapseTitle,
+ onClick: function () {
+ if (open) {
+ dispatch(actions.messageClose(messageId));
+ } else {
+ dispatch(actions.messageOpen(messageId));
+ }
+ },
+ });
+ }
+
+ const repeat = this.props.repeat ? MessageRepeat({repeat: this.props.repeat}) : null;
+
+ // Configure the location.
+ const location = dom.span({ className: "message-location devtools-monospace" },
+ frame ? FrameView({
+ frame,
+ onClick: serviceContainer ? serviceContainer.onViewSourceInDebugger : undefined,
+ showEmptyPathAsHost: true,
+ sourceMapService: serviceContainer ? serviceContainer.sourceMapService : undefined
+ }) : null
+ );
+
+ let learnMore;
+ if (exceptionDocURL) {
+ learnMore = dom.a({
+ className: "learn-more-link webconsole-learn-more-link",
+ title: exceptionDocURL.split("?")[0],
+ onClick: this.onLearnMoreClick,
+ }, `[${l10n.getStr("webConsoleMoreInfoLabel")}]`);
+ }
+
+ return dom.div({
+ className: topLevelClasses.join(" "),
+ ref: node => {
+ this.messageNode = node;
+ }
+ },
+ // @TODO add timestamp
+ MessageIndent({indent}),
+ icon,
+ collapse,
+ dom.span({ className: "message-body-wrapper" },
+ dom.span({ className: "message-flex-body" },
+ dom.span({ className: "message-body devtools-monospace" },
+ messageBody,
+ learnMore
+ ),
+ repeat,
+ location
+ ),
+ attachment
+ )
+ );
+ }
+});
+
+module.exports = Message;
diff --git a/devtools/client/webconsole/new-console-output/components/moz.build b/devtools/client/webconsole/new-console-output/components/moz.build
new file mode 100644
index 000000000..8c0022314
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -0,0 +1,23 @@
+# 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 += [
+ 'message-types'
+]
+
+DevToolsModules(
+ 'collapse-button.js',
+ 'console-output.js',
+ 'console-table.js',
+ 'filter-bar.js',
+ 'filter-button.js',
+ 'grip-message-body.js',
+ 'message-container.js',
+ 'message-icon.js',
+ 'message-indent.js',
+ 'message-repeat.js',
+ 'message.js',
+ 'variables-view-link.js'
+)
diff --git a/devtools/client/webconsole/new-console-output/components/variables-view-link.js b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
new file mode 100644
index 000000000..4d79c322f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const {openVariablesView} = require("devtools/client/webconsole/new-console-output/utils/variables-view");
+
+VariablesViewLink.displayName = "VariablesViewLink";
+
+VariablesViewLink.propTypes = {
+ object: PropTypes.object.isRequired
+};
+
+function VariablesViewLink(props) {
+ const { object, children } = props;
+
+ return (
+ dom.a({
+ onClick: openVariablesView.bind(null, object),
+ className: "cm-variable",
+ draggable: false,
+ }, children)
+ );
+}
+
+module.exports = VariablesViewLink;