summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/new-console-output
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/new-console-output')
-rw-r--r--devtools/client/webconsole/new-console-output/actions/enhancers.js20
-rw-r--r--devtools/client/webconsole/new-console-output/actions/filters.js55
-rw-r--r--devtools/client/webconsole/new-console-output/actions/index.js18
-rw-r--r--devtools/client/webconsole/new-console-output/actions/messages.js100
-rw-r--r--devtools/client/webconsole/new-console-output/actions/moz.build12
-rw-r--r--devtools/client/webconsole/new-console-output/actions/ui.js27
-rw-r--r--devtools/client/webconsole/new-console-output/components/collapse-button.js50
-rw-r--r--devtools/client/webconsole/new-console-output/components/console-output.js125
-rw-r--r--devtools/client/webconsole/new-console-output/components/console-table.js202
-rw-r--r--devtools/client/webconsole/new-console-output/components/filter-bar.js170
-rw-r--r--devtools/client/webconsole/new-console-output/components/filter-button.js46
-rw-r--r--devtools/client/webconsole/new-console-output/components/grip-message-body.js102
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-container.js92
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-icon.js32
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-indent.js37
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-repeat.js36
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js132
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/console-command.js57
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js22
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js64
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/moz.build13
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js63
-rw-r--r--devtools/client/webconsole/new-console-output/components/message-types/page-error.js69
-rw-r--r--devtools/client/webconsole/new-console-output/components/message.js176
-rw-r--r--devtools/client/webconsole/new-console-output/components/moz.build23
-rw-r--r--devtools/client/webconsole/new-console-output/components/variables-view-link.js34
-rw-r--r--devtools/client/webconsole/new-console-output/constants.js81
-rw-r--r--devtools/client/webconsole/new-console-output/main.js23
-rw-r--r--devtools/client/webconsole/new-console-output/moz.build21
-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.js39
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/index.js18
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/messages.js135
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/moz.build12
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/prefs.js18
-rw-r--r--devtools/client/webconsole/new-console-output/reducers/ui.js39
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/filters.js12
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/messages.js168
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/moz.build11
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/prefs.js12
-rw-r--r--devtools/client/webconsole/new-console-output/selectors/ui.js20
-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.js126
-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.build9
-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.js56
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js32
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js47
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js48
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js192
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build8
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js148
-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.js1482
-rw-r--r--devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js182
-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.js102
-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.js51
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js91
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js173
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js72
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js35
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js57
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js71
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js47
-rw-r--r--devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js46
-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.build17
-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.js22
-rw-r--r--devtools/client/webconsole/new-console-output/utils/messages.js283
-rw-r--r--devtools/client/webconsole/new-console-output/utils/moz.build10
-rw-r--r--devtools/client/webconsole/new-console-output/utils/variables-view.js20
107 files changed, 8185 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..5553942e2
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/enhancers.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..05d080219
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/filters.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..5ce76a402
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/index.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..467e27503
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -0,0 +1,100 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..c7a8ed52c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ '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..cf9814d79
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -0,0 +1,27 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..ab72fcf4d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/collapse-button.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ DOM: dom,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+const CollapseButton = createClass({
+
+ displayName: "CollapseButton",
+
+ propTypes: {
+ open: PropTypes.bool.isRequired,
+ title: PropTypes.string,
+ },
+
+ getDefaultProps: function () {
+ return {
+ title: l10n.getStr("messageToggleDetails")
+ };
+ },
+
+ render: function () {
+ const { open, onClick, title } = this.props;
+
+ let classes = ["theme-twisty"];
+
+ if (open) {
+ classes.push("open");
+ }
+
+ return dom.a({
+ className: classes.join(" "),
+ onClick,
+ title: title,
+ });
+ }
+});
+
+module.exports = CollapseButton;
diff --git a/devtools/client/webconsole/new-console-output/components/console-output.js b/devtools/client/webconsole/new-console-output/components/console-output.js
new file mode 100644
index 000000000..1ba7f8dda
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const {
+ getAllMessages,
+ getAllMessagesUiById,
+ getAllMessagesTableDataById,
+ getAllGroupsById,
+} = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { getScrollSetting } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
+
+const ConsoleOutput = createClass({
+
+ displayName: "ConsoleOutput",
+
+ propTypes: {
+ messages: PropTypes.object.isRequired,
+ messagesUi: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ attachRefToHud: PropTypes.func.isRequired,
+ }),
+ autoscroll: PropTypes.bool.isRequired,
+ },
+
+ componentDidMount() {
+ scrollToBottom(this.outputNode);
+ this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
+ },
+
+ componentWillUpdate(nextProps, nextState) {
+ if (!this.outputNode) {
+ return;
+ }
+
+ const outputNode = this.outputNode;
+
+ // Figure out if we are at the bottom. If so, then any new message should be scrolled
+ // into view.
+ if (this.props.autoscroll && outputNode.lastChild) {
+ this.shouldScrollBottom = isScrolledToBottom(outputNode.lastChild, outputNode);
+ }
+ },
+
+ componentDidUpdate() {
+ if (this.shouldScrollBottom) {
+ scrollToBottom(this.outputNode);
+ }
+ },
+
+ render() {
+ let {
+ dispatch,
+ autoscroll,
+ messages,
+ messagesUi,
+ messagesTableData,
+ serviceContainer,
+ groups,
+ } = this.props;
+
+ let messageNodes = messages.map((message) => {
+ const parentGroups = message.groupId ? (
+ (groups.get(message.groupId) || [])
+ .concat([message.groupId])
+ ) : [];
+
+ return (
+ MessageContainer({
+ dispatch,
+ message,
+ key: message.id,
+ serviceContainer,
+ open: messagesUi.includes(message.id),
+ tableData: messagesTableData.get(message.id),
+ autoscroll,
+ indent: parentGroups.length,
+ })
+ );
+ });
+ return (
+ dom.div({
+ className: "webconsole-output",
+ ref: node => {
+ this.outputNode = node;
+ },
+ }, messageNodes
+ )
+ );
+ }
+});
+
+function scrollToBottom(node) {
+ node.scrollTop = node.scrollHeight;
+}
+
+function isScrolledToBottom(outputNode, scrollNode) {
+ let lastNodeHeight = outputNode.lastChild ?
+ outputNode.lastChild.clientHeight : 0;
+ return scrollNode.scrollTop + scrollNode.clientHeight >=
+ scrollNode.scrollHeight - lastNodeHeight / 2;
+}
+
+function mapStateToProps(state, props) {
+ return {
+ messages: getAllMessages(state),
+ messagesUi: getAllMessagesUiById(state),
+ messagesTableData: getAllMessagesTableDataById(state),
+ autoscroll: getScrollSetting(state),
+ groups: getAllGroupsById(state),
+ };
+}
+
+module.exports = connect(mapStateToProps)(ConsoleOutput);
diff --git a/devtools/client/webconsole/new-console-output/components/console-table.js b/devtools/client/webconsole/new-console-output/components/console-table.js
new file mode 100644
index 000000000..bf8fdcbd8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/console-table.js
@@ -0,0 +1,202 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { ObjectClient } = require("devtools/shared/client/main");
+const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+
+const TABLE_ROW_MAX_ITEMS = 1000;
+const TABLE_COLUMN_MAX_ITEMS = 10;
+
+const ConsoleTable = createClass({
+
+ displayName: "ConsoleTable",
+
+ propTypes: {
+ dispatch: PropTypes.func.isRequired,
+ parameters: PropTypes.array.isRequired,
+ serviceContainer: PropTypes.shape({
+ hudProxyClient: PropTypes.object.isRequired,
+ }),
+ id: PropTypes.string.isRequired,
+ },
+
+ componentWillMount: function () {
+ const {id, dispatch, serviceContainer, parameters} = this.props;
+
+ if (!Array.isArray(parameters) || parameters.length === 0) {
+ return;
+ }
+
+ const client = new ObjectClient(serviceContainer.hudProxyClient, parameters[0]);
+ let dataType = getParametersDataType(parameters);
+
+ // Get all the object properties.
+ dispatch(actions.messageTableDataGet(id, client, dataType));
+ },
+
+ getHeaders: function (columns) {
+ let headerItems = [];
+ columns.forEach((value, key) => headerItems.push(dom.th({}, value)));
+ return headerItems;
+ },
+
+ getRows: function (columns, items) {
+ return items.map(item => {
+ let cells = [];
+ columns.forEach((value, key) => {
+ cells.push(
+ dom.td(
+ {},
+ GripMessageBody({
+ grip: item[key]
+ })
+ )
+ );
+ });
+ return dom.tr({}, cells);
+ });
+ },
+
+ render: function () {
+ const {parameters, tableData} = this.props;
+ const headersGrip = parameters[1];
+ const headers = headersGrip && headersGrip.preview ? headersGrip.preview.items : null;
+
+ // if tableData is nullable, we don't show anything.
+ if (!tableData) {
+ return null;
+ }
+
+ const {columns, items} = getTableItems(
+ tableData,
+ getParametersDataType(parameters),
+ headers
+ );
+
+ return (
+ dom.table({className: "new-consoletable devtools-monospace"},
+ dom.thead({}, this.getHeaders(columns)),
+ dom.tbody({}, this.getRows(columns, items))
+ )
+ );
+ }
+});
+
+function getParametersDataType(parameters = null) {
+ if (!Array.isArray(parameters) || parameters.length === 0) {
+ return null;
+ }
+ return parameters[0].class;
+}
+
+function getTableItems(data = {}, type, headers = null) {
+ const INDEX_NAME = "_index";
+ const VALUE_NAME = "_value";
+ const namedIndexes = {
+ [INDEX_NAME]: (
+ ["Object", "Array"].includes(type) ?
+ l10n.getStr("table.index") : l10n.getStr("table.iterationIndex")
+ ),
+ [VALUE_NAME]: l10n.getStr("table.value"),
+ key: l10n.getStr("table.key")
+ };
+
+ let columns = new Map();
+ let items = [];
+
+ let addItem = function (item) {
+ items.push(item);
+ Object.keys(item).forEach(key => addColumn(key));
+ };
+
+ let addColumn = function (columnIndex) {
+ let columnExists = columns.has(columnIndex);
+ let hasMaxColumns = columns.size == TABLE_COLUMN_MAX_ITEMS;
+ let hasCustomHeaders = Array.isArray(headers);
+
+ if (
+ !columnExists &&
+ !hasMaxColumns && (
+ !hasCustomHeaders ||
+ headers.includes(columnIndex) ||
+ columnIndex === INDEX_NAME
+ )
+ ) {
+ columns.set(columnIndex, namedIndexes[columnIndex] || columnIndex);
+ }
+ };
+
+ for (let index of Object.keys(data)) {
+ if (type !== "Object" && index == parseInt(index, 10)) {
+ index = parseInt(index, 10);
+ }
+
+ let item = {
+ [INDEX_NAME]: index
+ };
+
+ let property = data[index].value;
+
+ if (property.preview) {
+ let {preview} = property;
+ let entries = preview.ownProperties || preview.items;
+ if (entries) {
+ for (let key of Object.keys(entries)) {
+ let entry = entries[key];
+ item[key] = entry.value || entry;
+ }
+ } else {
+ if (preview.key) {
+ item.key = preview.key;
+ }
+
+ item[VALUE_NAME] = preview.value || property;
+ }
+ } else {
+ item[VALUE_NAME] = property;
+ }
+
+ addItem(item);
+
+ if (items.length === TABLE_ROW_MAX_ITEMS) {
+ break;
+ }
+ }
+
+ // Some headers might not be present in the items, so we make sure to
+ // return all the headers set by the user.
+ if (Array.isArray(headers)) {
+ headers.forEach(header => addColumn(header));
+ }
+
+ // We want to always have the index column first
+ if (columns.has(INDEX_NAME)) {
+ let index = columns.get(INDEX_NAME);
+ columns.delete(INDEX_NAME);
+ columns = new Map([[INDEX_NAME, index], ...columns.entries()]);
+ }
+
+ // We want to always have the values column last
+ if (columns.has(VALUE_NAME)) {
+ let index = columns.get(VALUE_NAME);
+ columns.delete(VALUE_NAME);
+ columns.set(VALUE_NAME, index);
+ }
+
+ return {
+ columns,
+ items
+ };
+}
+
+module.exports = ConsoleTable;
diff --git a/devtools/client/webconsole/new-console-output/components/filter-bar.js b/devtools/client/webconsole/new-console-output/components/filter-bar.js
new file mode 100644
index 000000000..a386a414a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ createFactory,
+ createClass,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const { filterTextSet, filtersClear } = require("devtools/client/webconsole/new-console-output/actions/index");
+const { messagesClear } = require("devtools/client/webconsole/new-console-output/actions/index");
+const uiActions = require("devtools/client/webconsole/new-console-output/actions/index");
+const {
+ MESSAGE_LEVEL
+} = require("../constants");
+const FilterButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-button"));
+
+const FilterBar = createClass({
+
+ displayName: "FilterBar",
+
+ propTypes: {
+ filter: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ attachRefToHud: PropTypes.func.isRequired,
+ }).isRequired,
+ ui: PropTypes.object.isRequired
+ },
+
+ componentDidMount() {
+ this.props.serviceContainer.attachRefToHud("filterBox",
+ this.wrapperNode.querySelector(".text-filter"));
+ },
+
+ onClickMessagesClear: function () {
+ this.props.dispatch(messagesClear());
+ },
+
+ onClickFilterBarToggle: function () {
+ this.props.dispatch(uiActions.filterBarToggle());
+ },
+
+ onClickFiltersClear: function () {
+ this.props.dispatch(filtersClear());
+ },
+
+ onSearchInput: function (e) {
+ this.props.dispatch(filterTextSet(e.target.value));
+ },
+
+ render() {
+ const {dispatch, filter, ui} = this.props;
+ let filterBarVisible = ui.filterBarVisible;
+ let children = [];
+
+ children.push(dom.div({className: "devtools-toolbar webconsole-filterbar-primary"},
+ dom.button({
+ className: "devtools-button devtools-clear-icon",
+ title: "Clear output",
+ onClick: this.onClickMessagesClear
+ }),
+ dom.button({
+ className: "devtools-button devtools-filter-icon" + (
+ filterBarVisible ? " checked" : ""),
+ title: "Toggle filter bar",
+ onClick: this.onClickFilterBarToggle
+ }),
+ dom.input({
+ className: "devtools-plaininput text-filter",
+ type: "search",
+ value: filter.text,
+ placeholder: "Filter output",
+ onInput: this.onSearchInput
+ })
+ ));
+
+ if (filterBarVisible) {
+ children.push(
+ dom.div({className: "devtools-toolbar webconsole-filterbar-secondary"},
+ FilterButton({
+ active: filter.error,
+ label: "Errors",
+ filterKey: MESSAGE_LEVEL.ERROR,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.warn,
+ label: "Warnings",
+ filterKey: MESSAGE_LEVEL.WARN,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.log,
+ label: "Logs",
+ filterKey: MESSAGE_LEVEL.LOG,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.info,
+ label: "Info",
+ filterKey: MESSAGE_LEVEL.INFO,
+ dispatch
+ }),
+ FilterButton({
+ active: filter.debug,
+ label: "Debug",
+ filterKey: MESSAGE_LEVEL.DEBUG,
+ dispatch
+ }),
+ dom.span({
+ className: "devtools-separator",
+ }),
+ FilterButton({
+ active: filter.netxhr,
+ label: "XHR",
+ filterKey: "netxhr",
+ dispatch
+ }),
+ FilterButton({
+ active: filter.net,
+ label: "Requests",
+ filterKey: "net",
+ dispatch
+ })
+ )
+ );
+ }
+
+ if (ui.filteredMessageVisible) {
+ children.push(
+ dom.div({className: "devtools-toolbar"},
+ dom.span({
+ className: "clear"},
+ "You have filters set that may hide some results. " +
+ "Learn more about our filtering syntax ",
+ dom.a({}, "here"),
+ "."),
+ dom.button({
+ className: "menu-filter-button",
+ onClick: this.onClickFiltersClear
+ }, "Remove filters")
+ )
+ );
+ }
+
+ return (
+ dom.div({
+ className: "webconsole-filteringbar-wrapper",
+ ref: node => {
+ this.wrapperNode = node;
+ }
+ }, ...children
+ )
+ );
+ }
+});
+
+function mapStateToProps(state) {
+ return {
+ filter: getAllFilters(state),
+ ui: getAllUi(state)
+ };
+}
+
+module.exports = connect(mapStateToProps)(FilterBar);
diff --git a/devtools/client/webconsole/new-console-output/components/filter-button.js b/devtools/client/webconsole/new-console-output/components/filter-button.js
new file mode 100644
index 000000000..4116bb524
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-button.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ createClass,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+
+const FilterButton = createClass({
+
+ displayName: "FilterButton",
+
+ propTypes: {
+ label: PropTypes.string.isRequired,
+ filterKey: PropTypes.string.isRequired,
+ active: PropTypes.bool.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ },
+
+ onClick: function () {
+ this.props.dispatch(actions.filterToggle(this.props.filterKey));
+ },
+
+ render() {
+ const {active, label, filterKey} = this.props;
+
+ let classList = [
+ "menu-filter-button",
+ filterKey,
+ ];
+ if (active) {
+ classList.push("checked");
+ }
+
+ return dom.button({
+ className: classList.join(" "),
+ onClick: this.onClick
+ }, label);
+ }
+});
+
+module.exports = FilterButton;
diff --git a/devtools/client/webconsole/new-console-output/components/grip-message-body.js b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
new file mode 100644
index 000000000..29c2e6a4f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// If this is being run from Mocha, then the browser loader hasn't set up
+// define. We need to do that before loading Rep.
+if (typeof define === "undefined") {
+ require("amd-loader");
+}
+
+// React
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+const StringRep = createFactories(require("devtools/client/shared/components/reps/string").StringRep).rep;
+const VariablesViewLink = createFactory(require("devtools/client/webconsole/new-console-output/components/variables-view-link"));
+const { Grip } = require("devtools/client/shared/components/reps/grip");
+
+GripMessageBody.displayName = "GripMessageBody";
+
+GripMessageBody.propTypes = {
+ grip: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.object,
+ ]).isRequired,
+ serviceContainer: PropTypes.shape({
+ createElement: PropTypes.func.isRequired,
+ }),
+ userProvidedStyle: PropTypes.string,
+};
+
+function GripMessageBody(props) {
+ const { grip, userProvidedStyle, serviceContainer } = props;
+
+ let styleObject;
+ if (userProvidedStyle && userProvidedStyle !== "") {
+ styleObject = cleanupStyle(userProvidedStyle, serviceContainer.createElement);
+ }
+
+ return (
+ // @TODO once there is a longString rep, also turn off quotes for those.
+ typeof grip === "string"
+ ? StringRep({
+ object: grip,
+ useQuotes: false,
+ mode: props.mode,
+ style: styleObject
+ })
+ : Rep({
+ object: grip,
+ objectLink: VariablesViewLink,
+ defaultRep: Grip,
+ mode: props.mode,
+ })
+ );
+}
+
+function cleanupStyle(userProvidedStyle, createElement) {
+ // Regular expression that matches the allowed CSS property names.
+ const allowedStylesRegex = new RegExp(
+ "^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|" +
+ "margin|padding|text|transition|outline|white-space|word|writing|" +
+ "(?:min-|max-)?width|(?:min-|max-)?height)"
+ );
+
+ // Regular expression that matches the forbidden CSS property values.
+ const forbiddenValuesRegexs = [
+ // url(), -moz-element()
+ /\b(?:url|(?:-moz-)?element)[\s('"]+/gi,
+
+ // various URL protocols
+ /['"(]*(?:chrome|resource|about|app|data|https?|ftp|file):+\/*/gi,
+ ];
+
+ // Use a dummy element to parse the style string.
+ let dummy = createElement("div");
+ dummy.style = userProvidedStyle;
+
+ // Return a style object as expected by React DOM components, e.g.
+ // {color: "red"}
+ // without forbidden properties and values.
+ return [...dummy.style]
+ .filter(name => {
+ return allowedStylesRegex.test(name)
+ && !forbiddenValuesRegexs.some(regex => regex.test(dummy.style[name]));
+ })
+ .reduce((object, name) => {
+ return Object.assign({
+ [name]: dummy.style[name]
+ }, object);
+ }, {});
+}
+
+module.exports = GripMessageBody;
diff --git a/devtools/client/webconsole/new-console-output/components/message-container.js b/devtools/client/webconsole/new-console-output/components/message-container.js
new file mode 100644
index 000000000..115e9e291
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+
+const {
+ MESSAGE_SOURCE,
+ MESSAGE_TYPE
+} = require("devtools/client/webconsole/new-console-output/constants");
+
+const componentMap = new Map([
+ ["ConsoleApiCall", require("./message-types/console-api-call")],
+ ["ConsoleCommand", require("./message-types/console-command")],
+ ["DefaultRenderer", require("./message-types/default-renderer")],
+ ["EvaluationResult", require("./message-types/evaluation-result")],
+ ["NetworkEventMessage", require("./message-types/network-event-message")],
+ ["PageError", require("./message-types/page-error")]
+]);
+
+const MessageContainer = createClass({
+ displayName: "MessageContainer",
+
+ propTypes: {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool.isRequired,
+ serviceContainer: PropTypes.object.isRequired,
+ autoscroll: PropTypes.bool.isRequired,
+ indent: PropTypes.number.isRequired,
+ },
+
+ getDefaultProps: function () {
+ return {
+ open: false,
+ indent: 0,
+ };
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ const repeatChanged = this.props.message.repeat !== nextProps.message.repeat;
+ const openChanged = this.props.open !== nextProps.open;
+ const tableDataChanged = this.props.tableData !== nextProps.tableData;
+ return repeatChanged || openChanged || tableDataChanged;
+ },
+
+ render() {
+ const { message } = this.props;
+
+ let MessageComponent = createFactory(getMessageComponent(message));
+ return MessageComponent(this.props);
+ }
+});
+
+function getMessageComponent(message) {
+ switch (message.source) {
+ case MESSAGE_SOURCE.CONSOLE_API:
+ return componentMap.get("ConsoleApiCall");
+ case MESSAGE_SOURCE.NETWORK:
+ return componentMap.get("NetworkEventMessage");
+ case MESSAGE_SOURCE.JAVASCRIPT:
+ switch (message.type) {
+ case MESSAGE_TYPE.COMMAND:
+ return componentMap.get("ConsoleCommand");
+ case MESSAGE_TYPE.RESULT:
+ return componentMap.get("EvaluationResult");
+ // @TODO this is probably not the right behavior, but works for now.
+ // Chrome doesn't distinguish between page errors and log messages. We
+ // may want to remove the PageError component and just handle errors
+ // with ConsoleApiCall.
+ case MESSAGE_TYPE.LOG:
+ return componentMap.get("PageError");
+ default:
+ return componentMap.get("DefaultRenderer");
+ }
+ }
+
+ return componentMap.get("DefaultRenderer");
+}
+
+module.exports.MessageContainer = MessageContainer;
+
+// Exported so we can test it with unit tests.
+module.exports.getMessageComponent = getMessageComponent;
diff --git a/devtools/client/webconsole/new-console-output/components/message-icon.js b/devtools/client/webconsole/new-console-output/components/message-icon.js
new file mode 100644
index 000000000..b4c32fda0
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-icon.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+MessageIcon.displayName = "MessageIcon";
+
+MessageIcon.propTypes = {
+ level: PropTypes.string.isRequired,
+};
+
+function MessageIcon(props) {
+ const { level } = props;
+
+ const title = l10n.getStr("level." + level);
+ return dom.div({
+ className: "icon",
+ title
+ });
+}
+
+module.exports = MessageIcon;
diff --git a/devtools/client/webconsole/new-console-output/components/message-indent.js b/devtools/client/webconsole/new-console-output/components/message-indent.js
new file mode 100644
index 000000000..354e13589
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-indent.js
@@ -0,0 +1,37 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ DOM: dom,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const INDENT_WIDTH = 12;
+const MessageIndent = createClass({
+
+ displayName: "MessageIndent",
+
+ propTypes: {
+ indent: PropTypes.number.isRequired,
+ },
+
+ render: function () {
+ const { indent } = this.props;
+ return dom.span({
+ className: "indent",
+ style: {"width": indent * INDENT_WIDTH}
+ });
+ }
+});
+
+module.exports.MessageIndent = MessageIndent;
+
+// Exported so we can test it with unit tests.
+module.exports.INDENT_WIDTH = INDENT_WIDTH;
diff --git a/devtools/client/webconsole/new-console-output/components/message-repeat.js b/devtools/client/webconsole/new-console-output/components/message-repeat.js
new file mode 100644
index 000000000..1820340ea
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-repeat.js
@@ -0,0 +1,36 @@
+
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { PluralForm } = require("devtools/shared/plural-form");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+MessageRepeat.displayName = "MessageRepeat";
+
+MessageRepeat.propTypes = {
+ repeat: PropTypes.number.isRequired
+};
+
+function MessageRepeat(props) {
+ const { repeat } = props;
+ const visibility = repeat > 1 ? "visible" : "hidden";
+
+ return dom.span({
+ className: "message-repeats",
+ style: {visibility},
+ title: PluralForm.get(repeat, l10n.getStr("messageRepeats.tooltip2"))
+ .replace("#1", repeat)
+ }, repeat);
+}
+
+module.exports = MessageRepeat;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
new file mode 100644
index 000000000..7200648fa
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -0,0 +1,132 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+const ConsoleTable = createFactory(require("devtools/client/webconsole/new-console-output/components/console-table"));
+const {isGroupType, l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+ConsoleApiCall.displayName = "ConsoleApiCall";
+
+ConsoleApiCall.propTypes = {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool,
+ serviceContainer: PropTypes.object.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+ConsoleApiCall.defaultProps = {
+ open: false,
+ indent: 0,
+};
+
+function ConsoleApiCall(props) {
+ const {
+ dispatch,
+ message,
+ open,
+ tableData,
+ serviceContainer,
+ indent,
+ } = props;
+ const {
+ id: messageId,
+ source,
+ type,
+ level,
+ repeat,
+ stacktrace,
+ frame,
+ parameters,
+ messageText,
+ userProvidedStyles,
+ } = message;
+
+ let messageBody;
+ if (type === "trace") {
+ messageBody = dom.span({className: "cm-variable"}, "console.trace()");
+ } else if (type === "assert") {
+ let reps = formatReps(parameters);
+ messageBody = dom.span({ className: "cm-variable" }, "Assertion failed: ", reps);
+ } else if (type === "table") {
+ // TODO: Chrome does not output anything, see if we want to keep this
+ messageBody = dom.span({className: "cm-variable"}, "console.table()");
+ } else if (parameters) {
+ messageBody = formatReps(parameters, userProvidedStyles, serviceContainer);
+ } else {
+ messageBody = messageText;
+ }
+
+ let attachment = null;
+ if (type === "table") {
+ attachment = ConsoleTable({
+ dispatch,
+ id: message.id,
+ serviceContainer,
+ parameters: message.parameters,
+ tableData
+ });
+ }
+
+ let collapseTitle = null;
+ if (isGroupType(type)) {
+ collapseTitle = l10n.getStr("groupToggle");
+ }
+
+ const collapsible = isGroupType(type)
+ || (type === "error" && Array.isArray(stacktrace));
+ const topLevelClasses = ["cm-s-mozilla"];
+
+ return Message({
+ messageId,
+ open,
+ collapsible,
+ collapseTitle,
+ source,
+ type,
+ level,
+ topLevelClasses,
+ messageBody,
+ repeat,
+ frame,
+ stacktrace,
+ attachment,
+ serviceContainer,
+ dispatch,
+ indent,
+ });
+}
+
+function formatReps(parameters, userProvidedStyles, serviceContainer) {
+ return (
+ parameters
+ // Get all the grips.
+ .map((grip, key) => GripMessageBody({
+ grip,
+ key,
+ userProvidedStyle: userProvidedStyles ? userProvidedStyles[key] : null,
+ serviceContainer
+ }))
+ // Interleave spaces.
+ .reduce((arr, v, i) => {
+ return i + 1 < parameters.length
+ ? arr.concat(v, dom.span({}, " "))
+ : arr.concat(v);
+ }, [])
+ );
+}
+
+module.exports = ConsoleApiCall;
+
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
new file mode 100644
index 000000000..d87229fa9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+ConsoleCommand.displayName = "ConsoleCommand";
+
+ConsoleCommand.propTypes = {
+ message: PropTypes.object.isRequired,
+ autoscroll: PropTypes.bool.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+ConsoleCommand.defaultProps = {
+ indent: 0,
+};
+
+/**
+ * Displays input from the console.
+ */
+function ConsoleCommand(props) {
+ const { autoscroll, indent, message } = props;
+ const {
+ source,
+ type,
+ level,
+ messageText: messageBody,
+ } = message;
+
+ const {
+ serviceContainer,
+ } = props;
+
+ const childProps = {
+ source,
+ type,
+ level,
+ topLevelClasses: [],
+ messageBody,
+ scrollToMessage: autoscroll,
+ serviceContainer,
+ indent: indent,
+ };
+ return Message(childProps);
+}
+
+module.exports = ConsoleCommand;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
new file mode 100644
index 000000000..d07089531
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/default-renderer.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+} = require("devtools/client/shared/vendor/react");
+
+DefaultRenderer.displayName = "DefaultRenderer";
+
+function DefaultRenderer(props) {
+ return dom.div({},
+ "This message type is not supported yet."
+ );
+}
+
+module.exports = DefaultRenderer;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
new file mode 100644
index 000000000..992dc62cf
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
+
+EvaluationResult.displayName = "EvaluationResult";
+
+EvaluationResult.propTypes = {
+ message: PropTypes.object.isRequired,
+ indent: PropTypes.number.isRequired,
+};
+
+EvaluationResult.defaultProps = {
+ indent: 0,
+};
+
+function EvaluationResult(props) {
+ const { message, serviceContainer, indent } = props;
+ const {
+ source,
+ type,
+ level,
+ id: messageId,
+ exceptionDocURL,
+ frame,
+ } = message;
+
+ let messageBody;
+ if (message.messageText) {
+ messageBody = message.messageText;
+ } else {
+ messageBody = GripMessageBody({grip: message.parameters});
+ }
+
+ const topLevelClasses = ["cm-s-mozilla"];
+
+ const childProps = {
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ messageId,
+ scrollToMessage: props.autoscroll,
+ serviceContainer,
+ exceptionDocURL,
+ frame,
+ };
+ return Message(childProps);
+}
+
+module.exports = EvaluationResult;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/moz.build b/devtools/client/webconsole/new-console-output/components/message-types/moz.build
new file mode 100644
index 000000000..9b9f72017
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/moz.build
@@ -0,0 +1,13 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'console-api-call.js',
+ 'console-command.js',
+ 'default-renderer.js',
+ 'evaluation-result.js',
+ 'network-event-message.js',
+ 'page-error.js',
+)
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
new file mode 100644
index 000000000..e3c81a487
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+
+NetworkEventMessage.displayName = "NetworkEventMessage";
+
+NetworkEventMessage.propTypes = {
+ message: PropTypes.object.isRequired,
+ serviceContainer: PropTypes.shape({
+ openNetworkPanel: PropTypes.func.isRequired,
+ }),
+ indent: PropTypes.number.isRequired,
+};
+
+NetworkEventMessage.defaultProps = {
+ indent: 0,
+};
+
+function NetworkEventMessage(props) {
+ const { message, serviceContainer, indent } = props;
+ const { actor, source, type, level, request, isXHR } = message;
+
+ const topLevelClasses = [ "cm-s-mozilla" ];
+
+ function onUrlClick() {
+ serviceContainer.openNetworkPanel(actor);
+ }
+
+ const method = dom.span({className: "method" }, request.method);
+ const xhr = isXHR
+ ? dom.span({ className: "xhr" }, l10n.getStr("webConsoleXhrIndicator"))
+ : null;
+ const url = dom.a({ className: "url", title: request.url, onClick: onUrlClick },
+ request.url.replace(/\?.+/, ""));
+
+ const messageBody = dom.span({}, method, xhr, url);
+
+ const childProps = {
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ serviceContainer,
+ };
+ return Message(childProps);
+}
+
+module.exports = NetworkEventMessage;
diff --git a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
new file mode 100644
index 000000000..77ea75ff7
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createFactory,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message"));
+
+PageError.displayName = "PageError";
+
+PageError.propTypes = {
+ message: PropTypes.object.isRequired,
+ open: PropTypes.bool,
+ indent: PropTypes.number.isRequired,
+};
+
+PageError.defaultProps = {
+ open: false,
+ indent: 0,
+};
+
+function PageError(props) {
+ const {
+ dispatch,
+ message,
+ open,
+ serviceContainer,
+ indent,
+ } = props;
+ const {
+ id: messageId,
+ source,
+ type,
+ level,
+ messageText: messageBody,
+ repeat,
+ stacktrace,
+ frame,
+ exceptionDocURL,
+ } = message;
+
+ const childProps = {
+ dispatch,
+ messageId,
+ open,
+ collapsible: Array.isArray(stacktrace),
+ source,
+ type,
+ level,
+ topLevelClasses: [],
+ indent,
+ messageBody,
+ repeat,
+ frame,
+ stacktrace,
+ serviceContainer,
+ exceptionDocURL,
+ };
+ return Message(childProps);
+}
+
+module.exports = PageError;
diff --git a/devtools/client/webconsole/new-console-output/components/message.js b/devtools/client/webconsole/new-console-output/components/message.js
new file mode 100644
index 000000000..f36bff7e4
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -0,0 +1,176 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ createClass,
+ createFactory,
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
+const actions = require("devtools/client/webconsole/new-console-output/actions/index");
+const CollapseButton = createFactory(require("devtools/client/webconsole/new-console-output/components/collapse-button"));
+const MessageIndent = createFactory(require("devtools/client/webconsole/new-console-output/components/message-indent").MessageIndent);
+const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon"));
+const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat"));
+const FrameView = createFactory(require("devtools/client/shared/components/frame"));
+const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
+
+const Message = createClass({
+ displayName: "Message",
+
+ propTypes: {
+ open: PropTypes.bool,
+ collapsible: PropTypes.bool,
+ collapseTitle: PropTypes.string,
+ source: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ level: PropTypes.string.isRequired,
+ indent: PropTypes.number.isRequired,
+ topLevelClasses: PropTypes.array.isRequired,
+ messageBody: PropTypes.any.isRequired,
+ repeat: PropTypes.any,
+ frame: PropTypes.any,
+ attachment: PropTypes.any,
+ stacktrace: PropTypes.any,
+ messageId: PropTypes.string,
+ scrollToMessage: PropTypes.bool,
+ exceptionDocURL: PropTypes.string,
+ serviceContainer: PropTypes.shape({
+ emitNewMessage: PropTypes.func.isRequired,
+ onViewSourceInDebugger: PropTypes.func.isRequired,
+ sourceMapService: PropTypes.any,
+ }),
+ },
+
+ getDefaultProps: function () {
+ return {
+ indent: 0
+ };
+ },
+
+ componentDidMount() {
+ if (this.messageNode) {
+ if (this.props.scrollToMessage) {
+ this.messageNode.scrollIntoView();
+ }
+ // Event used in tests. Some message types don't pass it in because existing tests
+ // did not emit for them.
+ if (this.props.serviceContainer) {
+ this.props.serviceContainer.emitNewMessage(this.messageNode, this.props.messageId);
+ }
+ }
+ },
+
+ onLearnMoreClick: function () {
+ let {exceptionDocURL} = this.props;
+ this.props.serviceContainer.openLink(exceptionDocURL);
+ },
+
+ render() {
+ const {
+ messageId,
+ open,
+ collapsible,
+ collapseTitle,
+ source,
+ type,
+ level,
+ indent,
+ topLevelClasses,
+ messageBody,
+ frame,
+ stacktrace,
+ serviceContainer,
+ dispatch,
+ exceptionDocURL,
+ } = this.props;
+
+ topLevelClasses.push("message", source, type, level);
+ if (open) {
+ topLevelClasses.push("open");
+ }
+
+ const icon = MessageIcon({level});
+
+ // Figure out if there is an expandable part to the message.
+ let attachment = null;
+ if (this.props.attachment) {
+ attachment = this.props.attachment;
+ } else if (stacktrace) {
+ const child = open ? StackTrace({
+ stacktrace: stacktrace,
+ onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger
+ }) : null;
+ attachment = dom.div({ className: "stacktrace devtools-monospace" }, child);
+ }
+
+ // If there is an expandable part, make it collapsible.
+ let collapse = null;
+ if (collapsible) {
+ collapse = CollapseButton({
+ open,
+ title: collapseTitle,
+ onClick: function () {
+ if (open) {
+ dispatch(actions.messageClose(messageId));
+ } else {
+ dispatch(actions.messageOpen(messageId));
+ }
+ },
+ });
+ }
+
+ const repeat = this.props.repeat ? MessageRepeat({repeat: this.props.repeat}) : null;
+
+ // Configure the location.
+ const location = dom.span({ className: "message-location devtools-monospace" },
+ frame ? FrameView({
+ frame,
+ onClick: serviceContainer ? serviceContainer.onViewSourceInDebugger : undefined,
+ showEmptyPathAsHost: true,
+ sourceMapService: serviceContainer ? serviceContainer.sourceMapService : undefined
+ }) : null
+ );
+
+ let learnMore;
+ if (exceptionDocURL) {
+ learnMore = dom.a({
+ className: "learn-more-link webconsole-learn-more-link",
+ title: exceptionDocURL.split("?")[0],
+ onClick: this.onLearnMoreClick,
+ }, `[${l10n.getStr("webConsoleMoreInfoLabel")}]`);
+ }
+
+ return dom.div({
+ className: topLevelClasses.join(" "),
+ ref: node => {
+ this.messageNode = node;
+ }
+ },
+ // @TODO add timestamp
+ MessageIndent({indent}),
+ icon,
+ collapse,
+ dom.span({ className: "message-body-wrapper" },
+ dom.span({ className: "message-flex-body" },
+ dom.span({ className: "message-body devtools-monospace" },
+ messageBody,
+ learnMore
+ ),
+ repeat,
+ location
+ ),
+ attachment
+ )
+ );
+ }
+});
+
+module.exports = Message;
diff --git a/devtools/client/webconsole/new-console-output/components/moz.build b/devtools/client/webconsole/new-console-output/components/moz.build
new file mode 100644
index 000000000..8c0022314
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -0,0 +1,23 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'message-types'
+]
+
+DevToolsModules(
+ 'collapse-button.js',
+ 'console-output.js',
+ 'console-table.js',
+ 'filter-bar.js',
+ 'filter-button.js',
+ 'grip-message-body.js',
+ 'message-container.js',
+ 'message-icon.js',
+ 'message-indent.js',
+ 'message-repeat.js',
+ 'message.js',
+ 'variables-view-link.js'
+)
diff --git a/devtools/client/webconsole/new-console-output/components/variables-view-link.js b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
new file mode 100644
index 000000000..4d79c322f
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// React & Redux
+const {
+ DOM: dom,
+ PropTypes
+} = require("devtools/client/shared/vendor/react");
+const {openVariablesView} = require("devtools/client/webconsole/new-console-output/utils/variables-view");
+
+VariablesViewLink.displayName = "VariablesViewLink";
+
+VariablesViewLink.propTypes = {
+ object: PropTypes.object.isRequired
+};
+
+function VariablesViewLink(props) {
+ const { object, children } = props;
+
+ return (
+ dom.a({
+ onClick: openVariablesView.bind(null, object),
+ className: "cm-variable",
+ draggable: false,
+ }, children)
+ );
+}
+
+module.exports = VariablesViewLink;
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..ef11d6eb8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -0,0 +1,81 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..7d0905aaa
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/moz.build
@@ -0,0 +1,21 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ '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..cd5f4bf7c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/filters.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..6ab10d565
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/index.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..0693fed60
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -0,0 +1,135 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..651512f85
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ '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..0707105e1
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/prefs.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..aa91dceeb
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..36afa60cc
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/filters.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..c4b1aee28
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -0,0 +1,168 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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()))
+ );
+}
+
+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..547f53542
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ '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..18d8b678c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/prefs.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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..c9729e92d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/ui.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..93f3a9ea5
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -0,0 +1,126 @@
+/* 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`);
+ });
+});
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..ff41d6c80
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ '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..fc859a002
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..507201a24
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..cc018f634
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..9323e0031
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..be988b9d8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -0,0 +1,192 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..4b4e8a1d8
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ '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..f79548e7b
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
@@ -0,0 +1,148 @@
+/* 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()
+`);
+
+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..26e95fe39
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -0,0 +1,1482 @@
+/* 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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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": 804,
+ "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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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\":[]}",
+ "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": []
+}));
+
+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')\"]}",
+ "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')"
+ ]
+}));
+
+
+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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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": 804,
+ "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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+ "userContextId": 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,
+} \ No newline at end of file
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..098086044
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/evaluationResult.js
@@ -0,0 +1,182 @@
+/* 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}",
+ "stacktrace": null,
+ "frame": null,
+ "groupId": null,
+ "userProvidedStyles": 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}",
+ "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
+}));
+
+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}",
+ "stacktrace": null,
+ "frame": {
+ "source": "debugger eval code",
+ "line": 1,
+ "column": 4
+ },
+ "groupId": null,
+ "userProvidedStyles": 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
+});
+
+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
+});
+
+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
+});
+
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+} \ No newline at end of file
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..eda8e8b83
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/pageError.js
@@ -0,0 +1,102 @@
+/* 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\"}",
+ "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"
+}));
+
+
+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
+ }
+ ]
+ }
+});
+
+
+module.exports = {
+ stubPreparedMessages,
+ stubPackets,
+} \ No newline at end of file
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..0bfdccc3c
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_batching.js
@@ -0,0 +1,51 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..94de78f13
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_group.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..a90ae1af1
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_console_table.js
@@ -0,0 +1,173 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..8eb536926
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_filters.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..4280270dd
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_init.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..7660df238
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..1038194b9
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -0,0 +1,71 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..5225a6ac1
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_observer_notifications.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..712a990b4
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_vview_close_on_esc_key.js
@@ -0,0 +1,46 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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..b71eaec4f
--- /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 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 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..da06c3162
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/moz.build
@@ -0,0 +1,17 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+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..897ae5d3a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/types.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+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,
+});
+
+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..7d875b750
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/id-generator.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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..f91209e9d
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -0,0 +1,283 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+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,
+ });
+ }
+
+ 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
+ } = 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,
+ });
+ }
+ }
+}
+
+// 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..00378baa4
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ '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..3cfee875a
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/variables-view.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* 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,
+ });
+};