diff options
Diffstat (limited to 'devtools/client/webconsole/new-console-output')
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, + }); +}; |