summaryrefslogtreecommitdiff
path: root/devtools/client/webconsole/new-console-output
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2021-12-21 21:30:53 +0000
committerMoonchild <moonchild@palemoon.org>2021-12-21 21:30:53 +0000
commit63f2b8552ad646ffd90338da302ddc32cf729fa8 (patch)
treefe349a8751d8505848a2c77174b80e9fa9c100d6 /devtools/client/webconsole/new-console-output
parent76820a38bd567e644a4d8d30dab09cd98a53f30e (diff)
downloadaura-central-63f2b8552ad646ffd90338da302ddc32cf729fa8.tar.gz
Issue %3005 - Move devtools back to source root
Devtools has a shit ton of hardcoded paths in itself that requires it to be in the tree root. It can't be moved trivially without changing hundreds of paths in the devtools modules.
Diffstat (limited to 'devtools/client/webconsole/new-console-output')
-rw-r--r--devtools/client/webconsole/new-console-output/actions/enhancers.js19
-rw-r--r--devtools/client/webconsole/new-console-output/actions/filters.js54
-rw-r--r--devtools/client/webconsole/new-console-output/actions/index.js17
-rw-r--r--devtools/client/webconsole/new-console-output/actions/messages.js99
-rw-r--r--devtools/client/webconsole/new-console-output/actions/moz.build11
-rw-r--r--devtools/client/webconsole/new-console-output/actions/ui.js26
-rw-r--r--devtools/client/webconsole/new-console-output/components/collapse-button.js49
-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.js101
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-container.js91
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-icon.js31
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-indent.js36
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-repeat.js35
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js131
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-command.js56
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js21
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js65
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/moz.build12
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js62
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/page-error.js70
-rw-r--r--devtools/client/webconsole/new-console-output/components/message.js204
-rw-r--r--devtools/client/webconsole/new-console-output/components/moz.build22
-rw-r--r--devtools/client/webconsole/new-console-output/components/variables-view-link.js33
-rw-r--r--devtools/client/webconsole/new-console-output/constants.js80
-rw-r--r--devtools/client/webconsole/new-console-output/main.js23
-rw-r--r--devtools/client/webconsole/new-console-output/moz.build20
-rw-r--r--devtools/client/webconsole/new-console-output/new-console-output-wrapper.js134
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/filters.js38
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/index.js17
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/messages.js134
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/moz.build11
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/prefs.js17
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/ui.js38
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/filters.js11
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/messages.js176
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/moz.build10
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/prefs.js11
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/ui.js19
-rw-r--r--devtools/client/webconsole/new-console-output/store.js74
-rw-r--r--devtools/client/webconsole/new-console-output/test/.eslintrc.js5
-rw-r--r--devtools/client/webconsole/new-console-output/test/chrome/chrome.ini7
-rw-r--r--devtools/client/webconsole/new-console-output/test/chrome/head.js16
-rw-r--r--devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html90
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js230
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js84
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js96
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/filter-button.test.js34
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/message-container.test.js54
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/message-icon.test.js23
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/message-repeat.test.js25
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js74
-rw-r--r--devtools/client/webconsole/new-console-output/test/components/page-error.test.js240
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/L10n.js27
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper.js10
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient.js9
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/PluralForm.js18
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/Services.js27
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils.js14
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/moz.build8
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js17
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini18
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js55
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js31
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js46
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js47
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js191
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build7
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js152
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html11
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html11
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js0
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js1477
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js184
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js29
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/moz.build11
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js189
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js188
-rw-r--r--devtools/client/webconsole/new-console-output/test/helpers.js67
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser.ini21
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js50
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js90
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js172
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js71
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js34
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js56
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js70
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js46
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js45
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/head.js137
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html28
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html17
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html28
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html19
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/test-console.html18
-rw-r--r--devtools/client/webconsole/new-console-output/test/moz.build16
-rw-r--r--devtools/client/webconsole/new-console-output/test/requireHelper.js38
-rw-r--r--devtools/client/webconsole/new-console-output/test/store/filters.test.js215
-rw-r--r--devtools/client/webconsole/new-console-output/test/store/messages.test.js353
-rw-r--r--devtools/client/webconsole/new-console-output/test/utils/getRepeatId.test.js41
-rw-r--r--devtools/client/webconsole/new-console-output/types.js53
-rw-r--r--devtools/client/webconsole/new-console-output/utils/id-generator.js21
-rw-r--r--devtools/client/webconsole/new-console-output/utils/messages.js285
-rw-r--r--devtools/client/webconsole/new-console-output/utils/moz.build9
-rw-r--r--devtools/client/webconsole/new-console-output/utils/variables-view.js19
107 files changed, 8375 insertions, 0 deletions
diff --git a/devtools/client/webconsole/new-console-output/actions/enhancers.js b/devtools/client/webconsole/new-console-output/actions/enhancers.js
new file mode 100644
index 000000000..b060cd3b3
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/enhancers.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { BATCH_ACTIONS } = require("../constants");
+
+function batchActions(batchedActions) {
+ return {
+ type: BATCH_ACTIONS,
+ actions: batchedActions,
+ };
+}
+
+module.exports = {
+ batchActions
+};
diff --git a/devtools/client/webconsole/new-console-output/actions/filters.js b/devtools/client/webconsole/new-console-output/actions/filters.js
new file mode 100644
index 000000000..5149464e0
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/filters.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const Services = require("Services");
+
+const {
+ FILTER_TEXT_SET,
+ FILTER_TOGGLE,
+ FILTERS_CLEAR,
+ PREFS,
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+function filterTextSet(text) {
+ return {
+ type: FILTER_TEXT_SET,
+ text
+ };
+}
+
+function filterToggle(filter) {
+ return (dispatch, getState) => {
+ dispatch({
+ type: FILTER_TOGGLE,
+ filter,
+ });
+ const filterState = getAllFilters(getState());
+ Services.prefs.setBoolPref(PREFS.FILTER[filter.toUpperCase()],
+ filterState.get(filter));
+ };
+}
+
+function filtersClear() {
+ return (dispatch, getState) => {
+ dispatch({
+ type: FILTERS_CLEAR,
+ });
+
+ const filterState = getAllFilters(getState());
+ for (let filter in filterState) {
+ Services.prefs.clearUserPref(PREFS.FILTER[filter.toUpperCase()]);
+ }
+ };
+}
+
+module.exports = {
+ filterTextSet,
+ filterToggle,
+ filtersClear
+};
diff --git a/devtools/client/webconsole/new-console-output/actions/index.js b/devtools/client/webconsole/new-console-output/actions/index.js
new file mode 100644
index 000000000..6aff9c848
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/index.js
@@ -0,0 +1,17 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 actionModules = [
+ "enhancers",
+ "filters",
+ "messages",
+ "ui",
+].map(filename => require(`./${filename}`));
+
+const actions = Object.assign({}, ...actionModules);
+
+module.exports = actions;
diff --git a/devtools/client/webconsole/new-console-output/actions/messages.js b/devtools/client/webconsole/new-console-output/actions/messages.js
new file mode 100644
index 000000000..5ae29efb6
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -0,0 +1,99 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 {
+ prepareMessage
+} = require("devtools/client/webconsole/new-console-output/utils/messages");
+const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
+const { batchActions } = require("devtools/client/webconsole/new-console-output/actions/enhancers");
+const {
+ MESSAGE_ADD,
+ MESSAGES_CLEAR,
+ MESSAGE_OPEN,
+ MESSAGE_CLOSE,
+ MESSAGE_TYPE,
+ MESSAGE_TABLE_RECEIVE,
+} = require("../constants");
+
+const defaultIdGenerator = new IdGenerator();
+
+function messageAdd(packet, idGenerator = null) {
+ if (idGenerator == null) {
+ idGenerator = defaultIdGenerator;
+ }
+ let message = prepareMessage(packet, idGenerator);
+ const addMessageAction = {
+ type: MESSAGE_ADD,
+ message
+ };
+
+ if (message.type === MESSAGE_TYPE.CLEAR) {
+ return batchActions([
+ messagesClear(),
+ addMessageAction,
+ ]);
+ }
+ return addMessageAction;
+}
+
+function messagesClear() {
+ return {
+ type: MESSAGES_CLEAR
+ };
+}
+
+function messageOpen(id) {
+ return {
+ type: MESSAGE_OPEN,
+ id
+ };
+}
+
+function messageClose(id) {
+ return {
+ type: MESSAGE_CLOSE,
+ id
+ };
+}
+
+function messageTableDataGet(id, client, dataType) {
+ return (dispatch) => {
+ let fetchObjectActorData;
+ if (["Map", "WeakMap", "Set", "WeakSet"].includes(dataType)) {
+ fetchObjectActorData = (cb) => client.enumEntries(cb);
+ } else {
+ fetchObjectActorData = (cb) => client.enumProperties({
+ ignoreNonIndexedProperties: dataType === "Array"
+ }, cb);
+ }
+
+ fetchObjectActorData(enumResponse => {
+ const {iterator} = enumResponse;
+ iterator.slice(0, iterator.count, sliceResponse => {
+ let {ownProperties} = sliceResponse;
+ dispatch(messageTableDataReceive(id, ownProperties));
+ });
+ });
+ };
+}
+
+function messageTableDataReceive(id, data) {
+ return {
+ type: MESSAGE_TABLE_RECEIVE,
+ id,
+ data
+ };
+}
+
+module.exports = {
+ messageAdd,
+ messagesClear,
+ messageOpen,
+ messageClose,
+ messageTableDataGet,
+};
+
diff --git a/devtools/client/webconsole/new-console-output/actions/moz.build b/devtools/client/webconsole/new-console-output/actions/moz.build
new file mode 100644
index 000000000..b98931c18
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/moz.build
@@ -0,0 +1,11 @@
+# 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(
+ 'enhancers.js',
+ 'filters.js',
+ 'index.js',
+ 'messages.js',
+ 'ui.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/actions/ui.js b/devtools/client/webconsole/new-console-output/actions/ui.js
new file mode 100644
index 000000000..a95dbc131
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const Services = require("Services");
+
+const {
+ FILTER_BAR_TOGGLE,
+ PREFS,
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+function filterBarToggle(show) {
+ return (dispatch, getState) => {
+ dispatch({
+ type: FILTER_BAR_TOGGLE
+ });
+ const uiState = getAllUi(getState());
+ Services.prefs.setBoolPref(PREFS.UI.FILTER_BAR, uiState.get("filterBarVisible"));
+ };
+}
+
+exports.filterBarToggle = filterBarToggle;
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..2737d0f11
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/collapse-button.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..9e353be0a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -0,0 +1,101 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..94163eb04
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..a2b9b8633
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-icon.js
@@ -0,0 +1,31 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..33ee3ac5f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-indent.js
@@ -0,0 +1,36 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..049c224c2
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-repeat.js
@@ -0,0 +1,35 @@
+
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..804855c2c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -0,0 +1,131 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..14f4efcd7
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..490d4aee9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
@@ -0,0 +1,21 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..e1d62ab3e
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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,
+ notes,
+ } = 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,
+ notes,
+ };
+ 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..a67d038c2
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/moz.build
@@ -0,0 +1,12 @@
+# 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..e507625e9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -0,0 +1,62 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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..3c43ad785
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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,
+ notes,
+ } = message;
+
+ const childProps = {
+ dispatch,
+ messageId,
+ open,
+ collapsible: Array.isArray(stacktrace),
+ source,
+ type,
+ level,
+ topLevelClasses: [],
+ indent,
+ messageBody,
+ repeat,
+ frame,
+ stacktrace,
+ serviceContainer,
+ exceptionDocURL,
+ notes,
+ };
+ 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..5d09ee14b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -0,0 +1,204 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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,
+ }),
+ notes: PropTypes.arrayOf(PropTypes.shape({
+ messageBody: PropTypes.string.isRequired,
+ frame: 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,
+ notes,
+ } = 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));
+ }
+ },
+ });
+ }
+
+ let notesNodes;
+ if (notes) {
+ notesNodes = notes.map(note => dom.span(
+ { className: "message-flex-body error-note" },
+ dom.span({ className: "message-body devtools-monospace" },
+ "note: " + note.messageBody
+ ),
+ dom.span({ className: "message-location devtools-monospace" },
+ note.frame ? FrameView({
+ frame: note.frame,
+ onClick: serviceContainer
+ ? serviceContainer.onViewSourceInDebugger
+ : undefined,
+ showEmptyPathAsHost: true,
+ sourceMapService: serviceContainer
+ ? serviceContainer.sourceMapService
+ : undefined
+ }) : null
+ )));
+ } else {
+ notesNodes = [];
+ }
+
+ 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,
+ ...notesNodes
+ )
+ );
+ }
+});
+
+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..6f59310d2
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -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/.
+
+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..cf3f8a383
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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;
diff --git a/devtools/client/webconsole/new-console-output/constants.js b/devtools/client/webconsole/new-console-output/constants.js
new file mode 100644
index 000000000..5af7c8f6c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 actionTypes = {
+ BATCH_ACTIONS: "BATCH_ACTIONS",
+ MESSAGE_ADD: "MESSAGE_ADD",
+ MESSAGES_CLEAR: "MESSAGES_CLEAR",
+ MESSAGE_OPEN: "MESSAGE_OPEN",
+ MESSAGE_CLOSE: "MESSAGE_CLOSE",
+ MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
+ FILTER_TOGGLE: "FILTER_TOGGLE",
+ FILTER_TEXT_SET: "FILTER_TEXT_SET",
+ FILTERS_CLEAR: "FILTERS_CLEAR",
+ FILTER_BAR_TOGGLE: "FILTER_BAR_TOGGLE",
+};
+
+const prefs = {
+ PREFS: {
+ FILTER: {
+ ERROR: "devtools.webconsole.filter.error",
+ WARN: "devtools.webconsole.filter.warn",
+ INFO: "devtools.webconsole.filter.info",
+ LOG: "devtools.webconsole.filter.log",
+ DEBUG: "devtools.webconsole.filter.debug",
+ NET: "devtools.webconsole.filter.net",
+ NETXHR: "devtools.webconsole.filter.netxhr",
+ },
+ UI: {
+ FILTER_BAR: "devtools.webconsole.ui.filterbar"
+ }
+ }
+};
+
+const chromeRDPEnums = {
+ MESSAGE_SOURCE: {
+ XML: "xml",
+ JAVASCRIPT: "javascript",
+ NETWORK: "network",
+ CONSOLE_API: "console-api",
+ STORAGE: "storage",
+ APPCACHE: "appcache",
+ RENDERING: "rendering",
+ SECURITY: "security",
+ OTHER: "other",
+ DEPRECATION: "deprecation"
+ },
+ MESSAGE_TYPE: {
+ LOG: "log",
+ DIR: "dir",
+ TABLE: "table",
+ TRACE: "trace",
+ CLEAR: "clear",
+ START_GROUP: "startGroup",
+ START_GROUP_COLLAPSED: "startGroupCollapsed",
+ END_GROUP: "endGroup",
+ ASSERT: "assert",
+ PROFILE: "profile",
+ PROFILE_END: "profileEnd",
+ // Undocumented in Chrome RDP, but is used for evaluation results.
+ RESULT: "result",
+ // Undocumented in Chrome RDP, but is used for input.
+ COMMAND: "command",
+ // Undocumented in Chrome RDP, but is used for messages that should not
+ // output anything (e.g. `console.time()` calls).
+ NULL_MESSAGE: "nullMessage",
+ },
+ MESSAGE_LEVEL: {
+ LOG: "log",
+ ERROR: "error",
+ WARN: "warn",
+ DEBUG: "debug",
+ INFO: "info"
+ }
+};
+
+// Combine into a single constants object
+module.exports = Object.assign({}, actionTypes, prefs, chromeRDPEnums);
diff --git a/devtools/client/webconsole/new-console-output/main.js b/devtools/client/webconsole/new-console-output/main.js
new file mode 100644
index 000000000..29db5e337
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/main.js
@@ -0,0 +1,23 @@
+/* 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/. */
+
+ /* global BrowserLoader */
+
+"use strict";
+
+var { utils: Cu } = Components;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+// Initialize module loader and load all modules of the new inline
+// preview feature. The entire code-base doesn't need any extra
+// privileges and runs entirely in content scope.
+const NewConsoleOutputWrapper = BrowserLoader({
+ baseURI: "resource://devtools/client/webconsole/new-console-output/",
+ window}).require("./new-console-output-wrapper");
+
+this.NewConsoleOutput = function (parentNode, jsterm, toolbox, owner, serviceContainer) {
+ return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, serviceContainer);
+};
diff --git a/devtools/client/webconsole/new-console-output/moz.build b/devtools/client/webconsole/new-console-output/moz.build
new file mode 100644
index 000000000..f3e183c6c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/moz.build
@@ -0,0 +1,20 @@
+# 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 += [
+ 'actions',
+ 'components',
+ 'reducers',
+ 'selectors',
+ 'test',
+ 'utils',
+]
+
+DevToolsModules(
+ 'constants.js',
+ 'main.js',
+ 'new-console-output-wrapper.js',
+ 'store.js',
+ 'types.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
new file mode 100644
index 000000000..17c1e767d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -0,0 +1,134 @@
+/* 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 React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
+
+const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
+const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
+
+const store = configureStore();
+let queuedActions = [];
+let throttledDispatchTimeout = false;
+
+function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, document) {
+ this.parentNode = parentNode;
+ this.jsterm = jsterm;
+ this.toolbox = toolbox;
+ this.owner = owner;
+ this.document = document;
+
+ this.init = this.init.bind(this);
+}
+
+NewConsoleOutputWrapper.prototype = {
+ init: function () {
+ const attachRefToHud = (id, node) => {
+ this.jsterm.hud[id] = node;
+ };
+
+ let childComponent = ConsoleOutput({
+ serviceContainer: {
+ attachRefToHud,
+ emitNewMessage: (node, messageId) => {
+ this.jsterm.hud.emit("new-messages", new Set([{
+ node,
+ messageId,
+ }]));
+ },
+ hudProxyClient: this.jsterm.hud.proxy.client,
+ onViewSourceInDebugger: frame => this.toolbox.viewSourceInDebugger.call(
+ this.toolbox,
+ frame.url,
+ frame.line
+ ),
+ openNetworkPanel: (requestId) => {
+ return this.toolbox.selectTool("netmonitor").then(panel => {
+ return panel.panelWin.NetMonitorController.inspectRequest(requestId);
+ });
+ },
+ sourceMapService: this.toolbox ? this.toolbox._sourceMapService : null,
+ openLink: url => this.jsterm.hud.owner.openLink.call(this.jsterm.hud.owner, url),
+ createElement: nodename => {
+ return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
+ }
+ }
+ });
+ let filterBar = FilterBar({
+ serviceContainer: {
+ attachRefToHud
+ }
+ });
+ let provider = React.createElement(
+ Provider,
+ { store },
+ React.DOM.div(
+ {className: "webconsole-output-wrapper"},
+ filterBar,
+ childComponent
+ ));
+
+ this.body = ReactDOM.render(provider, this.parentNode);
+ },
+
+ dispatchMessageAdd: function (message, waitForResponse) {
+ let action = actions.messageAdd(message);
+ batchedMessageAdd(action);
+
+ // Wait for the message to render to resolve with the DOM node.
+ // This is just for backwards compatibility with old tests, and should
+ // be removed once it's not needed anymore.
+ // Can only wait for response if the action contains a valid message.
+ if (waitForResponse && action.message) {
+ let messageId = action.message.get("id");
+ return new Promise(resolve => {
+ let jsterm = this.jsterm;
+ jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
+ for (let m of messages) {
+ if (m.messageId == messageId) {
+ resolve(m.node);
+ jsterm.hud.off("new-messages", onThisMessage);
+ return;
+ }
+ }
+ });
+ });
+ }
+
+ return Promise.resolve();
+ },
+
+ dispatchMessagesAdd: function (messages) {
+ const batchedActions = messages.map(message => actions.messageAdd(message));
+ store.dispatch(actions.batchActions(batchedActions));
+ },
+
+ dispatchMessagesClear: function () {
+ store.dispatch(actions.messagesClear());
+ },
+ // Should be used for test purpose only.
+ getStore: function () {
+ return store;
+ }
+};
+
+function batchedMessageAdd(action) {
+ queuedActions.push(action);
+ if (!throttledDispatchTimeout) {
+ throttledDispatchTimeout = setTimeout(() => {
+ store.dispatch(actions.batchActions(queuedActions));
+ queuedActions = [];
+ throttledDispatchTimeout = null;
+ }, 50);
+ }
+}
+
+// Exports from this module
+module.exports = NewConsoleOutputWrapper;
diff --git a/devtools/client/webconsole/new-console-output/reducers/filters.js b/devtools/client/webconsole/new-console-output/reducers/filters.js
new file mode 100644
index 000000000..b4b13e316
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/filters.js
@@ -0,0 +1,38 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const constants = require("devtools/client/webconsole/new-console-output/constants");
+
+const FilterState = Immutable.Record({
+ debug: true,
+ error: true,
+ info: true,
+ log: true,
+ net: false,
+ netxhr: false,
+ text: "",
+ warn: true,
+});
+
+function filters(state = new FilterState(), action) {
+ switch (action.type) {
+ case constants.FILTER_TOGGLE:
+ const {filter} = action;
+ const active = !state.get(filter);
+ return state.set(filter, active);
+ case constants.FILTERS_CLEAR:
+ return new FilterState();
+ case constants.FILTER_TEXT_SET:
+ let {text} = action;
+ return state.set("text", text);
+ }
+
+ return state;
+}
+
+exports.FilterState = FilterState;
+exports.filters = filters;
diff --git a/devtools/client/webconsole/new-console-output/reducers/index.js b/devtools/client/webconsole/new-console-output/reducers/index.js
new file mode 100644
index 000000000..c67e01792
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/index.js
@@ -0,0 +1,17 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { filters } = require("./filters");
+const { messages } = require("./messages");
+const { prefs } = require("./prefs");
+const { ui } = require("./ui");
+
+exports.reducers = {
+ filters,
+ messages,
+ prefs,
+ ui,
+};
diff --git a/devtools/client/webconsole/new-console-output/reducers/messages.js b/devtools/client/webconsole/new-console-output/reducers/messages.js
new file mode 100644
index 000000000..beebb0ee6
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -0,0 +1,134 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const constants = require("devtools/client/webconsole/new-console-output/constants");
+const {isGroupType} = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+const MessageState = Immutable.Record({
+ // List of all the messages added to the console.
+ messagesById: Immutable.List(),
+ // List of the message ids which are opened.
+ messagesUiById: Immutable.List(),
+ // Map of the form {messageId : tableData}, which represent the data passed
+ // as an argument in console.table calls.
+ messagesTableDataById: Immutable.Map(),
+ // Map of the form {groupMessageId : groupArray},
+ // where groupArray is the list of of all the parent groups' ids of the groupMessageId.
+ groupsById: Immutable.Map(),
+ // Message id of the current group (no corresponding console.groupEnd yet).
+ currentGroup: null,
+});
+
+function messages(state = new MessageState(), action) {
+ const {
+ messagesById,
+ messagesUiById,
+ messagesTableDataById,
+ groupsById,
+ currentGroup
+ } = state;
+
+ switch (action.type) {
+ case constants.MESSAGE_ADD:
+ let newMessage = action.message;
+
+ if (newMessage.type === constants.MESSAGE_TYPE.NULL_MESSAGE) {
+ // When the message has a NULL type, we don't add it.
+ return state;
+ }
+
+ if (newMessage.type === constants.MESSAGE_TYPE.END_GROUP) {
+ // Compute the new current group.
+ return state.set("currentGroup", getNewCurrentGroup(currentGroup, groupsById));
+ }
+
+ if (newMessage.allowRepeating && messagesById.size > 0) {
+ let lastMessage = messagesById.last();
+ if (lastMessage.repeatId === newMessage.repeatId) {
+ return state.withMutations(function (record) {
+ record.set("messagesById", messagesById.pop().push(
+ newMessage.set("repeat", lastMessage.repeat + 1)
+ ));
+ });
+ }
+ }
+
+ return state.withMutations(function (record) {
+ // Add the new message with a reference to the parent group.
+ record.set(
+ "messagesById",
+ messagesById.push(newMessage.set("groupId", currentGroup))
+ );
+
+ if (newMessage.type === "trace") {
+ // We want the stacktrace to be open by default.
+ record.set("messagesUiById", messagesUiById.push(newMessage.id));
+ } else if (isGroupType(newMessage.type)) {
+ record.set("currentGroup", newMessage.id);
+ record.set("groupsById",
+ groupsById.set(
+ newMessage.id,
+ getParentGroups(currentGroup, groupsById)
+ )
+ );
+
+ if (newMessage.type === constants.MESSAGE_TYPE.START_GROUP) {
+ // We want the group to be open by default.
+ record.set("messagesUiById", messagesUiById.push(newMessage.id));
+ }
+ }
+ });
+ case constants.MESSAGES_CLEAR:
+ return state.withMutations(function (record) {
+ record.set("messagesById", Immutable.List());
+ record.set("messagesUiById", Immutable.List());
+ record.set("groupsById", Immutable.Map());
+ record.set("currentGroup", null);
+ });
+ case constants.MESSAGE_OPEN:
+ return state.set("messagesUiById", messagesUiById.push(action.id));
+ case constants.MESSAGE_CLOSE:
+ let index = state.messagesUiById.indexOf(action.id);
+ return state.deleteIn(["messagesUiById", index]);
+ case constants.MESSAGE_TABLE_RECEIVE:
+ const {id, data} = action;
+ return state.set("messagesTableDataById", messagesTableDataById.set(id, data));
+ }
+
+ return state;
+}
+
+function getNewCurrentGroup(currentGoup, groupsById) {
+ let newCurrentGroup = null;
+ if (currentGoup) {
+ // Retrieve the parent groups of the current group.
+ let parents = groupsById.get(currentGoup);
+ if (Array.isArray(parents) && parents.length > 0) {
+ // If there's at least one parent, make the first one the new currentGroup.
+ newCurrentGroup = parents[0];
+ }
+ }
+ return newCurrentGroup;
+}
+
+function getParentGroups(currentGroup, groupsById) {
+ let groups = [];
+ if (currentGroup) {
+ // If there is a current group, we add it as a parent
+ groups = [currentGroup];
+
+ // As well as all its parents, if it has some.
+ let parentGroups = groupsById.get(currentGroup);
+ if (Array.isArray(parentGroups) && parentGroups.length > 0) {
+ groups = groups.concat(parentGroups);
+ }
+ }
+
+ return groups;
+}
+
+exports.messages = messages;
diff --git a/devtools/client/webconsole/new-console-output/reducers/moz.build b/devtools/client/webconsole/new-console-output/reducers/moz.build
new file mode 100644
index 000000000..c190d3b7c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/moz.build
@@ -0,0 +1,11 @@
+# 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(
+ 'filters.js',
+ 'index.js',
+ 'messages.js',
+ 'prefs.js',
+ 'ui.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/reducers/prefs.js b/devtools/client/webconsole/new-console-output/reducers/prefs.js
new file mode 100644
index 000000000..450026e26
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/prefs.js
@@ -0,0 +1,17 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const PrefState = Immutable.Record({
+ logLimit: 1000
+});
+
+function prefs(state = new PrefState(), action) {
+ return state;
+}
+
+exports.PrefState = PrefState;
+exports.prefs = prefs;
diff --git a/devtools/client/webconsole/new-console-output/reducers/ui.js b/devtools/client/webconsole/new-console-output/reducers/ui.js
new file mode 100644
index 000000000..0ce969c97
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -0,0 +1,38 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 {
+ FILTER_BAR_TOGGLE,
+ MESSAGE_ADD,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const Immutable = require("devtools/client/shared/vendor/immutable");
+
+const UiState = Immutable.Record({
+ filterBarVisible: false,
+ filteredMessageVisible: false,
+ autoscroll: true,
+});
+
+function ui(state = new UiState(), action) {
+ // Autoscroll should be set for all action types. If the last action was not message
+ // add, then turn it off. This prevents us from scrolling after someone toggles a
+ // filter, or to the bottom of the attachement when an expandable message at the bottom
+ // of the list is expanded. It does depend on the MESSAGE_ADD action being the last in
+ // its batch, though.
+ state = state.set("autoscroll", action.type == MESSAGE_ADD);
+
+ switch (action.type) {
+ case FILTER_BAR_TOGGLE:
+ return state.set("filterBarVisible", !state.filterBarVisible);
+ }
+
+ return state;
+}
+
+module.exports = {
+ UiState,
+ ui,
+};
diff --git a/devtools/client/webconsole/new-console-output/selectors/filters.js b/devtools/client/webconsole/new-console-output/selectors/filters.js
new file mode 100644
index 000000000..fcba24341
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/filters.js
@@ -0,0 +1,11 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+function getAllFilters(state) {
+ return state.filters;
+}
+
+exports.getAllFilters = getAllFilters;
diff --git a/devtools/client/webconsole/new-console-output/selectors/messages.js b/devtools/client/webconsole/new-console-output/selectors/messages.js
new file mode 100644
index 000000000..98721fc1f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -0,0 +1,176 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const { getLogLimit } = require("devtools/client/webconsole/new-console-output/selectors/prefs");
+const {
+ MESSAGE_TYPE,
+ MESSAGE_SOURCE
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+function getAllMessages(state) {
+ let messages = getAllMessagesById(state);
+ let logLimit = getLogLimit(state);
+ let filters = getAllFilters(state);
+
+ let groups = getAllGroupsById(state);
+ let messagesUI = getAllMessagesUiById(state);
+
+ return prune(
+ messages.filter(message => {
+ return (
+ isInOpenedGroup(message, groups, messagesUI)
+ && (
+ isUnfilterable(message)
+ || (
+ matchLevelFilters(message, filters)
+ && matchNetworkFilters(message, filters)
+ && matchSearchFilters(message, filters)
+ )
+ )
+ );
+ }),
+ logLimit
+ );
+}
+
+function getAllMessagesById(state) {
+ return state.messages.messagesById;
+}
+
+function getAllMessagesUiById(state) {
+ return state.messages.messagesUiById;
+}
+
+function getAllMessagesTableDataById(state) {
+ return state.messages.messagesTableDataById;
+}
+
+function getAllGroupsById(state) {
+ return state.messages.groupsById;
+}
+
+function getCurrentGroup(state) {
+ return state.messages.currentGroup;
+}
+
+function isUnfilterable(message) {
+ return [
+ MESSAGE_TYPE.COMMAND,
+ MESSAGE_TYPE.RESULT,
+ MESSAGE_TYPE.START_GROUP,
+ MESSAGE_TYPE.START_GROUP_COLLAPSED,
+ ].includes(message.type);
+}
+
+function isInOpenedGroup(message, groups, messagesUI) {
+ return !message.groupId
+ || (
+ !isGroupClosed(message.groupId, messagesUI)
+ && !hasClosedParentGroup(groups.get(message.groupId), messagesUI)
+ );
+}
+
+function hasClosedParentGroup(group, messagesUI) {
+ return group.some(groupId => isGroupClosed(groupId, messagesUI));
+}
+
+function isGroupClosed(groupId, messagesUI) {
+ return messagesUI.includes(groupId) === false;
+}
+
+function matchLevelFilters(message, filters) {
+ return filters.get(message.level) === true;
+}
+
+function matchNetworkFilters(message, filters) {
+ return (
+ message.source !== MESSAGE_SOURCE.NETWORK
+ || (filters.get("net") === true && message.isXHR === false)
+ || (filters.get("netxhr") === true && message.isXHR === true)
+ );
+}
+
+function matchSearchFilters(message, filters) {
+ let text = filters.text || "";
+ return (
+ text === ""
+ // @TODO currently we return true for any object grip. We should find a way to
+ // search object grips.
+ || (message.parameters !== null && !Array.isArray(message.parameters))
+ // Look for a match in location.
+ || isTextInFrame(text, message.frame)
+ // Look for a match in stacktrace.
+ || (
+ Array.isArray(message.stacktrace) &&
+ message.stacktrace.some(frame => isTextInFrame(text,
+ // isTextInFrame expect the properties of the frame object to be in the same
+ // order they are rendered in the Frame component.
+ {
+ functionName: frame.functionName ||
+ l10n.getStr("stacktrace.anonymousFunction"),
+ filename: frame.filename,
+ lineNumber: frame.lineNumber,
+ columnNumber: frame.columnNumber
+ }))
+ )
+ // Look for a match in messageText.
+ || (message.messageText !== null
+ && message.messageText.toLocaleLowerCase().includes(text.toLocaleLowerCase()))
+ // Look for a match in parameters. Currently only checks value grips.
+ || (message.parameters !== null
+ && message.parameters.join("").toLocaleLowerCase()
+ .includes(text.toLocaleLowerCase()))
+ // Look for a match in notes.
+ || (Array.isArray(message.notes) && message.notes.some(note =>
+ // Look for a match in location.
+ isTextInFrame(text, note.frame)
+ // Look for a match in messageBody.
+ || (note.messageBody !== null
+ && note.messageBody.toLocaleLowerCase()
+ .includes(text.toLocaleLowerCase()))
+ ))
+ );
+}
+
+function isTextInFrame(text, frame) {
+ if (!frame) {
+ return false;
+ }
+ // @TODO Change this to Object.values once it's supported in Node's version of V8
+ return Object.keys(frame)
+ .map(key => frame[key])
+ .join(":")
+ .toLocaleLowerCase()
+ .includes(text.toLocaleLowerCase());
+}
+
+function prune(messages, logLimit) {
+ let messageCount = messages.count();
+ if (messageCount > logLimit) {
+ // If the second non-pruned message is in a group,
+ // we want to return the group as the first non-pruned message.
+ let firstIndex = messages.size - logLimit;
+ let groupId = messages.get(firstIndex + 1).groupId;
+
+ if (groupId) {
+ return messages.splice(0, firstIndex + 1)
+ .unshift(
+ messages.findLast((message) => message.id === groupId)
+ );
+ }
+ return messages.splice(0, firstIndex);
+ }
+
+ return messages;
+}
+
+exports.getAllMessages = getAllMessages;
+exports.getAllMessagesUiById = getAllMessagesUiById;
+exports.getAllMessagesTableDataById = getAllMessagesTableDataById;
+exports.getAllGroupsById = getAllGroupsById;
+exports.getCurrentGroup = getCurrentGroup;
diff --git a/devtools/client/webconsole/new-console-output/selectors/moz.build b/devtools/client/webconsole/new-console-output/selectors/moz.build
new file mode 100644
index 000000000..9ae16c5c5
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/moz.build
@@ -0,0 +1,10 @@
+# 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(
+ 'filters.js',
+ 'messages.js',
+ 'prefs.js',
+ 'ui.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/selectors/prefs.js b/devtools/client/webconsole/new-console-output/selectors/prefs.js
new file mode 100644
index 000000000..1056e44e0
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/prefs.js
@@ -0,0 +1,11 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+function getLogLimit(state) {
+ return state.prefs.logLimit;
+}
+
+exports.getLogLimit = getLogLimit;
diff --git a/devtools/client/webconsole/new-console-output/selectors/ui.js b/devtools/client/webconsole/new-console-output/selectors/ui.js
new file mode 100644
index 000000000..b935af749
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/ui.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+function getAllUi(state) {
+ return state.ui;
+}
+
+function getScrollSetting(state) {
+ return getAllUi(state).autoscroll;
+}
+
+module.exports = {
+ getAllUi,
+ getScrollSetting,
+};
diff --git a/devtools/client/webconsole/new-console-output/store.js b/devtools/client/webconsole/new-console-output/store.js
new file mode 100644
index 000000000..8ad7947e9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -0,0 +1,74 @@
+/* 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 {FilterState} = require("devtools/client/webconsole/new-console-output/reducers/filters");
+const {PrefState} = require("devtools/client/webconsole/new-console-output/reducers/prefs");
+const {UiState} = require("devtools/client/webconsole/new-console-output/reducers/ui");
+const {
+ applyMiddleware,
+ combineReducers,
+ compose,
+ createStore
+} = require("devtools/client/shared/vendor/redux");
+const { thunk } = require("devtools/client/shared/redux/middleware/thunk");
+const {
+ BATCH_ACTIONS,
+ PREFS,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const { reducers } = require("./reducers/index");
+const Services = require("Services");
+
+function configureStore() {
+ const initialState = {
+ prefs: new PrefState({
+ logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1),
+ }),
+ filters: new FilterState({
+ error: Services.prefs.getBoolPref(PREFS.FILTER.ERROR),
+ warn: Services.prefs.getBoolPref(PREFS.FILTER.WARN),
+ info: Services.prefs.getBoolPref(PREFS.FILTER.INFO),
+ log: Services.prefs.getBoolPref(PREFS.FILTER.LOG),
+ net: Services.prefs.getBoolPref(PREFS.FILTER.NET),
+ netxhr: Services.prefs.getBoolPref(PREFS.FILTER.NETXHR),
+ }),
+ ui: new UiState({
+ filterBarVisible: Services.prefs.getBoolPref(PREFS.UI.FILTER_BAR),
+ })
+ };
+
+ return createStore(
+ combineReducers(reducers),
+ initialState,
+ compose(applyMiddleware(thunk), enableBatching())
+ );
+}
+
+/**
+ * A enhancer for the store to handle batched actions.
+ */
+function enableBatching() {
+ return next => (reducer, initialState, enhancer) => {
+ function batchingReducer(state, action) {
+ switch (action.type) {
+ case BATCH_ACTIONS:
+ return action.actions.reduce(batchingReducer, state);
+ default:
+ return reducer(state, action);
+ }
+ }
+
+ if (typeof initialState === "function" && typeof enhancer === "undefined") {
+ enhancer = initialState;
+ initialState = undefined;
+ }
+
+ return next(batchingReducer, initialState, enhancer);
+ };
+}
+
+// Provide the store factory for test code so that each test is working with
+// its own instance.
+module.exports.configureStore = configureStore;
+
diff --git a/devtools/client/webconsole/new-console-output/test/.eslintrc.js b/devtools/client/webconsole/new-console-output/test/.eslintrc.js
new file mode 100644
index 000000000..e010df386
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ "extends": ["../../../../.eslintrc.xpcshell.js"]
+};
diff --git a/devtools/client/webconsole/new-console-output/test/chrome/chrome.ini b/devtools/client/webconsole/new-console-output/test/chrome/chrome.ini
new file mode 100644
index 000000000..0543ae5c6
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/chrome/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+support-files =
+ head.js
+
+[test_render_perf.html]
+skip-if = true # Bug 1306783
diff --git a/devtools/client/webconsole/new-console-output/test/chrome/head.js b/devtools/client/webconsole/new-console-output/test/chrome/head.js
new file mode 100644
index 000000000..e8a5fd22e
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/chrome/head.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { utils: Cu } = Components;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var { Assert } = require("resource://testing-common/Assert.jsm");
+var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+var { Task } = require("devtools/shared/task");
+
+var { require: browserRequire } = BrowserLoader({
+ baseURI: "resource://devtools/client/webconsole/",
+ window
+});
diff --git a/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html b/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
new file mode 100644
index 000000000..d22819a2b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for getRepeatId()</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" src="head.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for render perf</p>
+<div id="output"></div>
+
+<script type="text/javascript;version=1.8">
+const testPackets = [];
+const numMessages = 1000;
+for (let id = 0; id < numMessages; id++) {
+ let message = "Odd text";
+ if (id % 2 === 0) {
+ message = "Even text";
+ }
+ testPackets.push({
+ "from": "server1.conn4.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "foobar",
+ message,
+ id
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "file:///test.html",
+ "functionName": "",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "private": false,
+ "styles": [],
+ "timeStamp": 1455064271115 + id,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+ });
+}
+
+function timeit(cb) {
+ // Return a Promise that resolves the number of seconds cb takes.
+ return new Promise(resolve => {
+ let start = performance.now();
+ cb();
+ let elapsed = performance.now() - start;
+ resolve(elapsed / 1000);
+ });
+}
+
+window.onload = Task.async(function* () {
+ const { configureStore } = browserRequire("devtools/client/webconsole/new-console-output/store");
+ const { filterTextSet, filtersClear } = browserRequire("devtools/client/webconsole/new-console-output/actions/index");
+ const NewConsoleOutputWrapper = browserRequire("devtools/client/webconsole/new-console-output/new-console-output-wrapper");
+ const wrapper = new NewConsoleOutputWrapper(document.querySelector("#output"), {});
+
+ const store = configureStore();
+
+ let time = yield timeit(() => {
+ testPackets.forEach((message) => {
+ wrapper.dispatchMessageAdd(message);
+ });
+ });
+ info("took " + time + " seconds to render messages");
+
+ time = yield timeit(() => {
+ store.dispatch(filterTextSet("Odd text"));
+ });
+ info("took " + time + " seconds to search filter half the messages");
+
+ time = yield timeit(() => {
+ store.dispatch(filtersClear());
+ });
+ info("took " + time + " seconds to clear the filter");
+
+ ok(true, "Yay, it didn't time out!");
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
new file mode 100644
index 000000000..3b4e2b196
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -0,0 +1,230 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test utils.
+const expect = require("expect");
+const { render, mount } = require("enzyme");
+const sinon = require("sinon");
+
+// React
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("react-redux").Provider);
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+// Components under test.
+const ConsoleApiCall = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call"));
+const {
+ MESSAGE_OPEN,
+ MESSAGE_CLOSE,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
+
+// Test fakes.
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+const tempfilePath = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js";
+
+describe("ConsoleAPICall component:", () => {
+ describe("console.log", () => {
+ it("renders string grips", () => {
+ const message = stubPreparedMessages.get("console.log('foobar', 'test')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe("foobar test");
+ expect(wrapper.find(".objectBox-string").length).toBe(2);
+ expect(wrapper.find("div.message.cm-s-mozilla span span.message-flex-body span.message-body.devtools-monospace").length).toBe(1);
+
+ // There should be the location
+ const locationLink = wrapper.find(`.message-location`);
+ expect(locationLink.length).toBe(1);
+ expect(locationLink.text()).toBe("test-tempfile.js:1:27");
+ });
+
+ it("renders string grips with custom style", () => {
+ const message = stubPreparedMessages.get("console.log(%cfoobar)");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ const elements = wrapper.find(".objectBox-string");
+ expect(elements.text()).toBe("foobar");
+ expect(elements.length).toBe(2);
+
+ const firstElementStyle = elements.eq(0).prop("style");
+ // Allowed styles are applied accordingly on the first element.
+ expect(firstElementStyle.color).toBe(`blue`);
+ expect(firstElementStyle["font-size"]).toBe(`1.3em`);
+ // Forbidden styles are not applied.
+ expect(firstElementStyle["background-image"]).toBe(undefined);
+ expect(firstElementStyle.position).toBe(undefined);
+ expect(firstElementStyle.top).toBe(undefined);
+
+ const secondElementStyle = elements.eq(1).prop("style");
+ // Allowed styles are applied accordingly on the second element.
+ expect(secondElementStyle.color).toBe(`red`);
+ // Forbidden styles are not applied.
+ expect(secondElementStyle.background).toBe(undefined);
+ });
+
+ it("renders repeat node", () => {
+ const message =
+ stubPreparedMessages.get("console.log('foobar', 'test')")
+ .set("repeat", 107);
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-repeats").text()).toBe("107");
+ expect(wrapper.find(".message-repeats").prop("title")).toBe("107 repeats");
+
+ expect(wrapper.find("span > span.message-flex-body > span.message-body.devtools-monospace + span.message-repeats").length).toBe(1);
+ });
+
+ it("has the expected indent", () => {
+ const message = stubPreparedMessages.get("console.log('foobar', 'test')");
+
+ const indent = 10;
+ let wrapper = render(ConsoleApiCall({ message, serviceContainer, indent }));
+ expect(wrapper.find(".indent").prop("style").width)
+ .toBe(`${indent * INDENT_WIDTH}px`);
+
+ wrapper = render(ConsoleApiCall({ message, serviceContainer}));
+ expect(wrapper.find(".indent").prop("style").width).toBe(`0`);
+ });
+ });
+
+ describe("console.count", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.count('bar')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe("bar: 1");
+ });
+ });
+
+ describe("console.assert", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.assert(false, {message: 'foobar'})");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe("Assertion failed: Object { message: \"foobar\" }");
+ });
+ });
+
+ describe("console.time", () => {
+ it("does not show anything", () => {
+ const message = stubPreparedMessages.get("console.time('bar')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe("");
+ });
+ });
+
+ describe("console.timeEnd", () => {
+ it("renders as expected", () => {
+ const message = stubPreparedMessages.get("console.timeEnd('bar')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe(message.messageText);
+ expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
+ });
+ });
+
+ describe("console.trace", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.trace()");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer, open: true }));
+ const filepath = `${tempfilePath}`;
+
+ expect(wrapper.find(".message-body").text()).toBe("console.trace()");
+
+ const frameLinks = wrapper.find(`.stack-trace span.frame-link[data-url='${filepath}']`);
+ expect(frameLinks.length).toBe(3);
+
+ expect(frameLinks.eq(0).find(".frame-link-function-display-name").text()).toBe("testStacktraceFiltering");
+ expect(frameLinks.eq(0).find(".frame-link-filename").text()).toBe(filepath);
+
+ expect(frameLinks.eq(1).find(".frame-link-function-display-name").text()).toBe("foo");
+ expect(frameLinks.eq(1).find(".frame-link-filename").text()).toBe(filepath);
+
+ expect(frameLinks.eq(2).find(".frame-link-function-display-name").text()).toBe("triggerPacket");
+ expect(frameLinks.eq(2).find(".frame-link-filename").text()).toBe(filepath);
+
+ //it should not be collapsible.
+ expect(wrapper.find(`.theme-twisty`).length).toBe(0);
+ });
+ });
+
+ describe("console.group", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.group('bar')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer, open: true }));
+
+ expect(wrapper.find(".message-body").text()).toBe(message.messageText);
+ expect(wrapper.find(".theme-twisty.open").length).toBe(1);
+ });
+
+ it("toggle the group when the collapse button is clicked", () => {
+ const store = setupStore([]);
+ store.dispatch = sinon.spy();
+ const message = stubPreparedMessages.get("console.group('bar')");
+
+ let wrapper = mount(Provider({store},
+ ConsoleApiCall({
+ message,
+ open: true,
+ dispatch: store.dispatch,
+ serviceContainer,
+ })
+ ));
+ wrapper.find(".theme-twisty.open").simulate("click");
+ let call = store.dispatch.getCall(0);
+ expect(call.args[0]).toEqual({
+ id: message.id,
+ type: MESSAGE_CLOSE
+ });
+
+ wrapper = mount(Provider({store},
+ ConsoleApiCall({
+ message,
+ open: false,
+ dispatch: store.dispatch,
+ serviceContainer,
+ })
+ ));
+ wrapper.find(".theme-twisty").simulate("click");
+ call = store.dispatch.getCall(1);
+ expect(call.args[0]).toEqual({
+ id: message.id,
+ type: MESSAGE_OPEN
+ });
+ });
+ });
+
+ describe("console.groupEnd", () => {
+ it("does not show anything", () => {
+ const message = stubPreparedMessages.get("console.groupEnd('bar')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text()).toBe("");
+ });
+ });
+
+ describe("console.groupCollapsed", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.groupCollapsed('foo')");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer, open: false}));
+
+ expect(wrapper.find(".message-body").text()).toBe(message.messageText);
+ expect(wrapper.find(".theme-twisty:not(.open)").length).toBe(1);
+ });
+ });
+
+ describe("console.dirxml", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("console.dirxml(window)");
+ const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text())
+ .toBe("Window http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html");
+ });
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
new file mode 100644
index 000000000..4d7890807
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test utils.
+const expect = require("expect");
+const { render, mount } = require("enzyme");
+const sinon = require("sinon");
+
+// React
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("react-redux").Provider);
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+// Components under test.
+const EvaluationResult = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result"));
+const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
+
+// Test fakes.
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+describe("EvaluationResult component:", () => {
+ it("renders a grip result", () => {
+ const message = stubPreparedMessages.get("new Date(0)");
+ const wrapper = render(EvaluationResult({ message }));
+
+ expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z");
+
+ expect(wrapper.find(".message.log").length).toBe(1);
+ });
+
+ it("renders an error", () => {
+ const message = stubPreparedMessages.get("asdf()");
+ const wrapper = render(EvaluationResult({ message }));
+
+ expect(wrapper.find(".message-body").text())
+ .toBe("ReferenceError: asdf is not defined[Learn More]");
+
+ expect(wrapper.find(".message.error").length).toBe(1);
+ });
+
+ it("displays a [Learn more] link", () => {
+ const store = setupStore([]);
+
+ const message = stubPreparedMessages.get("asdf()");
+
+ serviceContainer.openLink = sinon.spy();
+ const wrapper = mount(Provider({store},
+ EvaluationResult({message, serviceContainer})
+ ));
+
+ const url =
+ "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined";
+ const learnMore = wrapper.find(".learn-more-link");
+ expect(learnMore.length).toBe(1);
+ expect(learnMore.prop("title")).toBe(url);
+
+ learnMore.simulate("click");
+ let call = serviceContainer.openLink.getCall(0);
+ expect(call.args[0]).toEqual(message.exceptionDocURL);
+ });
+
+ it("has the expected indent", () => {
+ const message = stubPreparedMessages.get("new Date(0)");
+
+ const indent = 10;
+ let wrapper = render(EvaluationResult({ message, indent}));
+ expect(wrapper.find(".indent").prop("style").width)
+ .toBe(`${indent * INDENT_WIDTH}px`);
+
+ wrapper = render(EvaluationResult({ message}));
+ expect(wrapper.find(".indent").prop("style").width).toBe(`0`);
+ });
+
+ it("has location information", () => {
+ const message = stubPreparedMessages.get("1 + @");
+ const wrapper = render(EvaluationResult({ message }));
+
+ const locationLink = wrapper.find(`.message-location`);
+ expect(locationLink.length).toBe(1);
+ expect(locationLink.text()).toBe("debugger eval code:1:4");
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
new file mode 100644
index 000000000..23f958cd9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-bar.test.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const expect = require("expect");
+const sinon = require("sinon");
+const { render, mount } = require("enzyme");
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("react-redux").Provider);
+
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
+const FilterBar = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
+const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const {
+ MESSAGES_CLEAR,
+ MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+describe("FilterBar component:", () => {
+ it("initial render", () => {
+ const store = setupStore([]);
+
+ const wrapper = render(Provider({store}, FilterBar({ serviceContainer })));
+ const toolbar = wrapper.find(
+ ".devtools-toolbar.webconsole-filterbar-primary"
+ );
+
+ // Clear button
+ expect(toolbar.children().eq(0).attr("class"))
+ .toBe("devtools-button devtools-clear-icon");
+ expect(toolbar.children().eq(0).attr("title")).toBe("Clear output");
+
+ // Filter bar toggle
+ expect(toolbar.children().eq(1).attr("class"))
+ .toBe("devtools-button devtools-filter-icon");
+ expect(toolbar.children().eq(1).attr("title")).toBe("Toggle filter bar");
+
+ // Text filter
+ expect(toolbar.children().eq(2).attr("class")).toBe("devtools-plaininput text-filter");
+ expect(toolbar.children().eq(2).attr("placeholder")).toBe("Filter output");
+ expect(toolbar.children().eq(2).attr("type")).toBe("search");
+ expect(toolbar.children().eq(2).attr("value")).toBe("");
+ });
+
+ it("displays filter bar when button is clicked", () => {
+ const store = setupStore([]);
+
+ expect(getAllUi(store.getState()).filterBarVisible).toBe(false);
+
+ const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
+ wrapper.find(".devtools-filter-icon").simulate("click");
+
+ expect(getAllUi(store.getState()).filterBarVisible).toBe(true);
+
+ // Buttons are displayed
+ const buttonProps = {
+ active: true,
+ dispatch: store.dispatch
+ };
+ const logButton = FilterButton(Object.assign({}, buttonProps,
+ { label: "Logs", filterKey: MESSAGE_LEVEL.LOG }));
+ const debugButton = FilterButton(Object.assign({}, buttonProps,
+ { label: "Debug", filterKey: MESSAGE_LEVEL.DEBUG }));
+ const infoButton = FilterButton(Object.assign({}, buttonProps,
+ { label: "Info", filterKey: MESSAGE_LEVEL.INFO }));
+ const warnButton = FilterButton(Object.assign({}, buttonProps,
+ { label: "Warnings", filterKey: MESSAGE_LEVEL.WARN }));
+ const errorButton = FilterButton(Object.assign({}, buttonProps,
+ { label: "Errors", filterKey: MESSAGE_LEVEL.ERROR }));
+ expect(wrapper.contains([errorButton, warnButton, logButton, infoButton, debugButton])).toBe(true);
+ });
+
+ it("fires MESSAGES_CLEAR action when clear button is clicked", () => {
+ const store = setupStore([]);
+ store.dispatch = sinon.spy();
+
+ const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
+ wrapper.find(".devtools-clear-icon").simulate("click");
+ const call = store.dispatch.getCall(0);
+ expect(call.args[0]).toEqual({
+ type: MESSAGES_CLEAR
+ });
+ });
+
+ it("sets filter text when text is typed", () => {
+ const store = setupStore([]);
+
+ const wrapper = mount(Provider({store}, FilterBar({ serviceContainer })));
+ wrapper.find(".devtools-plaininput").simulate("input", { target: { value: "a" } });
+ expect(store.getState().filters.text).toBe("a");
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js b/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js
new file mode 100644
index 000000000..3774da0b8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/filter-button.test.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const expect = require("expect");
+const { render } = require("enzyme");
+
+const { createFactory } = require("devtools/client/shared/vendor/react");
+
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
+const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
+
+describe("FilterButton component:", () => {
+ const props = {
+ active: true,
+ label: "Error",
+ filterKey: MESSAGE_LEVEL.ERROR,
+ };
+
+ it("displays as active when turned on", () => {
+ const wrapper = render(FilterButton(props));
+ expect(wrapper.html()).toBe(
+ "<button class=\"menu-filter-button error checked\">Error</button>"
+ );
+ });
+
+ it("displays as inactive when turned off", () => {
+ const inactiveProps = Object.assign({}, props, { active: false });
+ const wrapper = render(FilterButton(inactiveProps));
+ expect(wrapper.html()).toBe(
+ "<button class=\"menu-filter-button error\">Error</button>"
+ );
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/message-container.test.js b/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
new file mode 100644
index 000000000..2377af906
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/message-container.test.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test utils.
+const expect = require("expect");
+const {
+ renderComponent,
+ shallowRenderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+// Components under test.
+const { MessageContainer } = require("devtools/client/webconsole/new-console-output/components/message-container");
+const ConsoleApiCall = require("devtools/client/webconsole/new-console-output/components/message-types/console-api-call");
+const EvaluationResult = require("devtools/client/webconsole/new-console-output/components/message-types/evaluation-result");
+const PageError = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
+
+// Test fakes.
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+describe("MessageContainer component:", () => {
+ it("pipes data to children as expected", () => {
+ const message = stubPreparedMessages.get("console.log('foobar', 'test')");
+ const rendered = renderComponent(MessageContainer, {message, serviceContainer});
+
+ expect(rendered.textContent.includes("foobar")).toBe(true);
+ });
+ it("picks correct child component", () => {
+ const messageTypes = [
+ {
+ component: ConsoleApiCall,
+ message: stubPreparedMessages.get("console.log('foobar', 'test')")
+ },
+ {
+ component: EvaluationResult,
+ message: stubPreparedMessages.get("new Date(0)")
+ },
+ {
+ component: PageError,
+ message: stubPreparedMessages.get("ReferenceError: asdf is not defined")
+ }
+ ];
+
+ messageTypes.forEach(info => {
+ const { component, message } = info;
+ const rendered = shallowRenderComponent(MessageContainer, {
+ message,
+ serviceContainer,
+ });
+ expect(rendered.type).toBe(component);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js b/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
new file mode 100644
index 000000000..0244f08cf
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/message-icon.test.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ MESSAGE_LEVEL,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const MessageIcon = require("devtools/client/webconsole/new-console-output/components/message-icon");
+
+const expect = require("expect");
+
+const {
+ renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("MessageIcon component:", () => {
+ it("renders icon based on level", () => {
+ const rendered = renderComponent(MessageIcon, { level: MESSAGE_LEVEL.ERROR });
+
+ expect(rendered.classList.contains("icon")).toBe(true);
+ expect(rendered.getAttribute("title")).toBe("Error");
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/message-repeat.test.js b/devtools/client/webconsole/new-console-output/test/components/message-repeat.test.js
new file mode 100644
index 000000000..0257a3aad
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/message-repeat.test.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const MessageRepeat = require("devtools/client/webconsole/new-console-output/components/message-repeat");
+
+const expect = require("expect");
+
+const {
+ renderComponent
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+describe("MessageRepeat component:", () => {
+ it("renders repeated value correctly", () => {
+ const rendered = renderComponent(MessageRepeat, { repeat: 99 });
+ expect(rendered.classList.contains("message-repeats")).toBe(true);
+ expect(rendered.style.visibility).toBe("visible");
+ expect(rendered.textContent).toBe("99");
+ });
+
+ it("renders an un-repeated value correctly", () => {
+ const rendered = renderComponent(MessageRepeat, { repeat: 1 });
+ expect(rendered.style.visibility).toBe("hidden");
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
new file mode 100644
index 000000000..8d0c5307e
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test utils.
+const expect = require("expect");
+const { render } = require("enzyme");
+
+// React
+const { createFactory } = require("devtools/client/shared/vendor/react");
+
+// Components under test.
+const NetworkEventMessage = createFactory(require("devtools/client/webconsole/new-console-output/components/message-types/network-event-message"));
+const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
+
+// Test fakes.
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+const EXPECTED_URL = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html";
+
+describe("NetworkEventMessage component:", () => {
+ describe("GET request", () => {
+ it("renders as expected", () => {
+ const message = stubPreparedMessages.get("GET request");
+ const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body .method").text()).toBe("GET");
+ expect(wrapper.find(".message-body .xhr").length).toBe(0);
+ expect(wrapper.find(".message-body .url").length).toBe(1);
+ expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
+ expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
+ });
+
+ it("has the expected indent", () => {
+ const message = stubPreparedMessages.get("GET request");
+
+ const indent = 10;
+ let wrapper = render(NetworkEventMessage({ message, serviceContainer, indent}));
+ expect(wrapper.find(".indent").prop("style").width)
+ .toBe(`${indent * INDENT_WIDTH}px`);
+
+ wrapper = render(NetworkEventMessage({ message, serviceContainer }));
+ expect(wrapper.find(".indent").prop("style").width).toBe(`0`);
+ });
+ });
+
+ describe("XHR GET request", () => {
+ it("renders as expected", () => {
+ const message = stubPreparedMessages.get("XHR GET request");
+ const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body .method").text()).toBe("GET");
+ expect(wrapper.find(".message-body .xhr").length).toBe(1);
+ expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
+ expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
+ expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
+ });
+ });
+
+ describe("XHR POST request", () => {
+ it("renders as expected", () => {
+ const message = stubPreparedMessages.get("XHR POST request");
+ const wrapper = render(NetworkEventMessage({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body .method").text()).toBe("POST");
+ expect(wrapper.find(".message-body .xhr").length).toBe(1);
+ expect(wrapper.find(".message-body .xhr").text()).toBe("XHR");
+ expect(wrapper.find(".message-body .url").length).toBe(1);
+ expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
+ expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
new file mode 100644
index 000000000..5bc5fe0f0
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test utils.
+const expect = require("expect");
+const { render, mount } = require("enzyme");
+const sinon = require("sinon");
+
+// React
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const Provider = createFactory(require("react-redux").Provider);
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+
+// Components under test.
+const PageError = require("devtools/client/webconsole/new-console-output/components/message-types/page-error");
+const {
+ MESSAGE_OPEN,
+ MESSAGE_CLOSE,
+} = require("devtools/client/webconsole/new-console-output/constants");
+const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
+
+// Test fakes.
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
+
+describe("PageError component:", () => {
+ it("renders", () => {
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ const wrapper = render(PageError({ message, serviceContainer }));
+
+ expect(wrapper.find(".message-body").text())
+ .toBe("ReferenceError: asdf is not defined[Learn More]");
+
+ // The stacktrace should be closed by default.
+ const frameLinks = wrapper.find(`.stack-trace`);
+ expect(frameLinks.length).toBe(0);
+
+ // There should be the location.
+ const locationLink = wrapper.find(`.message-location`);
+ expect(locationLink.length).toBe(1);
+ // @TODO Will likely change. See https://github.com/devtools-html/gecko-dev/issues/285
+ expect(locationLink.text()).toBe("test-tempfile.js:3:5");
+ });
+
+ it("displays a [Learn more] link", () => {
+ const store = setupStore([]);
+
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+
+ serviceContainer.openLink = sinon.spy();
+ const wrapper = mount(Provider({store},
+ PageError({message, serviceContainer})
+ ));
+
+ // There should be a [Learn more] link.
+ const url =
+ "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined";
+ const learnMore = wrapper.find(".learn-more-link");
+ expect(learnMore.length).toBe(1);
+ expect(learnMore.prop("title")).toBe(url);
+
+ learnMore.simulate("click");
+ let call = serviceContainer.openLink.getCall(0);
+ expect(call.args[0]).toEqual(message.exceptionDocURL);
+ });
+
+ it("has a stacktrace which can be openned", () => {
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ const wrapper = render(PageError({ message, serviceContainer, open: true }));
+
+ // There should be a collapse button.
+ expect(wrapper.find(".theme-twisty.open").length).toBe(1);
+
+ // There should be three stacktrace items.
+ const frameLinks = wrapper.find(`.stack-trace span.frame-link`);
+ expect(frameLinks.length).toBe(3);
+ });
+
+ it("toggle the stacktrace when the collapse button is clicked", () => {
+ const store = setupStore([]);
+ store.dispatch = sinon.spy();
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+
+ let wrapper = mount(Provider({store},
+ PageError({
+ message,
+ open: true,
+ dispatch: store.dispatch,
+ serviceContainer,
+ })
+ ));
+ wrapper.find(".theme-twisty.open").simulate("click");
+ let call = store.dispatch.getCall(0);
+ expect(call.args[0]).toEqual({
+ id: message.id,
+ type: MESSAGE_CLOSE
+ });
+
+ wrapper = mount(Provider({store},
+ PageError({
+ message,
+ open: false,
+ dispatch: store.dispatch,
+ serviceContainer,
+ })
+ ));
+ wrapper.find(".theme-twisty").simulate("click");
+ call = store.dispatch.getCall(1);
+ expect(call.args[0]).toEqual({
+ id: message.id,
+ type: MESSAGE_OPEN
+ });
+ });
+
+ it("has the expected indent", () => {
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ const indent = 10;
+ let wrapper = render(PageError({ message, serviceContainer, indent}));
+ expect(wrapper.find(".indent").prop("style").width)
+ .toBe(`${indent * INDENT_WIDTH}px`);
+
+ wrapper = render(PageError({ message, serviceContainer}));
+ expect(wrapper.find(".indent").prop("style").width).toBe(`0`);
+ });
+
+ it("has empty error notes", () => {
+ const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ let wrapper = render(PageError({ message, serviceContainer }));
+
+ const notes = wrapper.find(".error-note");
+
+ expect(notes.length).toBe(0);
+ });
+
+ it("can show an error note", () => {
+ const origMessage = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ const message = origMessage.set("notes", [
+ {
+ "messageBody": "test note",
+ "frame": {
+ "source": "http://example.com/test.js",
+ "line": 2,
+ "column": 6
+ }
+ }
+ ]);
+
+ let wrapper = render(PageError({ message, serviceContainer }));
+
+ const notes = wrapper.find(".error-note");
+ expect(notes.length).toBe(1);
+
+ const note = notes.eq(0);
+ expect(note.find(".message-body").text())
+ .toBe("note: test note");
+
+ // There should be the location.
+ const locationLink = note.find(`.message-location`);
+ expect(locationLink.length).toBe(1);
+ expect(locationLink.text()).toBe("test.js:2:6");
+ });
+
+ it("can show multiple error notes", () => {
+ const origMessage = stubPreparedMessages.get("ReferenceError: asdf is not defined");
+ const message = origMessage.set("notes", [
+ {
+ "messageBody": "test note 1",
+ "frame": {
+ "source": "http://example.com/test1.js",
+ "line": 2,
+ "column": 6
+ }
+ },
+ {
+ "messageBody": "test note 2",
+ "frame": {
+ "source": "http://example.com/test2.js",
+ "line": 10,
+ "column": 18
+ }
+ },
+ {
+ "messageBody": "test note 3",
+ "frame": {
+ "source": "http://example.com/test3.js",
+ "line": 9,
+ "column": 4
+ }
+ }
+ ]);
+
+ let wrapper = render(PageError({ message, serviceContainer }));
+
+ const notes = wrapper.find(".error-note");
+ expect(notes.length).toBe(3);
+
+ const note1 = notes.eq(0);
+ expect(note1.find(".message-body").text())
+ .toBe("note: test note 1");
+
+ const locationLink1 = note1.find(`.message-location`);
+ expect(locationLink1.length).toBe(1);
+ expect(locationLink1.text()).toBe("test1.js:2:6");
+
+ const note2 = notes.eq(1);
+ expect(note2.find(".message-body").text())
+ .toBe("note: test note 2");
+
+ const locationLink2 = note2.find(`.message-location`);
+ expect(locationLink2.length).toBe(1);
+ expect(locationLink2.text()).toBe("test2.js:10:18");
+
+ const note3 = notes.eq(2);
+ expect(note3.find(".message-body").text())
+ .toBe("note: test note 3");
+
+ const locationLink3 = note3.find(`.message-location`);
+ expect(locationLink3.length).toBe(1);
+ expect(locationLink3.text()).toBe("test3.js:9:4");
+ });
+
+ it("displays error notes", () => {
+ const message = stubPreparedMessages.get("SyntaxError: redeclaration of let a");
+
+ let wrapper = render(PageError({ message, serviceContainer }));
+
+ const notes = wrapper.find(".error-note");
+ expect(notes.length).toBe(1);
+
+ const note = notes.eq(0);
+ expect(note.find(".message-body").text())
+ .toBe("note: Previously declared at line 2, column 6");
+
+ // There should be the location.
+ const locationLink = note.find(`.message-location`);
+ expect(locationLink.length).toBe(1);
+ expect(locationLink.text()).toBe("test-console-api.html:2:6");
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/L10n.js b/devtools/client/webconsole/new-console-output/test/fixtures/L10n.js
new file mode 100644
index 000000000..bb34bb477
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/L10n.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// @TODO Load the actual strings from webconsole.properties instead.
+class L10n {
+ getStr(str) {
+ switch (str) {
+ case "level.error":
+ return "Error";
+ case "consoleCleared":
+ return "Console was cleared.";
+ case "webConsoleXhrIndicator":
+ return "XHR";
+ case "webConsoleMoreInfoLabel":
+ return "Learn More";
+ }
+ return str;
+ }
+
+ getFormatStr(str) {
+ return this.getStr(str);
+ }
+}
+
+module.exports = L10n;
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper.js b/devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper.js
new file mode 100644
index 000000000..8e6e9428c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper.js
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const LocalizationHelper = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
+
+module.exports = {
+ LocalizationHelper
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient.js b/devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient.js
new file mode 100644
index 000000000..87a058d5c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+class ObjectClient {
+}
+
+module.exports = ObjectClient;
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/PluralForm.js b/devtools/client/webconsole/new-console-output/test/fixtures/PluralForm.js
new file mode 100644
index 000000000..9ab3ad3ec
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/PluralForm.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+module.exports = {
+ PluralForm: {
+ get: function (occurence, str) {
+ // @TODO Remove when loading the actual strings from webconsole.properties
+ // is done in the L10n fixture.
+ if (str === "messageRepeats.tooltip2") {
+ return `${occurence} repeats`;
+ }
+
+ return str;
+ }
+ }
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/Services.js b/devtools/client/webconsole/new-console-output/test/fixtures/Services.js
new file mode 100644
index 000000000..61b3d5e13
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/Services.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PREFS } = require("devtools/client/webconsole/new-console-output/constants");
+
+module.exports = {
+ prefs: {
+ getIntPref: pref => {
+ switch (pref) {
+ case "devtools.hud.loglimit":
+ return 1000;
+ }
+ },
+ getBoolPref: pref => {
+ const falsey = [
+ PREFS.FILTER.NET,
+ PREFS.FILTER.NETXHR,
+ PREFS.UI.FILTER_BAR,
+ ];
+ return !falsey.includes(pref);
+ },
+ setBoolPref: () => {},
+ clearUserPref: () => {},
+ }
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils.js b/devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils.js
new file mode 100644
index 000000000..5ab1c0bb4
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
+
+const Utils = {
+ L10n
+};
+
+module.exports = {
+ Utils
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/moz.build b/devtools/client/webconsole/new-console-output/test/fixtures/moz.build
new file mode 100644
index 000000000..f27fdb849
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/moz.build
@@ -0,0 +1,8 @@
+# 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 += [
+ 'stub-generators',
+ 'stubs'
+]
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js b/devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js
new file mode 100644
index 000000000..04b15c88b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+module.exports = {
+ attachRefToHud: () => {},
+ emitNewMessage: () => {},
+ hudProxyClient: {},
+ onViewSourceInDebugger: () => {},
+ openNetworkPanel: () => {},
+ sourceMapService: {
+ subscribe: () => {},
+ },
+ openLink: () => {},
+ createElement: tagName => document.createElement(tagName)
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini
new file mode 100644
index 000000000..9f348544f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ !/devtools/client/framework/test/shared-head.js
+ test-console-api.html
+ test-network-event.html
+ test-tempfile.js
+
+[browser_webconsole_update_stubs_console_api.js]
+skip-if=true # This is only used to update stubs. It is not an actual test.
+[browser_webconsole_update_stubs_evaluation_result.js]
+skip-if=true # This is only used to update stubs. It is not an actual test.
+[browser_webconsole_update_stubs_network_event.js]
+skip-if=true # This is only used to update stubs. It is not an actual test.
+[browser_webconsole_update_stubs_page_error.js]
+skip-if=true # This is only used to update stubs. It is not an actual test.
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js
new file mode 100644
index 000000000..2013948a9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+requestLongerTimeout(2)
+
+Cu.import("resource://gre/modules/osfile.jsm");
+const { consoleApi: snippets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
+
+let stubs = {
+ preparedMessages: [],
+ packets: [],
+};
+
+add_task(function* () {
+ for (var [key, {keys, code}] of snippets) {
+ yield OS.File.writeAtomic(TEMP_FILE_PATH, `function triggerPacket() {${code}}`);
+
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let {ui} = toolbox.getCurrentPanel().hud;
+
+ ok(ui.jsterm, "jsterm exists");
+ ok(ui.newConsoleOutput, "newConsoleOutput exists");
+
+ let received = new Promise(resolve => {
+ let i = 0;
+ let listener = (type, res) => {
+ stubs.packets.push(formatPacket(keys[i], res));
+ stubs.preparedMessages.push(formatStub(keys[i], res));
+ if(++i === keys.length ){
+ toolbox.target.client.removeListener("consoleAPICall", listener);
+ resolve();
+ }
+ };
+ toolbox.target.client.addListener("consoleAPICall", listener);
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, key, function(key) {
+ var script = content.document.createElement("script");
+ script.src = "test-tempfile.js?key=" + encodeURIComponent(key);
+ script.onload = function() { content.wrappedJSObject.triggerPacket(); }
+ content.document.body.appendChild(script);
+ });
+
+ yield received;
+
+ yield closeTabAndToolbox();
+ }
+ let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "consoleApi.js");
+ OS.File.writeAtomic(filePath, formatFile(stubs));
+ OS.File.writeAtomic(TEMP_FILE_PATH, "");
+});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js
new file mode 100644
index 000000000..082913746
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js
@@ -0,0 +1,31 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm");
+const TEST_URI = "data:text/html;charset=utf-8,stub generation";
+
+const { evaluationResult: snippets} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
+
+let stubs = {
+ preparedMessages: [],
+ packets: [],
+};
+
+add_task(function* () {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ ok(true, "make the test not fail");
+
+ for (var [code,key] of snippets) {
+ const packet = yield new Promise(resolve => {
+ toolbox.target.activeConsole.evaluateJS(code, resolve);
+ });
+ stubs.packets.push(formatPacket(key, packet));
+ stubs.preparedMessages.push(formatStub(key, packet));
+ }
+
+ let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "evaluationResult.js");
+ OS.File.writeAtomic(filePath, formatFile(stubs));
+});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
new file mode 100644
index 000000000..c51a64beb
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
@@ -0,0 +1,46 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm");
+const TARGET = "networkEvent";
+const { [TARGET]: snippets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html";
+
+let stubs = {
+ preparedMessages: [],
+ packets: [],
+};
+
+add_task(function* () {
+ for (var [key, {keys, code}] of snippets) {
+ OS.File.writeAtomic(TEMP_FILE_PATH, `function triggerPacket() {${code}}`);
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let {ui} = toolbox.getCurrentPanel().hud;
+
+ ok(ui.jsterm, "jsterm exists");
+ ok(ui.newConsoleOutput, "newConsoleOutput exists");
+
+ let received = new Promise(resolve => {
+ let i = 0;
+ toolbox.target.client.addListener(TARGET, (type, res) => {
+ stubs.packets.push(formatPacket(keys[i], res));
+ stubs.preparedMessages.push(formatNetworkStub(keys[i], res));
+ if(++i === keys.length ){
+ resolve();
+ }
+ });
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.wrappedJSObject.triggerPacket();
+ });
+
+ yield received;
+ }
+ let filePath = OS.Path.join(`${BASE_PATH}/stubs/${TARGET}.js`);
+ OS.File.writeAtomic(filePath, formatFile(stubs));
+ OS.File.writeAtomic(TEMP_FILE_PATH, "");
+});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js
new file mode 100644
index 000000000..29c9478eb
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm");
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
+
+const { pageError: snippets} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
+
+let stubs = {
+ preparedMessages: [],
+ packets: [],
+};
+
+add_task(function* () {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ ok(true, "make the test not fail");
+
+ for (var [key,code] of snippets) {
+ OS.File.writeAtomic(TEMP_FILE_PATH, `${code}`);
+ let received = new Promise(resolve => {
+ toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
+ toolbox.target.client.removeListener("pageError", onPacket);
+ info("Received page error:" + e + " " + JSON.stringify(packet, null, "\t"));
+
+ let message = prepareMessage(packet, {getNextId: () => 1});
+ stubs.packets.push(formatPacket(message.messageText, packet));
+ stubs.preparedMessages.push(formatStub(message.messageText, packet));
+ resolve();
+ });
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, key, function(key) {
+ var script = content.document.createElement("script");
+ script.src = "test-tempfile.js?key=" + encodeURIComponent(key);
+ content.document.body.appendChild(script);
+ });
+
+ yield received;
+ }
+
+ let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "pageError.js");
+ OS.File.writeAtomic(filePath, formatFile(stubs));
+ OS.File.writeAtomic(TEMP_FILE_PATH, "");
+});
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
new file mode 100644
index 000000000..70d30a76d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -0,0 +1,191 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from ../../../../framework/test/shared-head.js */
+
+"use strict";
+
+// shared-head.js handles imports, constants, and utility functions
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
+ this);
+
+Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
+});
+
+const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js");
+
+const BASE_PATH = "../../../../devtools/client/webconsole/new-console-output/test/fixtures";
+const TEMP_FILE_PATH = OS.Path.join(`${BASE_PATH}/stub-generators`, "test-tempfile.js");
+
+let cachedPackets = {};
+
+function getCleanedPacket(key, packet) {
+ if(Object.keys(cachedPackets).includes(key)) {
+ return cachedPackets[key];
+ }
+
+ // Strip escaped characters.
+ let safeKey = key
+ .replace(/\\n/g, "\n")
+ .replace(/\\r/g, "\r")
+ .replace(/\\\"/g, `\"`)
+ .replace(/\\\'/g, `\'`);
+
+ // If the stub already exist, we want to ignore irrelevant properties
+ // (actor, timeStamp, timer, ...) that might changed and "pollute"
+ // the diff resulting from this stub generation.
+ let res;
+ if(stubPackets.has(safeKey)) {
+
+ let existingPacket = stubPackets.get(safeKey);
+ res = Object.assign({}, packet, {
+ from: existingPacket.from
+ });
+
+ // Clean root timestamp.
+ if(res.timestamp) {
+ res.timestamp = existingPacket.timestamp;
+ }
+
+ if (res.message) {
+ // Clean timeStamp on the message prop.
+ res.message.timeStamp = existingPacket.message.timeStamp;
+ if (res.message.timer) {
+ // Clean timer properties on the message.
+ // Those properties are found on console.time and console.timeEnd calls,
+ // and those time can vary, which is why we need to clean them.
+ if (res.message.timer.started) {
+ res.message.timer.started = existingPacket.message.timer.started;
+ }
+ if (res.message.timer.duration) {
+ res.message.timer.duration = existingPacket.message.timer.duration;
+ }
+ }
+
+ if(Array.isArray(res.message.arguments)) {
+ // Clean actor ids on each message.arguments item.
+ res.message.arguments.forEach((argument, i) => {
+ if (argument && argument.actor) {
+ argument.actor = existingPacket.message.arguments[i].actor;
+ }
+ });
+ }
+ }
+
+ if (res.result) {
+ // Clean actor ids on evaluation result messages.
+ res.result.actor = existingPacket.result.actor;
+ if (res.result.preview) {
+ if(res.result.preview.timestamp) {
+ // Clean timestamp there too.
+ res.result.preview.timestamp = existingPacket.result.preview.timestamp;
+ }
+ }
+ }
+
+ if (res.exception) {
+ // Clean actor ids on exception messages.
+ res.exception.actor = existingPacket.exception.actor;
+ if (res.exception.preview) {
+ if(res.exception.preview.timestamp) {
+ // Clean timestamp there too.
+ res.exception.preview.timestamp = existingPacket.exception.preview.timestamp;
+ }
+ }
+ }
+
+ if (res.eventActor) {
+ // Clean actor ids, timeStamp and startedDateTime on network messages.
+ res.eventActor.actor = existingPacket.eventActor.actor;
+ res.eventActor.startedDateTime = existingPacket.eventActor.startedDateTime;
+ res.eventActor.timeStamp = existingPacket.eventActor.timeStamp;
+ }
+
+ if (res.pageError) {
+ // Clean timeStamp on pageError messages.
+ res.pageError.timeStamp = existingPacket.pageError.timeStamp;
+ }
+
+ } else {
+ res = packet;
+ }
+
+ cachedPackets[key] = res;
+ return res;
+}
+
+function formatPacket(key, packet) {
+ return `
+stubPackets.set("${key}", ${JSON.stringify(getCleanedPacket(key, packet), null, "\t")});
+`;
+}
+
+function formatStub(key, packet) {
+ let prepared = prepareMessage(
+ getCleanedPacket(key, packet),
+ {getNextId: () => "1"}
+ );
+
+ return `
+stubPreparedMessages.set("${key}", new ConsoleMessage(${JSON.stringify(prepared, null, "\t")}));
+`;
+}
+
+function formatNetworkStub(key, packet) {
+ let actor = packet.eventActor;
+ let networkInfo = {
+ _type: "NetworkEvent",
+ timeStamp: actor.timeStamp,
+ node: null,
+ actor: actor.actor,
+ discardRequestBody: true,
+ discardResponseBody: true,
+ startedDateTime: actor.startedDateTime,
+ request: {
+ url: actor.url,
+ method: actor.method,
+ },
+ isXHR: actor.isXHR,
+ cause: actor.cause,
+ response: {},
+ timings: {},
+ // track the list of network event updates
+ updates: [],
+ private: actor.private,
+ fromCache: actor.fromCache,
+ fromServiceWorker: actor.fromServiceWorker
+ };
+ let prepared = prepareMessage(networkInfo, {getNextId: () => "1"});
+ return `
+stubPreparedMessages.set("${key}", new NetworkEventMessage(${JSON.stringify(prepared, null, "\t")}));
+`;
+}
+
+function formatFile(stubs) {
+ return `/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN TESTS IN FIXTURES/ TO UPDATE.
+ */
+
+const { ConsoleMessage, NetworkEventMessage } = require("devtools/client/webconsole/new-console-output/types");
+
+let stubPreparedMessages = new Map();
+let stubPackets = new Map();
+
+${stubs.preparedMessages.join("")}
+${stubs.packets.join("")}
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+}`;
+}
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build
new file mode 100644
index 000000000..ffa6eedec
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build
@@ -0,0 +1,7 @@
+# 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(
+ 'stub-snippets.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
new file mode 100644
index 000000000..f0f01a561
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {DebuggerServer} = require("devtools/server/main");
+var longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
+var initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+// Console API
+
+const consoleApiCommands = [
+ "console.log('foobar', 'test')",
+ "console.log(undefined)",
+ "console.warn('danger, will robinson!')",
+ "console.log(NaN)",
+ "console.log(null)",
+ "console.log('\u9f2c')",
+ "console.clear()",
+ "console.count('bar')",
+ "console.assert(false, {message: 'foobar'})",
+ "console.log('hello \\nfrom \\rthe \\\"string world!')",
+ "console.log('\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165')",
+ "console.dirxml(window)",
+];
+
+let consoleApi = new Map(consoleApiCommands.map(
+ cmd => [cmd, {keys: [cmd], code: cmd}]));
+
+consoleApi.set("console.trace()", {
+ keys: ["console.trace()"],
+ code: `
+function testStacktraceFiltering() {
+ console.trace()
+}
+function foo() {
+ testStacktraceFiltering()
+}
+
+foo()
+`});
+
+consoleApi.set("console.time('bar')", {
+ keys: ["console.time('bar')", "console.timeEnd('bar')"],
+ code: `
+console.time("bar");
+console.timeEnd("bar");
+`});
+
+consoleApi.set("console.table('bar')", {
+ keys: ["console.table('bar')"],
+ code: `
+console.table('bar');
+`});
+
+consoleApi.set("console.table(['a', 'b', 'c'])", {
+ keys: ["console.table(['a', 'b', 'c'])"],
+ code: `
+console.table(['a', 'b', 'c']);
+`});
+
+consoleApi.set("console.group('bar')", {
+ keys: ["console.group('bar')", "console.groupEnd('bar')"],
+ code: `
+console.group("bar");
+console.groupEnd("bar");
+`});
+
+consoleApi.set("console.groupCollapsed('foo')", {
+ keys: ["console.groupCollapsed('foo')", "console.groupEnd('foo')"],
+ code: `
+console.groupCollapsed("foo");
+console.groupEnd("foo");
+`});
+
+consoleApi.set("console.group()", {
+ keys: ["console.group()", "console.groupEnd()"],
+ code: `
+console.group();
+console.groupEnd();
+`});
+
+consoleApi.set("console.log(%cfoobar)", {
+ keys: ["console.log(%cfoobar)"],
+ code: `
+console.log(
+ "%cfoo%cbar",
+ "color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px",
+ "color:red;background:\\165rl('http://example.com/test')");
+`});
+
+// Evaluation Result
+const evaluationResultCommands = [
+ "new Date(0)",
+ "asdf()",
+ "1 + @"
+];
+
+let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));
+
+// Network Event
+
+let networkEvent = new Map();
+
+networkEvent.set("GET request", {
+ keys: ["GET request"],
+ code: `
+let i = document.createElement("img");
+i.src = "inexistent.html";
+`});
+
+networkEvent.set("XHR GET request", {
+ keys: ["XHR GET request"],
+ code: `
+const xhr = new XMLHttpRequest();
+xhr.open("GET", "inexistent.html");
+xhr.send();
+`});
+
+networkEvent.set("XHR POST request", {
+ keys: ["XHR POST request"],
+ code: `
+const xhr = new XMLHttpRequest();
+xhr.open("POST", "inexistent.html");
+xhr.send();
+`});
+
+// Page Error
+
+let pageError = new Map();
+
+pageError.set("Reference Error", `
+ function bar() {
+ asdf()
+ }
+ function foo() {
+ bar()
+ }
+
+ foo()
+`);
+
+pageError.set("Redeclaration Error", `
+ let a, a;
+`);
+
+module.exports = {
+ consoleApi,
+ evaluationResult,
+ networkEvent,
+ pageError,
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html
new file mode 100644
index 000000000..3246cff15
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Stub generator</title>
+ </head>
+ <body>
+ <p>Stub generator</p>
+ <script src="test-tempfile.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html
new file mode 100644
index 000000000..c234acea6
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Stub generator for network event</title>
+ </head>
+ <body>
+ <p>Stub generator for network event</p>
+ <script src="test-tempfile.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
new file mode 100644
index 000000000..bec3abd70
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -0,0 +1,1477 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN TESTS IN FIXTURES/ TO UPDATE.
+ */
+
+const { ConsoleMessage, NetworkEventMessage } = require("devtools/client/webconsole/new-console-output/types");
+
+let stubPreparedMessages = new Map();
+let stubPackets = new Map();
+stubPreparedMessages.set("console.log('foobar', 'test')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "foobar",
+ "test"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"foobar\",\"test\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27foobar%27%2C%20%27test%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[]}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27foobar%27%2C%20%27test%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log(undefined)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "undefined"
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"undefined\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(undefined)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(undefined)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.warn('danger, will robinson!')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "warn",
+ "level": "warn",
+ "messageText": null,
+ "parameters": [
+ "danger, will robinson!"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"warn\",\"level\":\"warn\",\"messageText\":null,\"parameters\":[\"danger, will robinson!\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.warn(%27danger%2C%20will%20robinson!%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.warn(%27danger%2C%20will%20robinson!%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log(NaN)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "NaN"
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"NaN\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(NaN)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(NaN)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log(null)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "null"
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"null\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(null)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(null)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log('鼬')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "鼬"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"鼬\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%E9%BC%AC%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%E9%BC%AC%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.clear()", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "clear",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "Console was cleared."
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"clear\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"Console was cleared.\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.clear()\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.clear()",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.count('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "debug",
+ "messageText": "bar: 1",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"debug\",\"messageText\":\"bar: 1\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.count(%27bar%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.count(%27bar%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.assert(false, {message: 'foobar'})", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "assert",
+ "level": "error",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "object",
+ "actor": "server1.conn8.child1/obj31",
+ "class": "Object",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 1,
+ "preview": {
+ "kind": "Object",
+ "ownProperties": {
+ "message": {
+ "configurable": true,
+ "enumerable": true,
+ "writable": true,
+ "value": "foobar"
+ }
+ },
+ "ownPropertiesLength": 1,
+ "safeGetterValues": {}
+ }
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"assert\",\"level\":\"error\",\"messageText\":null,\"parameters\":[{\"type\":\"object\",\"actor\":\"server1.conn8.child1/obj31\",\"class\":\"Object\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":1,\"preview\":{\"kind\":\"Object\",\"ownProperties\":{\"message\":{\"configurable\":true,\"enumerable\":true,\"writable\":true,\"value\":\"foobar\"}},\"ownPropertiesLength\":1,\"safeGetterValues\":{}}}],\"repeatId\":null,\"stacktrace\":[{\"columnNumber\":27,\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)\",\"functionName\":\"triggerPacket\",\"language\":2,\"lineNumber\":1}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": [
+ {
+ "columnNumber": 27,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)",
+ "functionName": "triggerPacket",
+ "language": 2,
+ "lineNumber": 1
+ }
+ ],
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log('hello \nfrom \rthe \"string world!')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "hello \nfrom \rthe \"string world!"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"hello \\nfrom \\rthe \\\"string world!\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27hello%20%5Cnfrom%20%5Crthe%20%5C%22string%20world!%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27hello%20%5Cnfrom%20%5Crthe%20%5C%22string%20world!%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log('úṇĩçödê țĕșť')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "úṇĩçödê țĕșť"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"úṇĩçödê țĕșť\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%C3%BA%E1%B9%87%C4%A9%C3%A7%C3%B6d%C3%AA%20%C8%9B%C4%95%C8%99%C5%A5%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%C3%BA%E1%B9%87%C4%A9%C3%A7%C3%B6d%C3%AA%20%C8%9B%C4%95%C8%99%C5%A5%27)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.dirxml(window)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "object",
+ "actor": "server1.conn11.child1/obj31",
+ "class": "Window",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 815,
+ "preview": {
+ "kind": "ObjectWithURL",
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html"
+ }
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"object\",\"actor\":\"server1.conn11.child1/obj31\",\"class\":\"Window\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":804,\"preview\":{\"kind\":\"ObjectWithURL\",\"url\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\"}}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.dirxml(window)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.dirxml(window)",
+ "line": 1,
+ "column": 27
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.trace()", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "trace",
+ "level": "log",
+ "messageText": null,
+ "parameters": [],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"trace\",\"level\":\"log\",\"messageText\":null,\"parameters\":[],\"repeatId\":null,\"stacktrace\":[{\"columnNumber\":3,\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()\",\"functionName\":\"testStacktraceFiltering\",\"language\":2,\"lineNumber\":3},{\"columnNumber\":3,\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()\",\"functionName\":\"foo\",\"language\":2,\"lineNumber\":6},{\"columnNumber\":1,\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()\",\"functionName\":\"triggerPacket\",\"language\":2,\"lineNumber\":9}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()\",\"line\":3,\"column\":3},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": [
+ {
+ "columnNumber": 3,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "testStacktraceFiltering",
+ "language": 2,
+ "lineNumber": 3
+ },
+ {
+ "columnNumber": 3,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "foo",
+ "language": 2,
+ "lineNumber": 6
+ },
+ {
+ "columnNumber": 1,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "triggerPacket",
+ "language": 2,
+ "lineNumber": 9
+ }
+ ],
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "line": 3,
+ "column": 3
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.time('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "nullMessage",
+ "level": "log",
+ "messageText": null,
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"nullMessage\",\"level\":\"log\",\"messageText\":null,\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.timeEnd('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "timeEnd",
+ "level": "log",
+ "messageText": "bar: 1.36ms",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"timeEnd\",\"level\":\"log\",\"messageText\":\"bar: 1.36ms\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)\",\"line\":3,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)",
+ "line": 3,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.table('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "bar"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"bar\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%27bar%27)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%27bar%27)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.table(['a', 'b', 'c'])", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "table",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ {
+ "type": "object",
+ "actor": "server1.conn15.child1/obj31",
+ "class": "Array",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 4,
+ "preview": {
+ "kind": "ArrayLike",
+ "length": 3,
+ "items": [
+ "a",
+ "b",
+ "c"
+ ]
+ }
+ }
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"table\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"object\",\"actor\":\"server1.conn15.child1/obj31\",\"class\":\"Array\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":4,\"preview\":{\"kind\":\"ArrayLike\",\"length\":3,\"items\":[\"a\",\"b\",\"c\"]}}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%5B%27a%27%2C%20%27b%27%2C%20%27c%27%5D)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%5B%27a%27%2C%20%27b%27%2C%20%27c%27%5D)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.group('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "startGroup",
+ "level": "log",
+ "messageText": "bar",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"startGroup\",\"level\":\"log\",\"messageText\":\"bar\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.groupEnd('bar')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "endGroup",
+ "level": "log",
+ "messageText": null,
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"endGroup\",\"level\":\"log\",\"messageText\":null,\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)\",\"line\":3,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)",
+ "line": 3,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.groupCollapsed('foo')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "startGroupCollapsed",
+ "level": "log",
+ "messageText": "foo",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"startGroupCollapsed\",\"level\":\"log\",\"messageText\":\"foo\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.groupEnd('foo')", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "endGroup",
+ "level": "log",
+ "messageText": null,
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"endGroup\",\"level\":\"log\",\"messageText\":null,\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)\",\"line\":3,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)",
+ "line": 3,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.group()", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "startGroup",
+ "level": "log",
+ "messageText": "<no group label>",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"startGroup\",\"level\":\"log\",\"messageText\":\"<no group label>\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.groupEnd()", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "endGroup",
+ "level": "log",
+ "messageText": null,
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"endGroup\",\"level\":\"log\",\"messageText\":null,\"parameters\":null,\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()\",\"line\":3,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()",
+ "line": 3,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [],
+ "notes": null
+}));
+
+stubPreparedMessages.set("console.log(%cfoobar)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "console-api",
+ "type": "log",
+ "level": "log",
+ "messageText": null,
+ "parameters": [
+ "foo",
+ "bar"
+ ],
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"foo\",\"bar\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%25cfoobar)\",\"line\":2,\"column\":1},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[\"color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px\",\"color:red;background:url('http://example.com/test')\"],\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%25cfoobar)",
+ "line": 2,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": null,
+ "userProvidedStyles": [
+ "color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px",
+ "color:red;background:url('http://example.com/test')"
+ ],
+ "notes": null
+}));
+
+stubPackets.set("console.log('foobar', 'test')", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "foobar",
+ "test"
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27foobar%27%2C%20%27test%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086261590,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log(undefined)", {
+ "from": "server1.conn1.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "undefined"
+ }
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(undefined)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086264886,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.warn('danger, will robinson!')", {
+ "from": "server1.conn2.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "danger, will robinson!"
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.warn(%27danger%2C%20will%20robinson!%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "warn",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086267284,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log(NaN)", {
+ "from": "server1.conn3.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "NaN"
+ }
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(NaN)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086269484,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log(null)", {
+ "from": "server1.conn4.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "null"
+ }
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(null)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086271418,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log('鼬')", {
+ "from": "server1.conn5.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "鼬"
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%E9%BC%AC%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086273549,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.clear()", {
+ "from": "server1.conn6.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.clear()",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "clear",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086275587,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.count('bar')", {
+ "from": "server1.conn7.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 27,
+ "counter": {
+ "count": 1,
+ "label": "bar"
+ },
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.count(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "count",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086277812,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.assert(false, {message: 'foobar'})", {
+ "from": "server1.conn8.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "object",
+ "actor": "server1.conn8.child1/obj31",
+ "class": "Object",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 1,
+ "preview": {
+ "kind": "Object",
+ "ownProperties": {
+ "message": {
+ "configurable": true,
+ "enumerable": true,
+ "writable": true,
+ "value": "foobar"
+ }
+ },
+ "ownPropertiesLength": 1,
+ "safeGetterValues": {}
+ }
+ }
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "assert",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086280131,
+ "timer": null,
+ "stacktrace": [
+ {
+ "columnNumber": 27,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.assert(false%2C%20%7Bmessage%3A%20%27foobar%27%7D)",
+ "functionName": "triggerPacket",
+ "language": 2,
+ "lineNumber": 1
+ }
+ ],
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log('hello \nfrom \rthe \"string world!')", {
+ "from": "server1.conn9.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "hello \nfrom \rthe \"string world!"
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27hello%20%5Cnfrom%20%5Crthe%20%5C%22string%20world!%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086281936,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log('úṇĩçödê țĕșť')", {
+ "from": "server1.conn10.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "úṇĩçödê țĕșť"
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27%C3%BA%E1%B9%87%C4%A9%C3%A7%C3%B6d%C3%AA%20%C8%9B%C4%95%C8%99%C5%A5%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [],
+ "timeStamp": 1477086283713,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.dirxml(window)", {
+ "from": "server1.conn11.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "object",
+ "actor": "server1.conn11.child1/obj31",
+ "class": "Window",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 815,
+ "preview": {
+ "kind": "ObjectWithURL",
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html"
+ }
+ }
+ ],
+ "columnNumber": 27,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.dirxml(window)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "dirxml",
+ "lineNumber": 1,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086285483,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.trace()", {
+ "from": "server1.conn12.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [],
+ "columnNumber": 3,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "testStacktraceFiltering",
+ "groupName": "",
+ "level": "trace",
+ "lineNumber": 3,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086287286,
+ "timer": null,
+ "stacktrace": [
+ {
+ "columnNumber": 3,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "testStacktraceFiltering",
+ "language": 2,
+ "lineNumber": 3
+ },
+ {
+ "columnNumber": 3,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "foo",
+ "language": 2,
+ "lineNumber": 6
+ },
+ {
+ "columnNumber": 1,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.trace()",
+ "functionName": "triggerPacket",
+ "language": 2,
+ "lineNumber": 9
+ }
+ ],
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.time('bar')", {
+ "from": "server1.conn13.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "time",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086289137,
+ "timer": {
+ "name": "bar",
+ "started": 1166.305
+ },
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.timeEnd('bar')", {
+ "from": "server1.conn13.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.time(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "timeEnd",
+ "lineNumber": 3,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086289138,
+ "timer": {
+ "duration": 1.3550000000000182,
+ "name": "bar"
+ },
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.table('bar')", {
+ "from": "server1.conn14.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "table",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086290984,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.table(['a', 'b', 'c'])", {
+ "from": "server1.conn15.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ {
+ "type": "object",
+ "actor": "server1.conn15.child1/obj31",
+ "class": "Array",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 4,
+ "preview": {
+ "kind": "ArrayLike",
+ "length": 3,
+ "items": [
+ "a",
+ "b",
+ "c"
+ ]
+ }
+ }
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.table(%5B%27a%27%2C%20%27b%27%2C%20%27c%27%5D)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "table",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086292762,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.group('bar')", {
+ "from": "server1.conn16.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "bar",
+ "level": "group",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086294628,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.groupEnd('bar')", {
+ "from": "server1.conn16.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group(%27bar%27)",
+ "functionName": "triggerPacket",
+ "groupName": "bar",
+ "level": "groupEnd",
+ "lineNumber": 3,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086294630,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.groupCollapsed('foo')", {
+ "from": "server1.conn17.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "foo"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)",
+ "functionName": "triggerPacket",
+ "groupName": "foo",
+ "level": "groupCollapsed",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086296567,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.groupEnd('foo')", {
+ "from": "server1.conn17.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "foo"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.groupCollapsed(%27foo%27)",
+ "functionName": "triggerPacket",
+ "groupName": "foo",
+ "level": "groupEnd",
+ "lineNumber": 3,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086296570,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.group()", {
+ "from": "server1.conn18.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "group",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086298462,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.groupEnd()", {
+ "from": "server1.conn18.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.group()",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "groupEnd",
+ "lineNumber": 3,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "timeStamp": 1477086298464,
+ "timer": null,
+ "workerType": "none",
+ "styles": [],
+ "category": "webdev"
+ }
+});
+
+stubPackets.set("console.log(%cfoobar)", {
+ "from": "server1.conn19.child1/consoleActor2",
+ "type": "consoleAPICall",
+ "message": {
+ "arguments": [
+ "foo",
+ "bar"
+ ],
+ "columnNumber": 1,
+ "counter": null,
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%25cfoobar)",
+ "functionName": "triggerPacket",
+ "groupName": "",
+ "level": "log",
+ "lineNumber": 2,
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "firstPartyDomain": "",
+ "inIsolatedMozBrowser": false,
+ "privateBrowsingId": 0
+ },
+ "private": false,
+ "styles": [
+ "color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px",
+ "color:red;background:url('http://example.com/test')"
+ ],
+ "timeStamp": 1477086300265,
+ "timer": null,
+ "workerType": "none",
+ "category": "webdev"
+ }
+});
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
new file mode 100644
index 000000000..0682d9134
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
@@ -0,0 +1,184 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN TESTS IN FIXTURES/ TO UPDATE.
+ */
+
+const { ConsoleMessage, NetworkEventMessage } = require("devtools/client/webconsole/new-console-output/types");
+
+let stubPreparedMessages = new Map();
+let stubPackets = new Map();
+stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "javascript",
+ "type": "result",
+ "level": "log",
+ "parameters": {
+ "type": "object",
+ "actor": "server1.conn0.child1/obj30",
+ "class": "Date",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 0,
+ "preview": {
+ "timestamp": 0
+ }
+ },
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"log\",\"parameters\":{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj30\",\"class\":\"Date\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"timestamp\":0}},\"repeatId\":null,\"stacktrace\":null,\"frame\":null,\"groupId\":null,\"userProvidedStyles\":null,\"notes\":null}",
+ "stacktrace": null,
+ "frame": null,
+ "groupId": null,
+ "userProvidedStyles": null,
+ "notes": null
+}));
+
+stubPreparedMessages.set("asdf()", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "javascript",
+ "type": "result",
+ "level": "error",
+ "messageText": "ReferenceError: asdf is not defined",
+ "parameters": {
+ "type": "undefined"
+ },
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"error\",\"messageText\":\"ReferenceError: asdf is not defined\",\"parameters\":{\"type\":\"undefined\"},\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"debugger eval code\",\"line\":1,\"column\":1},\"groupId\":null,\"exceptionDocURL\":\"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default\",\"userProvidedStyles\":null,\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "debugger eval code",
+ "line": 1,
+ "column": 1
+ },
+ "groupId": null,
+ "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
+ "userProvidedStyles": null,
+ "notes": null
+}));
+
+stubPreparedMessages.set("1 + @", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "javascript",
+ "type": "result",
+ "level": "error",
+ "messageText": "SyntaxError: illegal character",
+ "parameters": {
+ "type": "undefined"
+ },
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"result\",\"level\":\"error\",\"messageText\":\"SyntaxError: illegal character\",\"parameters\":{\"type\":\"undefined\"},\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"debugger eval code\",\"line\":1,\"column\":4},\"groupId\":null,\"userProvidedStyles\":null,\"notes\":null}",
+ "stacktrace": null,
+ "frame": {
+ "source": "debugger eval code",
+ "line": 1,
+ "column": 4
+ },
+ "groupId": null,
+ "userProvidedStyles": null,
+ "notes": null
+}));
+
+stubPackets.set("new Date(0)", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "input": "new Date(0)",
+ "result": {
+ "type": "object",
+ "actor": "server1.conn0.child1/obj30",
+ "class": "Date",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 0,
+ "preview": {
+ "timestamp": 0
+ }
+ },
+ "timestamp": 1476573073424,
+ "exception": null,
+ "frame": null,
+ "helperResult": null,
+ "notes": null
+});
+
+stubPackets.set("asdf()", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "input": "asdf()",
+ "result": {
+ "type": "undefined"
+ },
+ "timestamp": 1476573073442,
+ "exception": {
+ "type": "object",
+ "actor": "server1.conn0.child1/obj32",
+ "class": "Error",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 4,
+ "preview": {
+ "kind": "Error",
+ "name": "ReferenceError",
+ "message": "asdf is not defined",
+ "stack": "@debugger eval code:1:1\n",
+ "fileName": "debugger eval code",
+ "lineNumber": 1,
+ "columnNumber": 1
+ }
+ },
+ "exceptionMessage": "ReferenceError: asdf is not defined",
+ "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
+ "frame": {
+ "source": "debugger eval code",
+ "line": 1,
+ "column": 1
+ },
+ "helperResult": null,
+ "notes": null
+});
+
+stubPackets.set("1 + @", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "input": "1 + @",
+ "result": {
+ "type": "undefined"
+ },
+ "timestamp": 1478755616654,
+ "exception": {
+ "type": "object",
+ "actor": "server1.conn0.child1/obj33",
+ "class": "Error",
+ "extensible": true,
+ "frozen": false,
+ "sealed": false,
+ "ownPropertyLength": 4,
+ "preview": {
+ "kind": "Error",
+ "name": "SyntaxError",
+ "message": "illegal character",
+ "stack": "",
+ "fileName": "debugger eval code",
+ "lineNumber": 1,
+ "columnNumber": 4
+ }
+ },
+ "exceptionMessage": "SyntaxError: illegal character",
+ "frame": {
+ "source": "debugger eval code",
+ "line": 1,
+ "column": 4
+ },
+ "helperResult": null,
+ "notes": null
+});
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js
new file mode 100644
index 000000000..59b420180
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let maps = [];
+
+[
+ "consoleApi",
+ "evaluationResult",
+ "networkEvent",
+ "pageError",
+].forEach((filename) => {
+ maps[filename] = require(`./${filename}`);
+});
+
+// Combine all the maps into a single map.
+module.exports = {
+ stubPreparedMessages: new Map([
+ ...maps.consoleApi.stubPreparedMessages,
+ ...maps.evaluationResult.stubPreparedMessages,
+ ...maps.networkEvent.stubPreparedMessages,
+ ...maps.pageError.stubPreparedMessages, ]),
+ stubPackets: new Map([
+ ...maps.consoleApi.stubPackets,
+ ...maps.evaluationResult.stubPackets,
+ ...maps.networkEvent.stubPackets,
+ ...maps.pageError.stubPackets, ]),
+};
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/moz.build b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/moz.build
new file mode 100644
index 000000000..88e9c46df
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/moz.build
@@ -0,0 +1,11 @@
+# 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(
+ 'consoleApi.js',
+ 'evaluationResult.js',
+ 'index.js',
+ 'networkEvent.js',
+ 'pageError.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
new file mode 100644
index 000000000..58a40d30b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/networkEvent.js
@@ -0,0 +1,189 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN TESTS IN FIXTURES/ TO UPDATE.
+ */
+
+const { ConsoleMessage, NetworkEventMessage } = require("devtools/client/webconsole/new-console-output/types");
+
+let stubPreparedMessages = new Map();
+let stubPackets = new Map();
+
+
+stubPreparedMessages.set("GET request", new NetworkEventMessage({
+ "id": "1",
+ "actor": "server1.conn0.child1/netEvent29",
+ "level": "log",
+ "isXHR": false,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET"
+ },
+ "response": {},
+ "source": "network",
+ "type": "log",
+ "groupId": null
+}));
+
+stubPreparedMessages.set("XHR GET request", new NetworkEventMessage({
+ "id": "1",
+ "actor": "server1.conn1.child1/netEvent29",
+ "level": "log",
+ "isXHR": true,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET"
+ },
+ "response": {},
+ "source": "network",
+ "type": "log",
+ "groupId": null
+}));
+
+stubPreparedMessages.set("XHR POST request", new NetworkEventMessage({
+ "id": "1",
+ "actor": "server1.conn2.child1/netEvent29",
+ "level": "log",
+ "isXHR": true,
+ "request": {
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "POST"
+ },
+ "response": {},
+ "source": "network",
+ "type": "log",
+ "groupId": null
+}));
+
+
+stubPackets.set("GET request", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "type": "networkEvent",
+ "eventActor": {
+ "actor": "server1.conn0.child1/netEvent29",
+ "startedDateTime": "2016-10-15T23:12:04.196Z",
+ "timeStamp": 1476573124196,
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET",
+ "isXHR": false,
+ "cause": {
+ "type": 3,
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 3,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "private": false
+ }
+});
+
+stubPackets.set("XHR GET request", {
+ "from": "server1.conn1.child1/consoleActor2",
+ "type": "networkEvent",
+ "eventActor": {
+ "actor": "server1.conn1.child1/netEvent29",
+ "startedDateTime": "2016-10-15T23:12:05.690Z",
+ "timeStamp": 1476573125690,
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "GET",
+ "isXHR": true,
+ "cause": {
+ "type": 11,
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 4,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "private": false
+ }
+});
+
+stubPackets.set("XHR POST request", {
+ "from": "server1.conn2.child1/consoleActor2",
+ "type": "networkEvent",
+ "eventActor": {
+ "actor": "server1.conn2.child1/netEvent29",
+ "startedDateTime": "2016-10-15T23:12:07.158Z",
+ "timeStamp": 1476573127158,
+ "url": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/inexistent.html",
+ "method": "POST",
+ "isXHR": true,
+ "cause": {
+ "type": 11,
+ "loadingDocumentUri": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js",
+ "lineNumber": 4,
+ "columnNumber": 1,
+ "functionName": "triggerPacket",
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 4,
+ "columnNumber": 7,
+ "functionName": null,
+ "asyncCause": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null,
+ "asyncCause": null
+ }
+ ]
+ },
+ "private": false
+ }
+});
+
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+} \ No newline at end of file
diff --git a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
new file mode 100644
index 000000000..80147e7dd
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
@@ -0,0 +1,188 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN TESTS IN FIXTURES/ TO UPDATE.
+ */
+
+const { ConsoleMessage, NetworkEventMessage } = require("devtools/client/webconsole/new-console-output/types");
+
+let stubPreparedMessages = new Map();
+let stubPackets = new Map();
+stubPreparedMessages.set("ReferenceError: asdf is not defined", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "javascript",
+ "type": "log",
+ "level": "error",
+ "messageText": "ReferenceError: asdf is not defined",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"log\",\"level\":\"error\",\"messageText\":\"ReferenceError: asdf is not defined\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error\",\"lineNumber\":3,\"columnNumber\":5,\"functionName\":\"bar\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error\",\"lineNumber\":6,\"columnNumber\":5,\"functionName\":\"foo\"},{\"filename\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error\",\"lineNumber\":9,\"columnNumber\":3,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error\",\"line\":3,\"column\":5},\"groupId\":null,\"exceptionDocURL\":\"https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default\",\"userProvidedStyles\":null,\"notes\":null}",
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 3,
+ "columnNumber": 5,
+ "functionName": "bar"
+ },
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 6,
+ "columnNumber": 5,
+ "functionName": "foo"
+ },
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 9,
+ "columnNumber": 3,
+ "functionName": null
+ }
+ ],
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "line": 3,
+ "column": 5
+ },
+ "groupId": null,
+ "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
+ "userProvidedStyles": null,
+ "notes": null
+}));
+
+stubPreparedMessages.set("SyntaxError: redeclaration of let a", new ConsoleMessage({
+ "id": "1",
+ "allowRepeating": true,
+ "source": "javascript",
+ "type": "log",
+ "level": "error",
+ "messageText": "SyntaxError: redeclaration of let a",
+ "parameters": null,
+ "repeat": 1,
+ "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"javascript\",\"type\":\"log\",\"level\":\"error\",\"messageText\":\"SyntaxError: redeclaration of let a\",\"parameters\":null,\"repeatId\":null,\"stacktrace\":[{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval\",\"lineNumber\":6,\"columnNumber\":9,\"functionName\":null},{\"filename\":\"chrome://mochikit/content/tests/BrowserTestUtils/content-task.js\",\"lineNumber\":53,\"columnNumber\":20,\"functionName\":null}],\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":9},\"groupId\":null,\"userProvidedStyles\":null,\"notes\":[{\"messageBody\":\"Previously declared at line 2, column 6\",\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":6}}]}",
+ "stacktrace": [
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 6,
+ "columnNumber": 9,
+ "functionName": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null
+ }
+ ],
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+ "line": 2,
+ "column": 9
+ },
+ "groupId": null,
+ "userProvidedStyles": null,
+ "notes": [
+ {
+ "messageBody": "Previously declared at line 2, column 6",
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+ "line": 2,
+ "column": 6
+ }
+ }
+ ]
+}));
+
+stubPackets.set("ReferenceError: asdf is not defined", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "type": "pageError",
+ "pageError": {
+ "errorMessage": "ReferenceError: asdf is not defined",
+ "errorMessageName": "JSMSG_NOT_DEFINED",
+ "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_defined?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
+ "sourceName": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineText": "",
+ "lineNumber": 3,
+ "columnNumber": 5,
+ "category": "content javascript",
+ "timeStamp": 1476573167137,
+ "warning": false,
+ "error": false,
+ "exception": true,
+ "strict": false,
+ "info": false,
+ "private": false,
+ "stacktrace": [
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 3,
+ "columnNumber": 5,
+ "functionName": "bar"
+ },
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 6,
+ "columnNumber": 5,
+ "functionName": "foo"
+ },
+ {
+ "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=Reference%20Error",
+ "lineNumber": 9,
+ "columnNumber": 3,
+ "functionName": null
+ }
+ ],
+ "notes": null
+ }
+});
+
+stubPackets.set("SyntaxError: redeclaration of let a", {
+ "from": "server1.conn0.child1/consoleActor2",
+ "type": "pageError",
+ "pageError": {
+ "errorMessage": "SyntaxError: redeclaration of let a",
+ "errorMessageName": "JSMSG_REDECLARED_VAR",
+ "sourceName": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+ "lineText": " let a, a;\n",
+ "lineNumber": 2,
+ "columnNumber": 9,
+ "category": "content javascript",
+ "warning": false,
+ "error": false,
+ "exception": true,
+ "strict": false,
+ "info": false,
+ "private": false,
+ "stacktrace": [
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js line 52 > eval",
+ "lineNumber": 6,
+ "columnNumber": 9,
+ "functionName": null
+ },
+ {
+ "filename": "chrome://mochikit/content/tests/BrowserTestUtils/content-task.js",
+ "lineNumber": 53,
+ "columnNumber": 20,
+ "functionName": null
+ }
+ ],
+ "notes": [
+ {
+ "messageBody": "Previously declared at line 2, column 6",
+ "frame": {
+ "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+ "line": 2,
+ "column": 6
+ }
+ }
+ ]
+ }
+});
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+};
diff --git a/devtools/client/webconsole/new-console-output/test/helpers.js b/devtools/client/webconsole/new-console-output/test/helpers.js
new file mode 100644
index 000000000..39807eaed
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/helpers.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let ReactDOM = require("devtools/client/shared/vendor/react-dom");
+let React = require("devtools/client/shared/vendor/react");
+var TestUtils = React.addons.TestUtils;
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const { configureStore } = require("devtools/client/webconsole/new-console-output/store");
+const { IdGenerator } = require("devtools/client/webconsole/new-console-output/utils/id-generator");
+const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+
+/**
+ * Prepare actions for use in testing.
+ */
+function setupActions() {
+ // Some actions use dependency injection. This helps them avoid using state in
+ // a hard-to-test way. We need to inject stubbed versions of these dependencies.
+ const wrappedActions = Object.assign({}, actions);
+
+ const idGenerator = new IdGenerator();
+ wrappedActions.messageAdd = (packet) => {
+ return actions.messageAdd(packet, idGenerator);
+ };
+
+ return wrappedActions;
+}
+
+/**
+ * Prepare the store for use in testing.
+ */
+function setupStore(input) {
+ const store = configureStore();
+
+ // Add the messages from the input commands to the store.
+ input.forEach((cmd) => {
+ store.dispatch(actions.messageAdd(stubPackets.get(cmd)));
+ });
+
+ return store;
+}
+
+function renderComponent(component, props) {
+ const el = React.createElement(component, props, {});
+ // By default, renderIntoDocument() won't work for stateless components, but
+ // it will work if the stateless component is wrapped in a stateful one.
+ // See https://github.com/facebook/react/issues/4839
+ const wrappedEl = React.DOM.span({}, [el]);
+ const renderedComponent = TestUtils.renderIntoDocument(wrappedEl);
+ return ReactDOM.findDOMNode(renderedComponent).children[0];
+}
+
+function shallowRenderComponent(component, props) {
+ const el = React.createElement(component, props);
+ const renderer = TestUtils.createRenderer();
+ renderer.render(el, {});
+ return renderer.getRenderOutput();
+}
+
+module.exports = {
+ setupActions,
+ setupStore,
+ renderComponent,
+ shallowRenderComponent
+};
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
new file mode 100644
index 000000000..9881d0559
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ test-batching.html
+ test-console.html
+ test-console-filters.html
+ test-console-group.html
+ test-console-table.html
+ !/devtools/client/framework/test/shared-head.js
+
+[browser_webconsole_batching.js]
+[browser_webconsole_console_group.js]
+[browser_webconsole_console_table.js]
+[browser_webconsole_filters.js]
+[browser_webconsole_init.js]
+[browser_webconsole_input_focus.js]
+[browser_webconsole_keyboard_accessibility.js]
+[browser_webconsole_observer_notifications.js]
+[browser_webconsole_vview_close_on_esc_key.js]
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
new file mode 100644
index 000000000..d95707238
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check adding console calls as batch keep the order of the message.
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html";
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+add_task(function* () {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ const messageNumber = 100;
+ yield testSimpleBatchLogging(hud, messageNumber);
+ yield testBatchLoggingAndClear(hud, messageNumber);
+});
+
+function* testSimpleBatchLogging(hud, messageNumber) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
+ function (numMessages) {
+ content.wrappedJSObject.batchLog(numMessages);
+ }
+ );
+
+ for (let i = 0; i < messageNumber; i++) {
+ let node = yield waitFor(() => findMessageAtIndex(hud, i, i));
+ is(node.textContent, i.toString(), `message at index "${i}" is the expected one`);
+ }
+}
+
+function* testBatchLoggingAndClear(hud, messageNumber) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, messageNumber,
+ function (numMessages) {
+ content.wrappedJSObject.batchLogAndClear(numMessages);
+ }
+ );
+ yield waitFor(() => findMessage(hud, l10n.getStr("consoleCleared")));
+ ok(true, "console cleared message is displayed");
+
+ // Passing the text argument as an empty string will returns all the message,
+ // whatever their content is.
+ const messages = findMessages(hud, "");
+ is(messages.length, 1, "console was cleared as expected");
+}
+
+function findMessageAtIndex(hud, text, index) {
+ const selector = `.message:nth-of-type(${index + 1}) .message-body`;
+ return findMessage(hud, text, selector);
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
new file mode 100644
index 000000000..11688c5c3
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
@@ -0,0 +1,90 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check console.group, console.groupCollapsed and console.groupEnd calls
+// behave as expected.
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html";
+const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
+
+add_task(function* () {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let hud = toolbox.getCurrentPanel().hud;
+
+ const store = hud.ui.newConsoleOutput.getStore();
+ // Adding loggin each time the store is modified in order to check
+ // the store state in case of failure.
+ store.subscribe(() => {
+ const messages = store.getState().messages.messagesById.toJS()
+ .map(message => {
+ return {
+ id: message.id,
+ type: message.type,
+ parameters: message.parameters,
+ messageText: message.messageText
+ };
+ }
+ );
+ info("messages : " + JSON.stringify(messages));
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
+ content.wrappedJSObject.doLog();
+ });
+
+ info("Test a group at root level");
+ let node = yield waitFor(() => findMessage(hud, "group-1"));
+ testClass(node, "startGroup");
+ testIndent(node, 0);
+
+ info("Test a message in a 1 level deep group");
+ node = yield waitFor(() => findMessage(hud, "log-1"));
+ testClass(node, "log");
+ testIndent(node, 1);
+
+ info("Test a group in a 1 level deep group");
+ node = yield waitFor(() => findMessage(hud, "group-2"));
+ testClass(node, "startGroup");
+ testIndent(node, 1);
+
+ info("Test a message in a 2 level deep group");
+ node = yield waitFor(() => findMessage(hud, "log-2"));
+ testClass(node, "log");
+ testIndent(node, 2);
+
+ info("Test a message in a 1 level deep group, after closing a 2 level deep group");
+ node = yield waitFor(() => findMessage(hud, "log-3"));
+ testClass(node, "log");
+ testIndent(node, 1);
+
+ info("Test a message at root level, after closing all the groups");
+ node = yield waitFor(() => findMessage(hud, "log-4"));
+ testClass(node, "log");
+ testIndent(node, 0);
+
+ info("Test a collapsed group at root level");
+ node = yield waitFor(() => findMessage(hud, "group-3"));
+ testClass(node, "startGroupCollapsed");
+ testIndent(node, 0);
+
+ info("Test a message at root level, after closing a collapsed group");
+ node = yield waitFor(() => findMessage(hud, "log-6"));
+ testClass(node, "log");
+ testIndent(node, 0);
+
+ let nodes = hud.ui.experimentalOutputNode.querySelectorAll(".message");
+ is(nodes.length, 8, "expected number of messages are displayed");
+});
+
+function testClass(node, className) {
+ ok(node.classList.contains(className), `message has the expected "${className}" class`);
+}
+
+function testIndent(node, indent) {
+ indent = `${indent * INDENT_WIDTH}px`;
+ is(node.querySelector(".indent").style.width, indent,
+ "message has the expected level of indentation");
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
new file mode 100644
index 000000000..0aa544e13
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
@@ -0,0 +1,172 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check console.table calls with all the test cases shown
+// in the MDN doc (https://developer.mozilla.org/en-US/docs/Web/API/Console/table)
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html";
+
+add_task(function* () {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let hud = toolbox.getCurrentPanel().hud;
+
+ function Person(firstName, lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ const testCases = [{
+ info: "Testing when data argument is an array",
+ input: ["apples", "oranges", "bananas"],
+ expected: {
+ columns: ["(index)", "Values"],
+ rows: [
+ ["0", "apples"],
+ ["1", "oranges"],
+ ["2", "bananas"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is an object",
+ input: new Person("John", "Smith"),
+ expected: {
+ columns: ["(index)", "Values"],
+ rows: [
+ ["firstName", "John"],
+ ["lastName", "Smith"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is an array of arrays",
+ input: [["Jane", "Doe"], ["Emily", "Jones"]],
+ expected: {
+ columns: ["(index)", "0", "1"],
+ rows: [
+ ["0", "Jane", "Doe"],
+ ["1", "Emily", "Jones"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is an array of objects",
+ input: [
+ new Person("Jack", "Foo"),
+ new Person("Emma", "Bar"),
+ new Person("Michelle", "Rax"),
+ ],
+ expected: {
+ columns: ["(index)", "firstName", "lastName"],
+ rows: [
+ ["0", "Jack", "Foo"],
+ ["1", "Emma", "Bar"],
+ ["2", "Michelle", "Rax"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is an object whose properties are objects",
+ input: {
+ father: new Person("Darth", "Vader"),
+ daughter: new Person("Leia", "Organa"),
+ son: new Person("Luke", "Skywalker"),
+ },
+ expected: {
+ columns: ["(index)", "firstName", "lastName"],
+ rows: [
+ ["father", "Darth", "Vader"],
+ ["daughter", "Leia", "Organa"],
+ ["son", "Luke", "Skywalker"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is a Set",
+ input: new Set(["a", "b", "c"]),
+ expected: {
+ columns: ["(iteration index)", "Values"],
+ rows: [
+ ["0", "a"],
+ ["1", "b"],
+ ["2", "c"],
+ ]
+ }
+ }, {
+ info: "Testing when data argument is a Map",
+ input: new Map([["key-a", "value-a"], ["key-b", "value-b"]]),
+ expected: {
+ columns: ["(iteration index)", "Key", "Values"],
+ rows: [
+ ["0", "key-a", "value-a"],
+ ["1", "key-b", "value-b"],
+ ]
+ }
+ }, {
+ info: "Testing restricting the columns displayed",
+ input: [
+ new Person("Sam", "Wright"),
+ new Person("Elena", "Bartz"),
+ ],
+ headers: ["firstName"],
+ expected: {
+ columns: ["(index)", "firstName"],
+ rows: [
+ ["0", "Sam"],
+ ["1", "Elena"],
+ ]
+ }
+ }];
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, testCases, function (tests) {
+ tests.forEach((test) => {
+ content.wrappedJSObject.doConsoleTable(test.input, test.headers);
+ });
+ });
+
+ let nodes = [];
+ for (let testCase of testCases) {
+ let node = yield waitFor(
+ () => findConsoleTable(hud.ui.experimentalOutputNode, testCases.indexOf(testCase))
+ );
+ nodes.push(node);
+ }
+
+ let consoleTableNodes = hud.ui.experimentalOutputNode.querySelectorAll(
+ ".message .new-consoletable");
+
+ is(consoleTableNodes.length, testCases.length,
+ "console has the expected number of consoleTable items");
+
+ testCases.forEach((testCase, index) => {
+ info(testCase.info);
+
+ let node = nodes[index];
+ let columns = Array.from(node.querySelectorAll("thead th"));
+ let rows = Array.from(node.querySelectorAll("tbody tr"));
+
+ is(
+ JSON.stringify(testCase.expected.columns),
+ JSON.stringify(columns.map(column => column.textContent)),
+ "table has the expected columns"
+ );
+
+ is(testCase.expected.rows.length, rows.length,
+ "table has the expected number of rows");
+
+ testCase.expected.rows.forEach((expectedRow, rowIndex) => {
+ let row = rows[rowIndex];
+ let cells = row.querySelectorAll("td");
+ is(expectedRow.length, cells.length, "row has the expected number of cells");
+
+ expectedRow.forEach((expectedCell, cellIndex) => {
+ let cell = cells[cellIndex];
+ is(expectedCell, cell.textContent, "cell has the expected content");
+ });
+ });
+ });
+});
+
+function findConsoleTable(node, index) {
+ let condition = node.querySelector(
+ `.message:nth-of-type(${index + 1}) .new-consoletable`);
+ return condition;
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
new file mode 100644
index 000000000..b76dea3d9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
@@ -0,0 +1,71 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests filters.
+
+"use strict";
+
+const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html";
+
+add_task(function* () {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ const outputNode = hud.ui.experimentalOutputNode;
+
+ const toolbar = yield waitFor(() => {
+ return outputNode.querySelector(".webconsole-filterbar-primary");
+ });
+ ok(toolbar, "Toolbar found");
+
+ // Show the filter bar
+ toolbar.querySelector(".devtools-filter-icon").click();
+ const filterBar = yield waitFor(() => {
+ return outputNode.querySelector(".webconsole-filterbar-secondary");
+ });
+ ok(filterBar, "Filter bar is shown when filter icon is clicked.");
+
+ // Check defaults.
+ Object.values(MESSAGE_LEVEL).forEach(level => {
+ ok(filterIsEnabled(filterBar.querySelector(`.${level}`)),
+ `Filter button for ${level} is on by default`);
+ });
+ ["net", "netxhr"].forEach(category => {
+ ok(!filterIsEnabled(filterBar.querySelector(`.${category}`)),
+ `Filter button for ${category} is off by default`);
+ });
+
+ // Check that messages are shown as expected. This depends on cached messages being
+ // shown.
+ ok(findMessages(hud, "").length == 5,
+ "Messages of all levels shown when filters are on.");
+
+ // Check that messages are not shown when their filter is turned off.
+ filterBar.querySelector(".error").click();
+ yield waitFor(() => findMessages(hud, "").length == 4);
+ ok(true, "When a filter is turned off, its messages are not shown.");
+
+ // Check that the ui settings were persisted.
+ yield closeTabAndToolbox();
+ yield testFilterPersistence();
+});
+
+function filterIsEnabled(button) {
+ return button.classList.contains("checked");
+}
+
+function* testFilterPersistence() {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ const outputNode = hud.ui.experimentalOutputNode;
+ const filterBar = yield waitFor(() => {
+ return outputNode.querySelector(".webconsole-filterbar-secondary");
+ });
+ ok(filterBar, "Filter bar ui setting is persisted.");
+
+ // Check that the filter settings were persisted.
+ ok(!filterIsEnabled(filterBar.querySelector(".error")),
+ "Filter button setting is persisted");
+ ok(findMessages(hud, "").length == 4,
+ "Messages of all levels shown when filters are on.");
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js
new file mode 100644
index 000000000..e63f16100
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/mochitest/test-console.html";
+
+add_task(function* () {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let hud = toolbox.getCurrentPanel().hud;
+ let {ui} = hud;
+
+ ok(ui.jsterm, "jsterm exists");
+ ok(ui.newConsoleOutput, "newConsoleOutput exists");
+
+ // @TODO: fix proptype errors
+ let receievedMessages = waitForMessages({
+ hud,
+ messages: [{
+ text: '0',
+ }, {
+ text: '1',
+ }, {
+ text: '2',
+ }],
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.wrappedJSObject.doLogs(3);
+ });
+
+ yield receievedMessages;
+});
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
new file mode 100644
index 000000000..b05c84b37
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the input field is focused when the console is opened.
+
+"use strict";
+
+const TEST_URI =
+ `data:text/html;charset=utf-8,Test input focused
+ <script>
+ console.log("console message 1");
+ </script>`;
+
+add_task(function* () {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ hud.jsterm.clearOutput();
+
+ let inputNode = hud.jsterm.inputNode;
+ ok(inputNode.getAttribute("focused"), "input node is focused after output is cleared");
+
+ ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.wrappedJSObject.console.log("console message 2");
+ });
+ let msg = yield waitFor(() => findMessage(hud, "console message 2"));
+ let outputItem = msg.querySelector(".message-body");
+
+ inputNode = hud.jsterm.inputNode;
+ ok(inputNode.getAttribute("focused"), "input node is focused, first");
+
+ yield waitForBlurredInput(inputNode);
+
+ EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
+ ok(inputNode.getAttribute("focused"), "input node is focused, second time");
+
+ yield waitForBlurredInput(inputNode);
+
+ info("Setting a text selection and making sure a click does not re-focus");
+ let selection = hud.iframeWindow.getSelection();
+ selection.selectAllChildren(outputItem);
+
+ EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
+ ok(!inputNode.getAttribute("focused"),
+ "input node focused after text is selected");
+});
+
+function waitForBlurredInput(inputNode) {
+ return new Promise(resolve => {
+ let lostFocus = () => {
+ ok(!inputNode.getAttribute("focused"), "input node is not focused");
+ resolve();
+ };
+ inputNode.addEventListener("blur", lostFocus, { once: true });
+ document.getElementById("urlbar").click();
+ });
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
new file mode 100644
index 000000000..6ffaac43b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check that basic keyboard shortcuts work in the web console.
+
+"use strict";
+
+const TEST_URI =
+ `data:text/html;charset=utf-8,<p>Test keyboard accessibility</p>
+ <script>
+ for (let i = 1; i <= 100; i++) {
+ console.log("console message " + i);
+ }
+ </script>
+ `;
+
+add_task(function* () {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ info("Web Console opened");
+
+ const outputScroller = hud.ui.outputScroller;
+
+ yield waitFor(() => findMessages(hud, "").length == 100);
+
+ let currentPosition = outputScroller.scrollTop;
+ const bottom = currentPosition;
+
+ EventUtils.sendMouseEvent({type: "click"}, hud.jsterm.inputNode);
+
+ // Page up.
+ EventUtils.synthesizeKey("VK_PAGE_UP", {});
+ isnot(outputScroller.scrollTop, currentPosition,
+ "scroll position changed after page up");
+
+ // Page down.
+ currentPosition = outputScroller.scrollTop;
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+ ok(outputScroller.scrollTop > currentPosition,
+ "scroll position now at bottom");
+
+ // Home
+ EventUtils.synthesizeKey("VK_HOME", {});
+ is(outputScroller.scrollTop, 0, "scroll position now at top");
+
+ // End
+ EventUtils.synthesizeKey("VK_END", {});
+ let scrollTop = outputScroller.scrollTop;
+ ok(scrollTop > 0 && Math.abs(scrollTop - bottom) <= 5,
+ "scroll position now at bottom");
+
+ // Clear output
+ info("try ctrl-l to clear output");
+ let clearShortcut;
+ if (Services.appinfo.OS === "Darwin") {
+ clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
+ } else {
+ clearShortcut = WCUL10n.getStr("webconsole.clear.key");
+ }
+ synthesizeKeyShortcut(clearShortcut);
+ yield waitFor(() => findMessages(hud, "").length == 0);
+ is(hud.jsterm.inputNode.getAttribute("focused"), "true", "jsterm input is focused");
+
+ // Focus filter
+ info("try ctrl-f to focus filter");
+ synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
+ ok(!hud.jsterm.inputNode.getAttribute("focused"), "jsterm input is not focused");
+ is(hud.ui.filterBox, outputScroller.ownerDocument.activeElement,
+ "filter input is focused");
+});
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js
new file mode 100644
index 000000000..11c803bd9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js
@@ -0,0 +1,46 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for " +
+ "obeserver notifications";
+
+let created = false;
+let destroyed = false;
+
+add_task(function* () {
+ setupObserver();
+ yield openNewTabAndConsole(TEST_URI);
+ yield waitFor(() => created);
+
+ yield closeTabAndToolbox(gBrowser.selectedTab);
+ yield waitFor(() => destroyed);
+});
+
+function setupObserver() {
+ const observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ observe: function observe(subject, topic) {
+ subject = subject.QueryInterface(Ci.nsISupportsString);
+
+ switch (topic) {
+ case "web-console-created":
+ ok(HUDService.getHudReferenceById(subject.data), "We have a hud reference");
+ Services.obs.removeObserver(observer, "web-console-created");
+ created = true;
+ break;
+ case "web-console-destroyed":
+ ok(!HUDService.getHudReferenceById(subject.data), "We do not have a hud reference");
+ Services.obs.removeObserver(observer, "web-console-destroyed");
+ destroyed = true;
+ break;
+ }
+ },
+ };
+
+ Services.obs.addObserver(observer, "web-console-created", false);
+ Services.obs.addObserver(observer, "web-console-destroyed", false);
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js
new file mode 100644
index 000000000..54f835c9e
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check that the variables view sidebar can be closed by pressing Escape in the
+// web console.
+
+"use strict";
+
+const TEST_URI =
+ "data:text/html;charset=utf8,<script>let fooObj = {testProp: 'testValue'}</script>";
+
+add_task(function* () {
+ let hud = yield openNewTabAndConsole(TEST_URI);
+ let jsterm = hud.jsterm;
+ let vview;
+
+ yield openSidebar("fooObj", 'testProp: "testValue"');
+ vview.window.focus();
+
+ let sidebarClosed = jsterm.once("sidebar-closed");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield sidebarClosed;
+
+ function* openSidebar(objName, expectedText) {
+ yield jsterm.execute(objName);
+ info("JSTerm executed");
+
+ let msg = yield waitFor(() => findMessage(hud, "Object"));
+ ok(msg, "Message found");
+
+ let anchor = msg.querySelector("a");
+ let body = msg.querySelector(".message-body");
+ ok(anchor, "object anchor");
+ ok(body, "message body");
+ ok(body.textContent.includes(expectedText), "message text check");
+
+ msg.scrollIntoView();
+ yield EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
+
+ let vviewVar = yield jsterm.once("variablesview-fetched");
+ vview = vviewVar._variablesView;
+ ok(vview, "variables view object exists");
+ }
+});
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
new file mode 100644
index 000000000..66de043f8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -0,0 +1,137 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from ../../../../framework/test/shared-head.js */
+
+"use strict";
+
+// shared-head.js handles imports, constants, and utility functions
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
+ this);
+
+var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
+const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
+var {HUDService} = require("devtools/client/webconsole/hudservice");
+var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
+
+Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
+registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
+
+ let browserConsole = HUDService.getBrowserConsole();
+ if (browserConsole) {
+ if (browserConsole.jsterm) {
+ browserConsole.jsterm.clearOutput(true);
+ }
+ yield HUDService.toggleBrowserConsole();
+ }
+});
+
+/**
+ * Add a new tab and open the toolbox in it, and select the webconsole.
+ *
+ * @param string url
+ * The URL for the tab to be opened.
+ * @return Promise
+ * Resolves when the tab has been added, loaded and the toolbox has been opened.
+ * Resolves to the toolbox.
+ */
+var openNewTabAndConsole = Task.async(function* (url) {
+ let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+ let hud = toolbox.getCurrentPanel().hud;
+ hud.jsterm._lazyVariablesView = false;
+ return hud;
+});
+
+/**
+ * Wait for messages in the web console output, resolving once they are receieved.
+ *
+ * @param object options
+ * - hud: the webconsole
+ * - messages: Array[Object]. An array of messages to match. Current supported options:
+ * - text: Exact text match in .message-body
+ */
+function waitForMessages({ hud, messages }) {
+ return new Promise(resolve => {
+ let numMatched = 0;
+ let receivedLog = hud.ui.on("new-messages", function messagesReceieved(e, newMessages) {
+ for (let message of messages) {
+ if (message.matched) {
+ continue;
+ }
+
+ for (let newMessage of newMessages) {
+ if (newMessage.node.querySelector(".message-body").textContent == message.text) {
+ numMatched++;
+ message.matched = true;
+ info("Matched a message with text: " + message.text + ", still waiting for " + (messages.length - numMatched) + " messages");
+ break;
+ }
+ }
+
+ if (numMatched === messages.length) {
+ hud.ui.off("new-messages", messagesReceieved);
+ resolve(receivedLog);
+ return;
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Wait for a predicate to return a result.
+ *
+ * @param function condition
+ * Invoked once in a while until it returns a truthy value. This should be an
+ * idempotent function, since we have to run it a second time after it returns
+ * true in order to return the value.
+ * @param string message [optional]
+ * A message to output if the condition failes.
+ * @param number interval [optional]
+ * How often the predicate is invoked, in milliseconds.
+ * @return object
+ * A promise that is resolved with the result of the condition.
+ */
+function* waitFor(condition, message = "waitFor", interval = 10, maxTries = 500) {
+ return new Promise(resolve => {
+ BrowserTestUtils.waitForCondition(condition, message, interval, maxTries)
+ .then(() => resolve(condition()));
+ });
+}
+
+/**
+ * Find a message in the output.
+ *
+ * @param object hud
+ * The web console.
+ * @param string text
+ * A substring that can be found in the message.
+ * @param selector [optional]
+ * The selector to use in finding the message.
+ */
+function findMessage(hud, text, selector = ".message") {
+ const elements = findMessages(hud, text, selector);
+ return elements.pop();
+}
+
+/**
+ * Find multiple messages in the output.
+ *
+ * @param object hud
+ * The web console.
+ * @param string text
+ * A substring that can be found in the message.
+ * @param selector [optional]
+ * The selector to use in finding the message.
+ */
+function findMessages(hud, text, selector = ".message") {
+ const messages = hud.ui.experimentalOutputNode.querySelectorAll(selector);
+ const elements = Array.prototype.filter.call(
+ messages,
+ (el) => el.textContent.includes(text)
+ );
+ return elements;
+}
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html b/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html
new file mode 100644
index 000000000..9d122387a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-batching.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Webconsole batch console calls test page</title>
+ </head>
+ <body>
+ <p>batch console calls test page</p>
+ <script>
+ "use strict";
+
+ function batchLog(numMessages = 0) {
+ for (let i = 0; i < numMessages; i++) {
+ console.log(i);
+ }
+ }
+
+ function batchLogAndClear(numMessages = 0) {
+ for (let i = 0; i < numMessages; i++) {
+ console.log(i);
+ if (i === numMessages - 1) {
+ console.clear();
+ }
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html
new file mode 100644
index 000000000..293421549
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-filters.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Webconsole filters test page</title>
+ </head>
+ <body>
+ <p>Webconsole filters test page</p>
+ <script>
+ console.log("console log");
+ console.warn("console warn");
+ console.error("console error");
+ console.info("console info");
+ console.count("console debug");
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html
new file mode 100644
index 000000000..47373d3b9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-group.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Webconsole console.group test page</title>
+ </head>
+ <body>
+ <p>console.group() & console.groupCollapsed() test page</p>
+ <script>
+ "use strict";
+
+ function doLog() {
+ console.group("group-1");
+ console.log("log-1");
+ console.group("group-2");
+ console.log("log-2");
+ console.groupEnd("group-2");
+ console.log("log-3");
+ console.groupEnd("group-1");
+ console.log("log-4");
+ console.groupCollapsed("group-3");
+ console.log("log-5");
+ console.groupEnd("group-3");
+ console.log("log-6");
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html
new file mode 100644
index 000000000..b7666e50b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Simple webconsole test page</title>
+ </head>
+ <body>
+ <p>console.table() test page</p>
+ <script>
+ function doConsoleTable(data, constrainedHeaders = null) {
+ if (constrainedHeaders) {
+ console.table(data, constrainedHeaders);
+ } else {
+ console.table(data);
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/test-console.html b/devtools/client/webconsole/new-console-output/test/mochitest/test-console.html
new file mode 100644
index 000000000..7ef09d9a1
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-console.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Simple webconsole test page</title>
+ </head>
+ <body>
+ <p>Simple webconsole test page</p>
+ <script>
+ function doLogs(num) {
+ num = num || 1;
+ for (var i = 0; i < num; i++) {
+ console.log(i);
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/new-console-output/test/moz.build b/devtools/client/webconsole/new-console-output/test/moz.build
new file mode 100644
index 000000000..a704def99
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/moz.build
@@ -0,0 +1,16 @@
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'fixtures/stub-generators/browser.ini',
+ 'mochitest/browser.ini',
+]
+
+DIRS += [
+ 'fixtures'
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'chrome/chrome.ini',
+]
diff --git a/devtools/client/webconsole/new-console-output/test/requireHelper.js b/devtools/client/webconsole/new-console-output/test/requireHelper.js
new file mode 100644
index 000000000..ac6205808
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/requireHelper.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const requireHacker = require("require-hacker");
+
+requireHacker.global_hook("default", path => {
+ switch (path) {
+ // For Enzyme
+ case "react-dom/server":
+ return `const React = require('react-dev'); module.exports = React`;
+ case "react-addons-test-utils":
+ return `const React = require('react-dev'); module.exports = React.addons.TestUtils`;
+ // Use react-dev. This would be handled by browserLoader in Firefox.
+ case "react":
+ case "devtools/client/shared/vendor/react":
+ return `const React = require('react-dev'); module.exports = React`;
+ // For Rep's use of AMD
+ case "devtools/client/shared/vendor/react.default":
+ return `const React = require('react-dev'); module.exports = React`;
+ }
+
+ // Some modules depend on Chrome APIs which don't work in mocha. When such a module
+ // is required, replace it with a mock version.
+ switch (path) {
+ case "devtools/client/webconsole/utils":
+ return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils")`;
+ case "devtools/shared/l10n":
+ return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper")`;
+ case "devtools/shared/plural-form":
+ return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/PluralForm")`;
+ case "Services":
+ case "Services.default":
+ return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/Services")`;
+ case "devtools/shared/client/main":
+ return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient")`;
+ }
+});
diff --git a/devtools/client/webconsole/new-console-output/test/store/filters.test.js b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
new file mode 100644
index 000000000..3c38a255a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const { messageAdd } = require("devtools/client/webconsole/new-console-output/actions/index");
+const { ConsoleCommand } = require("devtools/client/webconsole/new-console-output/types");
+const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
+const { MESSAGE_LEVEL } = require("devtools/client/webconsole/new-console-output/constants");
+const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+
+describe("Filtering", () => {
+ let store;
+ let numMessages;
+ // Number of messages in prepareBaseStore which are not filtered out, i.e. Evaluation
+ // Results, console commands and console.groups .
+ const numUnfilterableMessages = 3;
+
+ beforeEach(() => {
+ store = prepareBaseStore();
+ store.dispatch(actions.filtersClear());
+ numMessages = getAllMessages(store.getState()).size;
+ });
+
+ describe("Level filter", () => {
+ it("filters log messages", () => {
+ store.dispatch(actions.filterToggle(MESSAGE_LEVEL.LOG));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages - 3);
+ });
+
+ it("filters debug messages", () => {
+ store.dispatch(actions.filterToggle(MESSAGE_LEVEL.DEBUG));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages - 1);
+ });
+
+ // @TODO add info stub
+ it("filters info messages");
+
+ it("filters warning messages", () => {
+ store.dispatch(actions.filterToggle(MESSAGE_LEVEL.WARN));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages - 1);
+ });
+
+ it("filters error messages", () => {
+ store.dispatch(actions.filterToggle(MESSAGE_LEVEL.ERROR));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages - 1);
+ });
+
+ it("filters xhr messages", () => {
+ let message = stubPreparedMessages.get("XHR GET request");
+ store.dispatch(messageAdd(message));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages);
+
+ store.dispatch(actions.filterToggle("netxhr"));
+ messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages + 1);
+ });
+
+ it("filters network messages", () => {
+ let message = stubPreparedMessages.get("GET request");
+ store.dispatch(messageAdd(message));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages);
+
+ store.dispatch(actions.filterToggle("net"));
+ messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages + 1);
+ });
+ });
+
+ describe("Text filter", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("danger"));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches unicode values", () => {
+ store.dispatch(actions.filterTextSet("鼬"));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches locations", () => {
+ // Add a message with a different filename.
+ let locationMsg =
+ Object.assign({}, stubPackets.get("console.log('foobar', 'test')"));
+ locationMsg.message =
+ Object.assign({}, locationMsg.message, { filename: "search-location-test.js" });
+ store.dispatch(messageAdd(locationMsg));
+
+ store.dispatch(actions.filterTextSet("search-location-test.js"));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches stacktrace functionName", () => {
+ let traceMessage = stubPackets.get("console.trace()");
+ store.dispatch(messageAdd(traceMessage));
+
+ store.dispatch(actions.filterTextSet("testStacktraceFiltering"));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches stacktrace location", () => {
+ let traceMessage = stubPackets.get("console.trace()");
+ traceMessage.message =
+ Object.assign({}, traceMessage.message, {
+ filename: "search-location-test.js",
+ lineNumber: 85,
+ columnNumber: 13
+ });
+
+ store.dispatch(messageAdd(traceMessage));
+
+ store.dispatch(actions.filterTextSet("search-location-test.js:85:13"));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("restores all messages once text is cleared", () => {
+ store.dispatch(actions.filterTextSet("danger"));
+ store.dispatch(actions.filterTextSet(""));
+
+ let messages = getAllMessages(store.getState());
+ expect(messages.size).toEqual(numMessages);
+ });
+ });
+
+ describe("Combined filters", () => {
+ // @TODO add test
+ it("filters");
+ });
+});
+
+describe("Clear filters", () => {
+ it("clears all filters", () => {
+ const store = setupStore([]);
+
+ // Setup test case
+ store.dispatch(actions.filterToggle(MESSAGE_LEVEL.ERROR));
+ store.dispatch(actions.filterToggle("netxhr"));
+ store.dispatch(actions.filterTextSet("foobar"));
+
+ let filters = getAllFilters(store.getState());
+ expect(filters.toJS()).toEqual({
+ "debug": true,
+ "error": false,
+ "info": true,
+ "log": true,
+ "net": false,
+ "netxhr": true,
+ "warn": true,
+ "text": "foobar"
+ });
+
+ store.dispatch(actions.filtersClear());
+
+ filters = getAllFilters(store.getState());
+ expect(filters.toJS()).toEqual({
+ "debug": true,
+ "error": true,
+ "info": true,
+ "log": true,
+ "net": false,
+ "netxhr": false,
+ "warn": true,
+ "text": ""
+ });
+ });
+});
+
+function prepareBaseStore() {
+ const store = setupStore([
+ // Console API
+ "console.log('foobar', 'test')",
+ "console.warn('danger, will robinson!')",
+ "console.log(undefined)",
+ "console.count('bar')",
+ "console.log('鼬')",
+ // Evaluation Result - never filtered
+ "new Date(0)",
+ // PageError
+ "ReferenceError: asdf is not defined",
+ "console.group('bar')"
+ ]);
+
+ // Console Command - never filtered
+ store.dispatch(messageAdd(new ConsoleCommand({ messageText: `console.warn("x")` })));
+
+ return store;
+}
diff --git a/devtools/client/webconsole/new-console-output/test/store/messages.test.js b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
new file mode 100644
index 000000000..582ca36e3
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -0,0 +1,353 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ getAllMessages,
+ getAllMessagesUiById,
+ getAllGroupsById,
+ getCurrentGroup,
+} = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const {
+ setupActions,
+ setupStore
+} = require("devtools/client/webconsole/new-console-output/test/helpers");
+const { stubPackets, stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+const {
+ MESSAGE_TYPE,
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const expect = require("expect");
+
+describe("Message reducer:", () => {
+ let actions;
+
+ before(() => {
+ actions = setupActions();
+ });
+
+ describe("messagesById", () => {
+ it("adds a message to an empty store", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ const message = stubPreparedMessages.get("console.log('foobar', 'test')");
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+
+ expect(messages.first()).toEqual(message);
+ });
+
+ it("increments repeat on a repeating message", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log('foobar', 'test')"
+ ]);
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messageAdd(packet));
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+
+ expect(messages.size).toBe(1);
+ expect(messages.first().repeat).toBe(4);
+ });
+
+ it("does not clobber a unique message", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log('foobar', 'test')"
+ ]);
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messageAdd(packet));
+
+ const packet2 = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messageAdd(packet2));
+
+ const messages = getAllMessages(getState());
+
+ expect(messages.size).toBe(2);
+ expect(messages.first().repeat).toBe(3);
+ expect(messages.last().repeat).toBe(1);
+ });
+
+ it("adds a message in response to console.clear()", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ dispatch(actions.messageAdd(stubPackets.get("console.clear()")));
+
+ const messages = getAllMessages(getState());
+
+ expect(messages.size).toBe(1);
+ expect(messages.first().parameters[0]).toBe("Console was cleared.");
+ });
+
+ it("clears the messages list in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log(undefined)"
+ ]);
+
+ dispatch(actions.messagesClear());
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(0);
+ });
+
+ it("limits the number of messages displayed", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const logLimit = 1000;
+ const packet = stubPackets.get("console.log(undefined)");
+ for (let i = 1; i <= logLimit + 1; i++) {
+ packet.message.arguments = [`message num ${i}`];
+ dispatch(actions.messageAdd(packet));
+ }
+
+ const messages = getAllMessages(getState());
+ expect(messages.count()).toBe(logLimit);
+ expect(messages.first().parameters[0]).toBe(`message num 2`);
+ expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 1}`);
+ });
+
+ it("does not add null messages to the store", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.time('bar')");
+ dispatch(actions.messageAdd(message));
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(0);
+ });
+
+ it("adds console.table call with unsupported type as console.log", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const packet = stubPackets.get("console.table('bar')");
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+ const tableMessage = messages.last();
+ expect(tableMessage.level).toEqual(MESSAGE_TYPE.LOG);
+ });
+
+ it("adds console.group messages to the store", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.group('bar')");
+ dispatch(actions.messageAdd(message));
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(1);
+ });
+
+ it("sets groupId property as expected", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ dispatch(actions.messageAdd(
+ stubPackets.get("console.group('bar')")));
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(2);
+ expect(messages.last().groupId).toBe(messages.first().id);
+ });
+
+ it("does not display console.groupEnd messages to the store", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.groupEnd('bar')");
+ dispatch(actions.messageAdd(message));
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(0);
+ });
+
+ it("filters out message added after a console.groupCollapsed message", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.groupCollapsed('foo')");
+ dispatch(actions.messageAdd(message));
+
+ dispatch(actions.messageAdd(
+ stubPackets.get("console.log('foobar', 'test')")));
+
+ const messages = getAllMessages(getState());
+ expect(messages.size).toBe(1);
+ });
+
+ it("shows the group of the first displayed message when messages are pruned", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const logLimit = 1000;
+
+ const groupMessage = stubPreparedMessages.get("console.group('bar')");
+ dispatch(actions.messageAdd(
+ stubPackets.get("console.group('bar')")));
+
+ const packet = stubPackets.get("console.log(undefined)");
+ for (let i = 1; i <= logLimit + 1; i++) {
+ packet.message.arguments = [`message num ${i}`];
+ dispatch(actions.messageAdd(packet));
+ }
+
+ const messages = getAllMessages(getState());
+ expect(messages.count()).toBe(logLimit);
+ expect(messages.first().messageText).toBe(groupMessage.messageText);
+ expect(messages.get(1).parameters[0]).toBe(`message num 3`);
+ expect(messages.last().parameters[0]).toBe(`message num ${logLimit + 1}`);
+ });
+
+ it("adds console.dirxml call as console.log", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const packet = stubPackets.get("console.dirxml(window)");
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+ const dirxmlMessage = messages.last();
+ expect(dirxmlMessage.level).toEqual(MESSAGE_TYPE.LOG);
+ });
+ });
+
+ describe("messagesUiById", () => {
+ it("opens console.trace messages when they are added", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.trace()");
+ dispatch(actions.messageAdd(message));
+
+ const messages = getAllMessages(getState());
+ const messagesUi = getAllMessagesUiById(getState());
+ expect(messagesUi.size).toBe(1);
+ expect(messagesUi.first()).toBe(messages.first().id);
+ });
+
+ it("clears the messages UI list in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log(undefined)"
+ ]);
+
+ const traceMessage = stubPackets.get("console.trace()");
+ dispatch(actions.messageAdd(traceMessage));
+
+ dispatch(actions.messagesClear());
+
+ const messagesUi = getAllMessagesUiById(getState());
+ expect(messagesUi.size).toBe(0);
+ });
+
+ it("opens console.group messages when they are added", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.group('bar')");
+ dispatch(actions.messageAdd(message));
+
+ const messages = getAllMessages(getState());
+ const messagesUi = getAllMessagesUiById(getState());
+ expect(messagesUi.size).toBe(1);
+ expect(messagesUi.first()).toBe(messages.first().id);
+ });
+
+ it("does not open console.groupCollapsed messages when they are added", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const message = stubPackets.get("console.groupCollapsed('foo')");
+ dispatch(actions.messageAdd(message));
+
+ const messagesUi = getAllMessagesUiById(getState());
+ expect(messagesUi.size).toBe(0);
+ });
+ });
+
+ describe("currentGroup", () => {
+ it("sets the currentGroup when console.group message is added", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const packet = stubPackets.get("console.group('bar')");
+ dispatch(actions.messageAdd(packet));
+
+ const messages = getAllMessages(getState());
+ const currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(messages.first().id);
+ });
+
+ it("sets currentGroup to expected value when console.groupEnd is added", () => {
+ const { dispatch, getState } = setupStore([
+ "console.group('bar')",
+ "console.groupCollapsed('foo')"
+ ]);
+
+ let messages = getAllMessages(getState());
+ let currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(messages.last().id);
+
+ const endFooPacket = stubPackets.get("console.groupEnd('foo')");
+ dispatch(actions.messageAdd(endFooPacket));
+ messages = getAllMessages(getState());
+ currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(messages.first().id);
+
+ const endBarPacket = stubPackets.get("console.groupEnd('foo')");
+ dispatch(actions.messageAdd(endBarPacket));
+ messages = getAllMessages(getState());
+ currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(null);
+ });
+
+ it("resets the currentGroup to null in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.group('bar')"
+ ]);
+
+ dispatch(actions.messagesClear());
+
+ const currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(null);
+ });
+ });
+
+ describe("groupsById", () => {
+ it("adds the group with expected array when console.group message is added", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ const barPacket = stubPackets.get("console.group('bar')");
+ dispatch(actions.messageAdd(barPacket));
+
+ let messages = getAllMessages(getState());
+ let groupsById = getAllGroupsById(getState());
+ expect(groupsById.size).toBe(1);
+ expect(groupsById.has(messages.first().id)).toBe(true);
+ expect(groupsById.get(messages.first().id)).toEqual([]);
+
+ const fooPacket = stubPackets.get("console.groupCollapsed('foo')");
+ dispatch(actions.messageAdd(fooPacket));
+ messages = getAllMessages(getState());
+ groupsById = getAllGroupsById(getState());
+ expect(groupsById.size).toBe(2);
+ expect(groupsById.has(messages.last().id)).toBe(true);
+ expect(groupsById.get(messages.last().id)).toEqual([messages.first().id]);
+ });
+
+ it("resets groupsById in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.group('bar')",
+ "console.groupCollapsed('foo')",
+ ]);
+
+ let groupsById = getAllGroupsById(getState());
+ expect(groupsById.size).toBe(2);
+
+ dispatch(actions.messagesClear());
+
+ groupsById = getAllGroupsById(getState());
+ expect(groupsById.size).toBe(0);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/test/utils/getRepeatId.test.js b/devtools/client/webconsole/new-console-output/test/utils/getRepeatId.test.js
new file mode 100644
index 000000000..d27238e14
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/utils/getRepeatId.test.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { getRepeatId } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
+
+const expect = require("expect");
+
+describe("getRepeatId:", () => {
+ it("returns same repeatId for duplicate values", () => {
+ const message1 = stubPreparedMessages.get("console.log('foobar', 'test')");
+ const message2 = message1.set("repeat", 3);
+ expect(getRepeatId(message1)).toEqual(getRepeatId(message2));
+ });
+
+ it("returns different repeatIds for different values", () => {
+ const message1 = stubPreparedMessages.get("console.log('foobar', 'test')");
+ const message2 = message1.set("parameters", ["funny", "monkey"]);
+ expect(getRepeatId(message1)).toNotEqual(getRepeatId(message2));
+ });
+
+ it("returns different repeatIds for different severities", () => {
+ const message1 = stubPreparedMessages.get("console.log('foobar', 'test')");
+ const message2 = message1.set("level", "error");
+ expect(getRepeatId(message1)).toNotEqual(getRepeatId(message2));
+ });
+
+ it("handles falsy values distinctly", () => {
+ const messageNaN = stubPreparedMessages.get("console.log(NaN)");
+ const messageUnd = stubPreparedMessages.get("console.log(undefined)");
+ const messageNul = stubPreparedMessages.get("console.log(null)");
+
+ const repeatIds = new Set([
+ getRepeatId(messageNaN),
+ getRepeatId(messageUnd),
+ getRepeatId(messageNul)]
+ );
+ expect(repeatIds.size).toEqual(3);
+ });
+});
diff --git a/devtools/client/webconsole/new-console-output/types.js b/devtools/client/webconsole/new-console-output/types.js
new file mode 100644
index 000000000..3def93ef2
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+
+const {
+ MESSAGE_SOURCE,
+ MESSAGE_TYPE,
+ MESSAGE_LEVEL
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+exports.ConsoleCommand = Immutable.Record({
+ id: null,
+ allowRepeating: false,
+ messageText: null,
+ source: MESSAGE_SOURCE.JAVASCRIPT,
+ type: MESSAGE_TYPE.COMMAND,
+ level: MESSAGE_LEVEL.LOG,
+ groupId: null,
+});
+
+exports.ConsoleMessage = Immutable.Record({
+ id: null,
+ allowRepeating: true,
+ source: null,
+ type: null,
+ level: null,
+ messageText: null,
+ parameters: null,
+ repeat: 1,
+ repeatId: null,
+ stacktrace: null,
+ frame: null,
+ groupId: null,
+ exceptionDocURL: null,
+ userProvidedStyles: null,
+ notes: null,
+});
+
+exports.NetworkEventMessage = Immutable.Record({
+ id: null,
+ actor: null,
+ level: MESSAGE_LEVEL.LOG,
+ isXHR: false,
+ request: null,
+ response: null,
+ source: MESSAGE_SOURCE.NETWORK,
+ type: MESSAGE_TYPE.LOG,
+ groupId: null,
+});
diff --git a/devtools/client/webconsole/new-console-output/utils/id-generator.js b/devtools/client/webconsole/new-console-output/utils/id-generator.js
new file mode 100644
index 000000000..1ddfb4379
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/id-generator.js
@@ -0,0 +1,21 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+exports.IdGenerator = class IdGenerator {
+ constructor() {
+ this.messageId = 1;
+ }
+
+ getNextId() {
+ // Return the next message id, as a string.
+ return "" + this.messageId++;
+ }
+
+ getCurrentId() {
+ return this.messageId;
+ }
+};
diff --git a/devtools/client/webconsole/new-console-output/utils/messages.js b/devtools/client/webconsole/new-console-output/utils/messages.js
new file mode 100644
index 000000000..edcc17435
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -0,0 +1,285 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
+const STRINGS_URI = "devtools/client/locales/webconsole.properties";
+const l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+
+const {
+ MESSAGE_SOURCE,
+ MESSAGE_TYPE,
+ MESSAGE_LEVEL,
+} = require("../constants");
+const {
+ ConsoleMessage,
+ NetworkEventMessage,
+} = require("../types");
+
+function prepareMessage(packet, idGenerator) {
+ // This packet is already in the expected packet structure. Simply return.
+ if (!packet.source) {
+ packet = transformPacket(packet);
+ }
+
+ if (packet.allowRepeating) {
+ packet = packet.set("repeatId", getRepeatId(packet));
+ }
+ return packet.set("id", idGenerator.getNextId());
+}
+
+/**
+ * Transforms a packet from Firefox RDP structure to Chrome RDP structure.
+ */
+function transformPacket(packet) {
+ if (packet._type) {
+ packet = convertCachedPacket(packet);
+ }
+
+ switch (packet.type) {
+ case "consoleAPICall": {
+ let { message } = packet;
+
+ let parameters = message.arguments;
+ let type = message.level;
+ let level = getLevelFromType(type);
+ let messageText = null;
+ const timer = message.timer;
+
+ // Special per-type conversion.
+ switch (type) {
+ case "clear":
+ // We show a message to users when calls console.clear() is called.
+ parameters = [l10n.getStr("consoleCleared")];
+ break;
+ case "count":
+ // Chrome RDP doesn't have a special type for count.
+ type = MESSAGE_TYPE.LOG;
+ let {counter} = message;
+ let label = counter.label ? counter.label : l10n.getStr("noCounterLabel");
+ messageText = `${label}: ${counter.count}`;
+ parameters = null;
+ break;
+ case "time":
+ // We don't show anything for console.time calls to match Chrome's behaviour.
+ parameters = null;
+ type = MESSAGE_TYPE.NULL_MESSAGE;
+ break;
+ case "timeEnd":
+ parameters = null;
+ if (timer) {
+ // We show the duration to users when calls console.timeEnd() is called,
+ // if corresponding console.time() was called before.
+ let duration = Math.round(timer.duration * 100) / 100;
+ messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
+ } else {
+ // If the `timer` property does not exists, we don't output anything.
+ type = MESSAGE_TYPE.NULL_MESSAGE;
+ }
+ break;
+ case "table":
+ const supportedClasses = [
+ "Array", "Object", "Map", "Set", "WeakMap", "WeakSet"];
+ if (
+ !Array.isArray(parameters) ||
+ parameters.length === 0 ||
+ !supportedClasses.includes(parameters[0].class)
+ ) {
+ // If the class of the first parameter is not supported,
+ // we handle the call as a simple console.log
+ type = "log";
+ }
+ break;
+ case "group":
+ type = MESSAGE_TYPE.START_GROUP;
+ parameters = null;
+ messageText = message.groupName || l10n.getStr("noGroupLabel");
+ break;
+ case "groupCollapsed":
+ type = MESSAGE_TYPE.START_GROUP_COLLAPSED;
+ parameters = null;
+ messageText = message.groupName || l10n.getStr("noGroupLabel");
+ break;
+ case "groupEnd":
+ type = MESSAGE_TYPE.END_GROUP;
+ parameters = null;
+ break;
+ case "dirxml":
+ // Handle console.dirxml calls as simple console.log
+ type = "log";
+ break;
+ }
+
+ const frame = message.filename ? {
+ source: message.filename,
+ line: message.lineNumber,
+ column: message.columnNumber,
+ } : null;
+
+ return new ConsoleMessage({
+ source: MESSAGE_SOURCE.CONSOLE_API,
+ type,
+ level,
+ parameters,
+ messageText,
+ stacktrace: message.stacktrace ? message.stacktrace : null,
+ frame,
+ userProvidedStyles: message.styles,
+ });
+ }
+
+ case "navigationMessage": {
+ let { message } = packet;
+ return new ConsoleMessage({
+ source: MESSAGE_SOURCE.CONSOLE_API,
+ type: MESSAGE_TYPE.LOG,
+ level: MESSAGE_LEVEL.LOG,
+ messageText: "Navigated to " + message.url,
+ });
+ }
+
+ case "pageError": {
+ let { pageError } = packet;
+ let level = MESSAGE_LEVEL.ERROR;
+ if (pageError.warning || pageError.strict) {
+ level = MESSAGE_LEVEL.WARN;
+ } else if (pageError.info) {
+ level = MESSAGE_LEVEL.INFO;
+ }
+
+ const frame = pageError.sourceName ? {
+ source: pageError.sourceName,
+ line: pageError.lineNumber,
+ column: pageError.columnNumber
+ } : null;
+
+ return new ConsoleMessage({
+ source: MESSAGE_SOURCE.JAVASCRIPT,
+ type: MESSAGE_TYPE.LOG,
+ level,
+ messageText: pageError.errorMessage,
+ stacktrace: pageError.stacktrace ? pageError.stacktrace : null,
+ frame,
+ exceptionDocURL: pageError.exceptionDocURL,
+ notes: pageError.notes,
+ });
+ }
+
+ case "networkEvent": {
+ let { networkEvent } = packet;
+
+ return new NetworkEventMessage({
+ actor: networkEvent.actor,
+ isXHR: networkEvent.isXHR,
+ request: networkEvent.request,
+ response: networkEvent.response,
+ });
+ }
+
+ case "evaluationResult":
+ default: {
+ let {
+ exceptionMessage: messageText,
+ exceptionDocURL,
+ frame,
+ result: parameters,
+ notes,
+ } = packet;
+
+ const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG;
+ return new ConsoleMessage({
+ source: MESSAGE_SOURCE.JAVASCRIPT,
+ type: MESSAGE_TYPE.RESULT,
+ level,
+ messageText,
+ parameters,
+ exceptionDocURL,
+ frame,
+ notes,
+ });
+ }
+ }
+}
+
+// Helpers
+function getRepeatId(message) {
+ message = message.toJS();
+ delete message.repeat;
+ return JSON.stringify(message);
+}
+
+function convertCachedPacket(packet) {
+ // The devtools server provides cached message packets in a different shape, so we
+ // transform them here.
+ let convertPacket = {};
+ if (packet._type === "ConsoleAPI") {
+ convertPacket.message = packet;
+ convertPacket.type = "consoleAPICall";
+ } else if (packet._type === "PageError") {
+ convertPacket.pageError = packet;
+ convertPacket.type = "pageError";
+ } else if ("_navPayload" in packet) {
+ convertPacket.type = "navigationMessage";
+ convertPacket.message = packet;
+ } else if (packet._type === "NetworkEvent") {
+ convertPacket.networkEvent = packet;
+ convertPacket.type = "networkEvent";
+ } else {
+ throw new Error("Unexpected packet type");
+ }
+ return convertPacket;
+}
+
+/**
+ * Maps a Firefox RDP type to its corresponding level.
+ */
+function getLevelFromType(type) {
+ const levels = {
+ LEVEL_ERROR: "error",
+ LEVEL_WARNING: "warn",
+ LEVEL_INFO: "info",
+ LEVEL_LOG: "log",
+ LEVEL_DEBUG: "debug",
+ };
+
+ // A mapping from the console API log event levels to the Web Console levels.
+ const levelMap = {
+ error: levels.LEVEL_ERROR,
+ exception: levels.LEVEL_ERROR,
+ assert: levels.LEVEL_ERROR,
+ warn: levels.LEVEL_WARNING,
+ info: levels.LEVEL_INFO,
+ log: levels.LEVEL_LOG,
+ clear: levels.LEVEL_LOG,
+ trace: levels.LEVEL_LOG,
+ table: levels.LEVEL_LOG,
+ debug: levels.LEVEL_LOG,
+ dir: levels.LEVEL_LOG,
+ dirxml: levels.LEVEL_LOG,
+ group: levels.LEVEL_LOG,
+ groupCollapsed: levels.LEVEL_LOG,
+ groupEnd: levels.LEVEL_LOG,
+ time: levels.LEVEL_LOG,
+ timeEnd: levels.LEVEL_LOG,
+ count: levels.LEVEL_DEBUG,
+ };
+
+ return levelMap[type] || MESSAGE_TYPE.LOG;
+}
+
+function isGroupType(type) {
+ return [
+ MESSAGE_TYPE.START_GROUP,
+ MESSAGE_TYPE.START_GROUP_COLLAPSED
+ ].includes(type);
+}
+
+exports.prepareMessage = prepareMessage;
+// Export for use in testing.
+exports.getRepeatId = getRepeatId;
+
+exports.l10n = l10n;
+exports.isGroupType = isGroupType;
diff --git a/devtools/client/webconsole/new-console-output/utils/moz.build b/devtools/client/webconsole/new-console-output/utils/moz.build
new file mode 100644
index 000000000..b666607d6
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/moz.build
@@ -0,0 +1,9 @@
+# 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(
+ 'id-generator.js',
+ 'messages.js',
+ 'variables-view.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/utils/variables-view.js b/devtools/client/webconsole/new-console-output/utils/variables-view.js
new file mode 100644
index 000000000..a2b5fc187
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/variables-view.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+/* global window */
+"use strict";
+
+/**
+ * @TODO Remove this.
+ *
+ * Once JSTerm is also written in React/Redux, these will be actions.
+ */
+exports.openVariablesView = (objectActor) => {
+ window.jsterm.openVariablesView({
+ objectActor,
+ autofocus: true,
+ });
+};