diff --git a/addon-sdk/source/python-lib/cuddlefish/prefs.py b/addon-sdk/source/python-lib/cuddlefish/prefs.py index 3dea6f8f4ae9..81f48c40e692 100644 --- a/addon-sdk/source/python-lib/cuddlefish/prefs.py +++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py @@ -50,6 +50,7 @@ DEFAULT_NO_CONNECTIONS_PREFS = { 'media.gmp-manager.cert.requireBuiltIn' : False, 'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager', 'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml', + 'media.gmp-manager.updateEnabled': False, 'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy', 'browser.newtab.url' : 'about:blank', 'browser.search.update': False, diff --git a/addon-sdk/source/test/preferences/no-connections.json b/addon-sdk/source/test/preferences/no-connections.json index 20b20b77c435..370b7909c700 100644 --- a/addon-sdk/source/test/preferences/no-connections.json +++ b/addon-sdk/source/test/preferences/no-connections.json @@ -10,6 +10,7 @@ "media.gmp-manager.cert.requireBuiltIn": false, "media.gmp-manager.url": "http://localhost/media-dummy/gmpmanager", "media.gmp-manager.url.override": "http://localhost/dummy-gmp-manager.xml", + "media.gmp-manager.updateEnabled": false, "browser.aboutHomeSnippets.updateUrl": "https://localhost/snippet-dummy", "browser.newtab.url": "about:blank", "browser.search.update": false, diff --git a/devtools/client/shared/developer-toolbar.js b/devtools/client/shared/developer-toolbar.js index d28089278f89..be57e879c043 100644 --- a/devtools/client/shared/developer-toolbar.js +++ b/devtools/client/shared/developer-toolbar.js @@ -332,6 +332,8 @@ DeveloperToolbar.prototype.createToolbar = function () { toolboxBtn.setAttribute("class", "developer-toolbar-button"); let toolboxTooltip = L10N.getStr("toolbar.toolsButton.tooltip"); toolboxBtn.setAttribute("tooltiptext", toolboxTooltip); + let toolboxOpen = gDevToolsBrowser.hasToolboxOpened(this._chromeWindow); + toolboxBtn.setAttribute("checked", toolboxOpen); toolboxBtn.addEventListener("command", function (event) { let window = event.target.ownerDocument.defaultView; gDevToolsBrowser.toggleToolboxCommand(window.gBrowser); @@ -714,6 +716,8 @@ DeveloperToolbar.prototype.handleEvent = function (ev) { }); if (ev.type == "TabSelect") { + let toolboxOpen = gDevToolsBrowser.hasToolboxOpened(this._chromeWindow); + this._errorCounterButton.setAttribute("checked", toolboxOpen); this._initErrorsCount(ev.target); } } diff --git a/devtools/client/storage/test/browser_storage_values.js b/devtools/client/storage/test/browser_storage_values.js index 8531d321d450..920ce350eb17 100644 --- a/devtools/client/storage/test/browser_storage_values.js +++ b/devtools/client/storage/test/browser_storage_values.js @@ -42,6 +42,14 @@ const testCases = [ {name: "c1.2", value: "Object"}, {name: "c1.2.foo", value: "Bar"}, ], true], + ["c_encoded", [ + {name: "c_encoded", value: encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}}))} + ]], + [null, [ + {name: "c_encoded", value: "Object"}, + {name: "c_encoded.foo", value: "Object"}, + {name: "c_encoded.foo.foo1", value: "bar"} + ], true], [["localStorage", "http://test1.example.org"]], ["ls2", [ {name: "ls2", value: "foobar-2"} diff --git a/devtools/client/storage/test/storage-complex-values.html b/devtools/client/storage/test/storage-complex-values.html index bf5131f70c9d..d96da1932f0d 100644 --- a/devtools/client/storage/test/storage-complex-values.html +++ b/devtools/client/storage/test/storage-complex-values.html @@ -19,6 +19,9 @@ document.cookie = "c1=" + JSON.stringify([ }]) + "; expires=" + new Date(cookieExpiresTime).toGMTString() + "; path=/browser"; document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname; +// URLEncoded cookie +document.cookie = "c_encoded=" + encodeURIComponent(JSON.stringify({foo: {foo1: "bar"}})); + // ... and some local storage items .. const es6 = "for"; localStorage.setItem("ls1", JSON.stringify({ diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js index 31d4d4115ee2..b0af7104941f 100644 --- a/devtools/client/storage/ui.js +++ b/devtools/client/storage/ui.js @@ -645,7 +645,17 @@ StorageUI.prototype = { * @param {string} value * The string to be parsed into an object */ - parseItemValue: function (name, value) { + parseItemValue: function (name, originalValue) { + // Find if value is URLEncoded ie + let decodedValue = ""; + try { + decodedValue = decodeURIComponent(originalValue); + } catch (e) { + // Unable to decode, nothing to do + } + let value = (decodedValue && decodedValue !== originalValue) + ? decodedValue : originalValue; + let json = null; try { json = JSOL.parse(value); diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index b534e3e599aa..600df05bb4dd 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -723,3 +723,39 @@ a.learn-more-link.webconsole-learn-more-link { .message.network .method { margin-inline-end: 5px; } + +/* console.table() */ +.new-consoletable { + width: 100%; + border-collapse: collapse; + --consoletable-border: 1px solid var(--table-splitter-color); +} + +.new-consoletable thead, +.new-consoletable tbody { + background-color: var(--theme-body-background); +} + +.new-consoletable th { + background-color: var(--theme-selection-background); + color: var(--theme-selection-color); + margin: 0; + padding: 5px 0 0; + font-weight: inherit; + border-inline-end: var(--consoletable-border); + border-bottom: var(--consoletable-border); +} + +.new-consoletable tr:nth-of-type(even) { + background-color: var(--table-zebra-background); +} + +.new-consoletable td { + padding: 3px 4px; + min-width: 100px; + -moz-user-focus: normal; + color: var(--theme-body-color); + border-inline-end: var(--consoletable-border); + height: 1.25em; + line-height: 1.25em; +} 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 000000000000..5553942e211b --- /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/index.js b/devtools/client/webconsole/new-console-output/actions/index.js new file mode 100644 index 000000000000..5ce76a402a52 --- /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 index 4db49edcc114..4aec73d99d93 100644 --- a/devtools/client/webconsole/new-console-output/actions/messages.js +++ b/devtools/client/webconsole/new-console-output/actions/messages.js @@ -10,33 +10,35 @@ 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) { - return (dispatch) => { - if (idGenerator == null) { - idGenerator = defaultIdGenerator; - } - let message = prepareMessage(packet, idGenerator); - - if (message.type === MESSAGE_TYPE.CLEAR) { - dispatch(messagesClear()); - } - - dispatch({ - type: MESSAGE_ADD, - message - }); + 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() { @@ -59,7 +61,39 @@ function messageClose(id) { }; } -exports.messageAdd = messageAdd; -exports.messagesClear = messagesClear; -exports.messageOpen = messageOpen; -exports.messageClose = messageClose; +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 index fc89ca3d960e..c7a8ed52cac3 100644 --- a/devtools/client/webconsole/new-console-output/actions/moz.build +++ b/devtools/client/webconsole/new-console-output/actions/moz.build @@ -4,7 +4,9 @@ # 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/components/console-output.js b/devtools/client/webconsole/new-console-output/components/console-output.js index 31273b5de526..897ae5101fde 100644 --- a/devtools/client/webconsole/new-console-output/components/console-output.js +++ b/devtools/client/webconsole/new-console-output/components/console-output.js @@ -12,13 +12,13 @@ const { const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { connect } = require("devtools/client/shared/vendor/react-redux"); -const { getAllMessages, getAllMessagesUiById } = require("devtools/client/webconsole/new-console-output/selectors/messages"); +const { getAllMessages, getAllMessagesUiById, getAllMessagesTableDataById } = require("devtools/client/webconsole/new-console-output/selectors/messages"); const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer); const ConsoleOutput = createClass({ propTypes: { - jsterm: PropTypes.object.isRequired, + hudProxyClient: PropTypes.object.isRequired, messages: PropTypes.object.isRequired, messagesUi: PropTypes.object.isRequired, sourceMapService: PropTypes.object, @@ -46,8 +46,10 @@ const ConsoleOutput = createClass({ render() { let { dispatch, + hudProxyClient, messages, messagesUi, + messagesTableData, sourceMapService, onViewSourceInDebugger, openNetworkPanel, @@ -58,6 +60,7 @@ const ConsoleOutput = createClass({ return ( MessageContainer({ dispatch, + hudProxyClient, message, key: message.id, sourceMapService, @@ -65,6 +68,7 @@ const ConsoleOutput = createClass({ openNetworkPanel, openLink, open: messagesUi.includes(message.id), + tableData: messagesTableData.get(message.id), }) ); }); @@ -85,6 +89,7 @@ function mapStateToProps(state) { return { messages: getAllMessages(state), messagesUi: getAllMessagesUiById(state), + messagesTableData: getAllMessagesTableDataById(state), }; } 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 000000000000..bf8183fe6b0e --- /dev/null +++ b/devtools/client/webconsole/new-console-output/components/console-table.js @@ -0,0 +1,200 @@ +/* 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").GripMessageBody); + +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, + hudProxyClient: PropTypes.object.isRequired, + id: PropTypes.string.isRequired, + }, + + componentWillMount: function () { + const {id, dispatch, hudProxyClient, parameters} = this.props; + + if (!Array.isArray(parameters) || parameters.length === 0) { + return; + } + + const client = new ObjectClient(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 + }; +} + +exports.ConsoleTable = 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 index a7ef6953288b..2836ef0b16c5 100644 --- a/devtools/client/webconsole/new-console-output/components/filter-bar.js +++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js @@ -12,9 +12,9 @@ const { 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/filters"); -const { messagesClear } = require("devtools/client/webconsole/new-console-output/actions/messages"); -const uiActions = require("devtools/client/webconsole/new-console-output/actions/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"); diff --git a/devtools/client/webconsole/new-console-output/components/filter-button.js b/devtools/client/webconsole/new-console-output/components/filter-button.js index 0643ce895d65..0766126626eb 100644 --- a/devtools/client/webconsole/new-console-output/components/filter-button.js +++ b/devtools/client/webconsole/new-console-output/components/filter-button.js @@ -8,7 +8,7 @@ const { DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); -const actions = require("devtools/client/webconsole/new-console-output/actions/filters"); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); const FilterButton = createClass({ 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 index d7d180461a74..39e4a9864e83 100644 --- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js +++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js @@ -41,12 +41,14 @@ function GripMessageBody(props) { typeof grip === "string" ? StringRep({ object: grip, - useQuotes: false + useQuotes: false, + mode: props.mode, }) : Rep({ object: grip, objectLink: VariablesViewLink, - defaultRep: Grip + defaultRep: Grip, + mode: props.mode, }) ); } diff --git a/devtools/client/webconsole/new-console-output/components/message-container.js b/devtools/client/webconsole/new-console-output/components/message-container.js index 8651127d0c3a..8a5d1aa89a4f 100644 --- a/devtools/client/webconsole/new-console-output/components/message-container.js +++ b/devtools/client/webconsole/new-console-output/components/message-container.js @@ -37,6 +37,7 @@ const MessageContainer = createClass({ openNetworkPanel: PropTypes.func.isRequired, openLink: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, + hudProxyClient: PropTypes.object.isRequired, }, getDefaultProps: function () { @@ -46,8 +47,10 @@ const MessageContainer = createClass({ }, shouldComponentUpdate(nextProps, nextState) { - return this.props.message.repeat !== nextProps.message.repeat - || this.props.open !== nextProps.open; + 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() { @@ -59,6 +62,8 @@ const MessageContainer = createClass({ openNetworkPanel, openLink, open, + tableData, + hudProxyClient, } = this.props; let MessageComponent = createFactory(getMessageComponent(message)); @@ -70,6 +75,8 @@ const MessageContainer = createClass({ openNetworkPanel, openLink, open, + tableData, + hudProxyClient, }); } }); 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 index 322e0eb78723..56e42bd6a2e9 100644 --- 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 @@ -18,7 +18,8 @@ const GripMessageBody = createFactory(require("devtools/client/webconsole/new-co const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat").MessageRepeat); const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon); const CollapseButton = createFactory(require("devtools/client/webconsole/new-console-output/components/collapse-button").CollapseButton); -const actions = require("devtools/client/webconsole/new-console-output/actions/messages"); +const ConsoleTable = createFactory(require("devtools/client/webconsole/new-console-output/components/console-table").ConsoleTable); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); ConsoleApiCall.displayName = "ConsoleApiCall"; @@ -27,6 +28,7 @@ ConsoleApiCall.propTypes = { sourceMapService: PropTypes.object, onViewSourceInDebugger: PropTypes.func.isRequired, open: PropTypes.bool, + hudProxyClient: PropTypes.object.isRequired, }; ConsoleApiCall.defaultProps = { @@ -34,15 +36,26 @@ ConsoleApiCall.defaultProps = { }; function ConsoleApiCall(props) { - const { dispatch, message, sourceMapService, onViewSourceInDebugger, open } = props; - const { source, level, stacktrace, type, frame, parameters } = message; + const { + dispatch, + message, + sourceMapService, + onViewSourceInDebugger, + open, + hudProxyClient, + tableData + } = props; + const {source, level, stacktrace, type, frame, parameters } = message; let messageBody; if (type === "trace") { - messageBody = dom.span({ className: "cm-variable" }, "console.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); } else { @@ -83,6 +96,14 @@ function ConsoleApiCall(props) { } }, }); + } else if (type === "table") { + attachment = ConsoleTable({ + dispatch, + id: message.id, + hudProxyClient, + parameters: message.parameters, + tableData + }); } const classes = ["message", "cm-s-mozilla"]; 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 index bbfbb69318c1..93ab5e1af9aa 100644 --- 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 @@ -15,7 +15,7 @@ const { const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon); const CollapseButton = createFactory(require("devtools/client/webconsole/new-console-output/components/collapse-button").CollapseButton); const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages"); -const actions = require("devtools/client/webconsole/new-console-output/actions/messages"); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); NetworkEventMessage.displayName = "NetworkEventMessage"; @@ -60,17 +60,6 @@ function NetworkEventMessage(props) { // @TODO add timestamp // @TODO add indent if necessary MessageIcon({ level }), - CollapseButton({ - open, - title: l10n.getStr("messageToggleDetails"), - onClick: () => { - if (open) { - dispatch(actions.messageClose(message.id)); - } else { - dispatch(actions.messageOpen(message.id)); - } - }, - }), dom.span({ className: "message-body-wrapper message-body devtools-monospace", "aria-haspopup": "true" @@ -78,8 +67,7 @@ function NetworkEventMessage(props) { dom.span({ className: "method" }, method), isXHR ? dom.span({ className: "xhr" }, xhr) : null, dom.a({ className: "url", title: url, onClick: onUrlClick }, - url.replace(/\?.+/, "")), - dom.a({ className: "status" }, statusInfo) + url.replace(/\?.+/, "")) ) ); } 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 index 143d104976ed..5a7297a9db58 100644 --- 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 @@ -18,7 +18,7 @@ const CollapseButton = createFactory(require("devtools/client/webconsole/new-con const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat").MessageRepeat); const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon").MessageIcon); -const actions = require("devtools/client/webconsole/new-console-output/actions/messages"); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); PageError.displayName = "PageError"; diff --git a/devtools/client/webconsole/new-console-output/components/moz.build b/devtools/client/webconsole/new-console-output/components/moz.build index 5f26e741c459..862751a4933b 100644 --- a/devtools/client/webconsole/new-console-output/components/moz.build +++ b/devtools/client/webconsole/new-console-output/components/moz.build @@ -10,6 +10,7 @@ DIRS += [ DevToolsModules( 'collapse-button.js', 'console-output.js', + 'console-table.js', 'filter-bar.js', 'filter-button.js', 'grip-message-body.js', diff --git a/devtools/client/webconsole/new-console-output/constants.js b/devtools/client/webconsole/new-console-output/constants.js index 7a3ea2ab7af4..54121ecfc910 100644 --- a/devtools/client/webconsole/new-console-output/constants.js +++ b/devtools/client/webconsole/new-console-output/constants.js @@ -6,10 +6,12 @@ "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", 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 index ea4d340f3dc2..8f0fa218d589 100644 --- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js +++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js @@ -8,7 +8,7 @@ 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/messages"); +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")); @@ -17,40 +17,54 @@ const FilterBar = React.createFactory(require("devtools/client/webconsole/new-co const store = configureStore(); function NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner) { - const sourceMapService = toolbox ? toolbox._sourceMapService : null; - let childComponent = ConsoleOutput({ - jsterm, - sourceMapService, - onViewSourceInDebugger: frame => toolbox.viewSourceInDebugger.call( - toolbox, - frame.url, - frame.line - ), - openNetworkPanel: (requestId) => { - return toolbox.selectTool("netmonitor").then(panel => { - return panel.panelWin.NetMonitorController.inspectRequest(requestId); - }); - }, - openLink: (url) => { - owner.openLink(url); - }, - }); - let filterBar = FilterBar({}); - let provider = React.createElement( - Provider, - { store }, - React.DOM.div( - {className: "webconsole-output-wrapper"}, - filterBar, - childComponent - )); - this.body = ReactDOM.render(provider, parentNode); + this.parentNode = parentNode; + this.jsterm = jsterm; + this.toolbox = toolbox; + this.owner = owner; + + this.init = this.init.bind(this); } NewConsoleOutputWrapper.prototype = { + init: function () { + const sourceMapService = this.toolbox ? this.toolbox._sourceMapService : null; + + let childComponent = ConsoleOutput({ + hudProxyClient: this.jsterm.hud.proxy.client, + sourceMapService, + 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); + }); + }, + openLink: (url) => { + this.owner.openLink(url); + }, + }); + let filterBar = FilterBar({}); + let provider = React.createElement( + Provider, + { store }, + React.DOM.div( + {className: "webconsole-output-wrapper"}, + filterBar, + childComponent + )); + + this.body = ReactDOM.render(provider, this.parentNode); + }, dispatchMessageAdd: (message) => { store.dispatch(actions.messageAdd(message)); }, + dispatchMessagesAdd: (messages) => { + const batchedActions = messages.map(message => actions.messageAdd(message)); + store.dispatch(actions.batchActions(batchedActions)); + }, dispatchMessagesClear: () => { store.dispatch(actions.messagesClear()); }, diff --git a/devtools/client/webconsole/new-console-output/reducers/messages.js b/devtools/client/webconsole/new-console-output/reducers/messages.js index df8c741966d9..6826bd2a5a69 100644 --- a/devtools/client/webconsole/new-console-output/reducers/messages.js +++ b/devtools/client/webconsole/new-console-output/reducers/messages.js @@ -11,11 +11,13 @@ const constants = require("devtools/client/webconsole/new-console-output/constan const MessageState = Immutable.Record({ messagesById: Immutable.List(), messagesUiById: Immutable.List(), + messagesTableDataById: Immutable.Map(), }); function messages(state = new MessageState(), action) { const messagesById = state.messagesById; const messagesUiById = state.messagesUiById; + const messagesTableDataById = state.messagesTableDataById; switch (action.type) { case constants.MESSAGE_ADD: @@ -52,6 +54,9 @@ function messages(state = new MessageState(), action) { 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; diff --git a/devtools/client/webconsole/new-console-output/selectors/messages.js b/devtools/client/webconsole/new-console-output/selectors/messages.js index cd244f6c99fe..662491bb8052 100644 --- a/devtools/client/webconsole/new-console-output/selectors/messages.js +++ b/devtools/client/webconsole/new-console-output/selectors/messages.js @@ -34,6 +34,10 @@ function getAllMessagesUiById(state) { return state.messages.messagesUiById; } +function getAllMessagesTableDataById(state) { + return state.messages.messagesTableDataById; +} + function filterLevel(messages, filters) { return messages.filter((message) => { return filters.get(message.level) === true @@ -114,3 +118,4 @@ function prune(messages, logLimit) { exports.getAllMessages = getAllMessages; exports.getAllMessagesUiById = getAllMessagesUiById; +exports.getAllMessagesTableDataById = getAllMessagesTableDataById; diff --git a/devtools/client/webconsole/new-console-output/store.js b/devtools/client/webconsole/new-console-output/store.js index c187182b1aa9..8bc34aa303be 100644 --- a/devtools/client/webconsole/new-console-output/store.js +++ b/devtools/client/webconsole/new-console-output/store.js @@ -5,8 +5,14 @@ const {FilterState} = require("devtools/client/webconsole/new-console-output/reducers/filters"); const {PrefState} = require("devtools/client/webconsole/new-console-output/reducers/prefs"); -const { applyMiddleware, combineReducers, createStore } = require("devtools/client/shared/vendor/redux"); +const { + applyMiddleware, + combineReducers, + compose, + createStore +} = require("devtools/client/shared/vendor/redux"); const { thunk } = require("devtools/client/shared/redux/middleware/thunk"); +const constants = require("devtools/client/webconsole/new-console-output/constants"); const { reducers } = require("./reducers/index"); const Services = require("Services"); @@ -28,10 +34,33 @@ function configureStore() { return createStore( combineReducers(reducers), initialState, - applyMiddleware(thunk) + 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 constants.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/actions/filters.test.js b/devtools/client/webconsole/new-console-output/test/actions/filters.test.js index 6c94f5739811..7ab46770011b 100644 --- a/devtools/client/webconsole/new-console-output/test/actions/filters.test.js +++ b/devtools/client/webconsole/new-console-output/test/actions/filters.test.js @@ -2,7 +2,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -const actions = require("devtools/client/webconsole/new-console-output/actions/filters"); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); const { FILTER_TEXT_SET, FILTER_TOGGLE, diff --git a/devtools/client/webconsole/new-console-output/test/actions/messages.test.js b/devtools/client/webconsole/new-console-output/test/actions/messages.test.js index 493fdd06b046..5bdf2e2400ed 100644 --- a/devtools/client/webconsole/new-console-output/test/actions/messages.test.js +++ b/devtools/client/webconsole/new-console-output/test/actions/messages.test.js @@ -26,16 +26,16 @@ describe("Message actions:", () => { const store = mockStore({}); store.dispatch(actions.messageAdd(packet)); - const expectedActions = store.getActions(); - expect(expectedActions.length).toEqual(1); + const actualActions = store.getActions(); + expect(actualActions.length).toEqual(1); - const addAction = expectedActions[0]; + const addAction = actualActions[0]; const {message} = addAction; - const expected = { + const expectedAction = { type: constants.MESSAGE_ADD, message: stubPreparedMessages.get("console.log('foobar', 'test')") }; - expect(message.toJS()).toEqual(expected.message.toJS()); + expect(message.toJS()).toEqual(expectedAction.message.toJS()); }); it("dispatches expected actions given a console.clear packet", () => { @@ -43,18 +43,35 @@ describe("Message actions:", () => { const store = mockStore({}); store.dispatch(actions.messageAdd(packet)); - const expectedActions = store.getActions(); - expect(expectedActions.length).toEqual(2); + const actualActions = store.getActions(); + expect(actualActions.length).toEqual(1); - const [clearAction, addAction] = expectedActions; + const [clearAction, addAction] = actualActions[0].actions; expect(clearAction.type).toEqual(constants.MESSAGES_CLEAR); const {message} = addAction; - const expected = { + const expectedAction = { type: constants.MESSAGE_ADD, message: stubPreparedMessages.get("console.clear()") }; expect(addAction.type).toEqual(constants.MESSAGE_ADD); + expect(message.toJS()).toEqual(expectedAction.message.toJS()); + }); + + it("dispatches expected action given a console.table packet", () => { + const packet = stubPackets.get("console.table(['a', 'b', 'c'])"); + const store = mockStore({}); + store.dispatch(actions.messageAdd(packet)); + + const expectedActions = store.getActions(); + expect(expectedActions.length).toEqual(1); + + const addAction = expectedActions[0]; + const {message} = addAction; + const expected = { + type: constants.MESSAGE_ADD, + message: stubPreparedMessages.get("console.table(['a', 'b', 'c'])") + }; expect(message.toJS()).toEqual(expected.message.toJS()); }); }); diff --git a/devtools/client/webconsole/new-console-output/test/actions/ui.test.js b/devtools/client/webconsole/new-console-output/test/actions/ui.test.js index b64c622c4ebc..0bb43ae55094 100644 --- a/devtools/client/webconsole/new-console-output/test/actions/ui.test.js +++ b/devtools/client/webconsole/new-console-output/test/actions/ui.test.js @@ -2,7 +2,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -const actions = require("devtools/client/webconsole/new-console-output/actions/ui"); +const actions = require("devtools/client/webconsole/new-console-output/actions/index"); const { FILTER_BAR_TOGGLE } = require("devtools/client/webconsole/new-console-output/constants"); 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 index 9c99064104d5..d22819a2bed1 100644 --- 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 @@ -58,7 +58,7 @@ function timeit(cb) { 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/filters"); + 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"), {}); 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 index f51e7c3a6e04..8365700a7ede 100644 --- 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 @@ -29,7 +29,6 @@ describe("NetworkEventMessage component:", () => { 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(".message-body .status").length).toBe(1); expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1); }); }); @@ -43,7 +42,6 @@ describe("NetworkEventMessage component:", () => { 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(".message-body .status").length).toBe(1); expect(wrapper.find("div.message.cm-s-mozilla span.message-body.devtools-monospace").length).toBe(1); }); }); @@ -58,7 +56,6 @@ describe("NetworkEventMessage component:", () => { 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(".message-body .status").length).toBe(1); 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/fixtures/ObjectClient.js b/devtools/client/webconsole/new-console-output/test/fixtures/ObjectClient.js new file mode 100644 index 000000000000..87a058d5c788 --- /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/stub-generators/stub-snippets.js b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js index 71338e0c7281..18ad754988f3 100644 --- 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 @@ -46,6 +46,17 @@ 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']); +`}); // Evaluation Result const evaluationResultCommands = [ 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 index c6f698f10540..8485805e68dc 100644 --- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js +++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js @@ -331,10 +331,10 @@ stubPreparedMessages.set("console.timeEnd('bar')", new ConsoleMessage({ "source": "console-api", "type": "timeEnd", "level": "log", - "messageText": "bar: 1.63ms", + "messageText": "bar: 1.81ms", "parameters": null, "repeat": 1, - "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"timeEnd\",\"level\":\"log\",\"messageText\":\"bar: 1.63ms\",\"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}}", + "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"type\":\"timeEnd\",\"level\":\"log\",\"messageText\":\"bar: 1.81ms\",\"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}}", "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)", @@ -343,6 +343,63 @@ stubPreparedMessages.set("console.timeEnd('bar')", new ConsoleMessage({ } })); +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}}", + "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 + } +})); + +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.conn14.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.conn14.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}}", + "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 + } +})); + stubPackets.set("console.log('foobar', 'test')", { "from": "server1.conn0.child1/consoleActor2", @@ -370,7 +427,7 @@ stubPackets.set("console.log('foobar', 'test')", { }, "private": false, "styles": [], - "timeStamp": 1474329261562, + "timeStamp": 1474757913492, "timer": null, "workerType": "none", "category": "webdev" @@ -404,7 +461,7 @@ stubPackets.set("console.log(undefined)", { }, "private": false, "styles": [], - "timeStamp": 1474329262588, + "timeStamp": 1474757916196, "timer": null, "workerType": "none", "category": "webdev" @@ -436,7 +493,7 @@ stubPackets.set("console.warn('danger, will robinson!')", { }, "private": false, "styles": [], - "timeStamp": 1474329263650, + "timeStamp": 1474757918499, "timer": null, "workerType": "none", "category": "webdev" @@ -470,7 +527,7 @@ stubPackets.set("console.log(NaN)", { }, "private": false, "styles": [], - "timeStamp": 1474329264822, + "timeStamp": 1474757920577, "timer": null, "workerType": "none", "category": "webdev" @@ -504,7 +561,7 @@ stubPackets.set("console.log(null)", { }, "private": false, "styles": [], - "timeStamp": 1474329265855, + "timeStamp": 1474757922439, "timer": null, "workerType": "none", "category": "webdev" @@ -536,7 +593,7 @@ stubPackets.set("console.log('鼬')", { }, "private": false, "styles": [], - "timeStamp": 1474329266922, + "timeStamp": 1474757924400, "timer": null, "workerType": "none", "category": "webdev" @@ -565,7 +622,7 @@ stubPackets.set("console.clear()", { "userContextId": 0 }, "private": false, - "timeStamp": 1474329267971, + "timeStamp": 1474757926626, "timer": null, "workerType": "none", "styles": [], @@ -600,7 +657,7 @@ stubPackets.set("console.count('bar')", { "userContextId": 0 }, "private": false, - "timeStamp": 1474329269084, + "timeStamp": 1474757929281, "timer": null, "workerType": "none", "styles": [], @@ -654,7 +711,7 @@ stubPackets.set("console.assert(false, {message: 'foobar'})", { }, "private": false, "styles": [], - "timeStamp": 1474329270125, + "timeStamp": 1474757931800, "timer": null, "stacktrace": [ { @@ -695,7 +752,7 @@ stubPackets.set("console.log('hello \nfrom \rthe \"string world!')", { }, "private": false, "styles": [], - "timeStamp": 1474329271256, + "timeStamp": 1474757936217, "timer": null, "workerType": "none", "category": "webdev" @@ -727,7 +784,7 @@ stubPackets.set("console.log('úṇĩçödê țĕșť')", { }, "private": false, "styles": [], - "timeStamp": 1474329272298, + "timeStamp": 1474757938480, "timer": null, "workerType": "none", "category": "webdev" @@ -756,7 +813,7 @@ stubPackets.set("console.trace()", { "userContextId": 0 }, "private": false, - "timeStamp": 1474329273375, + "timeStamp": 1474757940569, "timer": null, "stacktrace": [ { @@ -811,10 +868,10 @@ stubPackets.set("console.time('bar')", { "userContextId": 0 }, "private": false, - "timeStamp": 1474329274410, + "timeStamp": 1474757942740, "timer": { "name": "bar", - "started": 618.57 + "started": 1220.705 }, "workerType": "none", "styles": [], @@ -846,9 +903,9 @@ stubPackets.set("console.timeEnd('bar')", { "userContextId": 0 }, "private": false, - "timeStamp": 1474329274411, + "timeStamp": 1474757942742, "timer": { - "duration": 1.3249999999999318, + "duration": 1.8100000000001728, "name": "bar" }, "workerType": "none", @@ -857,6 +914,87 @@ stubPackets.set("console.timeEnd('bar')", { } }); +stubPackets.set("console.table('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.table(%27bar%27)", + "functionName": "triggerPacket", + "groupName": "", + "level": "table", + "lineNumber": 2, + "originAttributes": { + "addonId": "", + "appId": 0, + "firstPartyDomain": "", + "inIsolatedMozBrowser": false, + "privateBrowsingId": 0, + "signedPkg": "", + "userContextId": 0 + }, + "private": false, + "timeStamp": 1474757944789, + "timer": null, + "workerType": "none", + "styles": [], + "category": "webdev" + } +}); + +stubPackets.set("console.table(['a', 'b', 'c'])", { + "from": "server1.conn14.child1/consoleActor2", + "type": "consoleAPICall", + "message": { + "arguments": [ + { + "type": "object", + "actor": "server1.conn14.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, + "signedPkg": "", + "userContextId": 0 + }, + "private": false, + "timeStamp": 1474757946731, + "timer": null, + "workerType": "none", + "styles": [], + "category": "webdev" + } +}); + module.exports = { stubPreparedMessages, diff --git a/devtools/client/webconsole/new-console-output/test/helpers.js b/devtools/client/webconsole/new-console-output/test/helpers.js index 6ac1768e5bbf..39807eaeda65 100644 --- a/devtools/client/webconsole/new-console-output/test/helpers.js +++ b/devtools/client/webconsole/new-console-output/test/helpers.js @@ -7,7 +7,7 @@ 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/messages"); +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"); diff --git a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini index d7206443f11c..2bc22726eb7d 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini +++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini @@ -4,8 +4,10 @@ subsuite = devtools support-files = head.js !/devtools/client/framework/test/shared-head.js + test-console-table.html test-console.html +[browser_webconsole_console_table.js] [browser_webconsole_init.js] [browser_webconsole_input_focus.js] [browser_webconsole_observer_notifications.js] 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 000000000000..a90ae1af1b3a --- /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_input_focus.js b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_input_focus.js index c972c276157c..7660df2380ff 100644 --- 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 @@ -7,12 +7,51 @@ "use strict"; -const TEST_URI = "data:text/html;charset=utf-8,Test input focus"; +const TEST_URI = + `data:text/html;charset=utf-8,Test input focused + `; 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"); + 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/head.js b/devtools/client/webconsole/new-console-output/test/mochitest/head.js index 8957af0a1b73..9335f2786e24 100644 --- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js +++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js @@ -91,7 +91,7 @@ function waitForMessages({ hud, messages }) { function* waitFor(condition, message = "waitFor", interval = 100, maxTries = 50) { return new Promise(resolve => { BrowserTestUtils.waitForCondition(condition, message, interval, maxTries) - .then(resolve(condition())); + .then(() => resolve(condition())); }); } @@ -110,5 +110,5 @@ function findMessage(hud, text, selector = ".message") { hud.ui.experimentalOutputNode.querySelectorAll(selector), (el) => el.textContent.includes(text) ); - return elements.pop(); + return elements.length > 0 ? elements.pop() : false; } 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 000000000000..b7666e50b001 --- /dev/null +++ b/devtools/client/webconsole/new-console-output/test/mochitest/test-console-table.html @@ -0,0 +1,19 @@ + + +
+ +console.table() test page
+ + + diff --git a/devtools/client/webconsole/new-console-output/test/requireHelper.js b/devtools/client/webconsole/new-console-output/test/requireHelper.js index a853bee8fcc0..51c733e78a2e 100644 --- a/devtools/client/webconsole/new-console-output/test/requireHelper.js +++ b/devtools/client/webconsole/new-console-output/test/requireHelper.js @@ -30,5 +30,7 @@ requireHacker.global_hook("default", path => { 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 index 62b9b59804ad..04fa55ad3d27 100644 --- a/devtools/client/webconsole/new-console-output/test/store/filters.test.js +++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js @@ -5,8 +5,8 @@ const expect = require("expect"); -const actions = require("devtools/client/webconsole/new-console-output/actions/filters"); -const { messageAdd } = require("devtools/client/webconsole/new-console-output/actions/messages"); +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"); 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 index d2f353620b1f..ad98f478d0b2 100644 --- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js +++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js @@ -11,6 +11,9 @@ const { 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"); @@ -117,6 +120,17 @@ describe("Message reducer:", () => { 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); + }); }); describe("messagesUiById", () => { diff --git a/devtools/client/webconsole/new-console-output/utils/messages.js b/devtools/client/webconsole/new-console-output/utils/messages.js index b464a778142d..93280a9c0035 100644 --- a/devtools/client/webconsole/new-console-output/utils/messages.js +++ b/devtools/client/webconsole/new-console-output/utils/messages.js @@ -81,6 +81,19 @@ function transformPacket(packet) { 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; } const frame = message.filename ? { diff --git a/devtools/client/webconsole/webconsole.js b/devtools/client/webconsole/webconsole.js index fdad530e07d1..37ddd9751573 100644 --- a/devtools/client/webconsole/webconsole.js +++ b/devtools/client/webconsole/webconsole.js @@ -494,6 +494,10 @@ WebConsoleFrame.prototype = { }; allReady.then(notifyObservers, notifyObservers); + if (this.NEW_CONSOLE_OUTPUT_ENABLED) { + allReady.then(this.newConsoleOutput.init); + } + return allReady; }, @@ -3272,6 +3276,13 @@ WebConsoleConnectionProxy.prototype = { }); }, + /** + * Batched dispatch of messages. + */ + dispatchMessagesAdd: function(packets) { + this.webConsoleFrame.newConsoleOutput.dispatchMessagesAdd(packets); + }, + /** * The "cachedMessages" response handler. * @@ -3301,9 +3312,7 @@ WebConsoleConnectionProxy.prototype = { // Filter out CSS page errors. messages = messages.filter(message => !(message._type == "PageError" && Utils.categoryForScriptError(message) === CATEGORY_CSS)); - for (let packet of messages) { - this.dispatchMessageAdd(packet); - } + this.dispatchMessagesAdd(messages); } else { this.webConsoleFrame.displayCachedMessages(messages); if (!this._hasNativeConsoleAPI) { diff --git a/devtools/client/webide/test/test_manifestUpdate.html b/devtools/client/webide/test/test_manifestUpdate.html index 21e645348d20..66f9affd026d 100644 --- a/devtools/client/webide/test/test_manifestUpdate.html +++ b/devtools/client/webide/test/test_manifestUpdate.html @@ -32,8 +32,11 @@ let packagedAppLocation = getTestFilePath("app"); + let onValidated = waitForUpdate(win, "project-validated"); + let onDetails = waitForUpdate(win, "details"); yield winProject.projectList.importPackagedApp(packagedAppLocation); - yield waitForUpdate(win, "details"); + yield onValidated; + yield onDetails; let project = win.AppManager.selectedProject; diff --git a/layout/tools/reftest/reftest-preferences.js b/layout/tools/reftest/reftest-preferences.js index e46064ece121..45533f648462 100644 --- a/layout/tools/reftest/reftest-preferences.js +++ b/layout/tools/reftest/reftest-preferences.js @@ -110,6 +110,7 @@ user_pref("startup.homepage_override_url", ""); user_pref("browser.usedOnWindows10.introURL", ""); user_pref("media.gmp-manager.url.override", "http://localhost/dummy-gmp-manager.xml"); +user_pref("media.gmp-manager.updateEnabled", false); // A fake bool pref for "@supports -moz-bool-pref" sanify test. user_pref("testing.supports.moz-bool-pref", true); diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index c82040a928f9..ba4b91566cd9 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -47,6 +47,7 @@ user_pref("app.update.staging.enabled", false); user_pref("app.update.url.android", ""); // Make sure GMPInstallManager won't hit the network. user_pref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml"); +user_pref("media.gmp-manager.updateEnabled", false); user_pref("dom.w3c_touch_events.enabled", 1); user_pref("layout.accessiblecaret.enabled_on_touch", false); user_pref("dom.undo_manager.enabled", true); diff --git a/testing/talos/talos/config.py b/testing/talos/talos/config.py index b650e51fb4a3..edab99a3d908 100644 --- a/testing/talos/talos/config.py +++ b/testing/talos/talos/config.py @@ -140,6 +140,7 @@ DEFAULTS = dict( 'http://127.0.0.1/extensions-dummy/repositorySearchURL', 'media.gmp-manager.url': 'http://127.0.0.1/gmpmanager-dummy/update.xml', + 'media.gmp-manager.updateEnabled': False, 'extensions.systemAddon.update.url': 'http://127.0.0.1/dummy-system-addons.xml', 'media.navigator.enabled': True, diff --git a/testing/xpcshell/head.js b/testing/xpcshell/head.js index c4b9564d4c00..74fd482cf032 100644 --- a/testing/xpcshell/head.js +++ b/testing/xpcshell/head.js @@ -1616,6 +1616,7 @@ try { .getService(Components.interfaces.nsIPrefBranch); prefs.setCharPref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml"); + prefs.setCharPref("media.gmp-manager.updateEnabled", false); prefs.setCharPref("extensions.systemAddon.update.url", "http://%(server)s/dummy-system-addons.xml"); prefs.setCharPref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/"); prefs.setCharPref("toolkit.telemetry.server", "https://%(server)s/telemetry-dummy"); diff --git a/toolkit/content/gmp-sources/eme-adobe.json b/toolkit/content/gmp-sources/eme-adobe.json new file mode 100644 index 000000000000..3bd808be8e3d --- /dev/null +++ b/toolkit/content/gmp-sources/eme-adobe.json @@ -0,0 +1,31 @@ +{ + "vendors": { + "gmp-eme-adobe": { + "platforms": { + "WINNT_x86-msvc-x64": { + "alias": "WINNT_x86-msvc" + }, + "WINNT_x86-msvc": { + "fileUrl": "https://cdmdownload.adobe.com/firefox/win/x86/primetime_gmp_win_x86_gmc_40673.zip", + "hashValue": "8aad35fc13814b0f1daacddb0d599eedd685287d5afddc97c2f740c8aea270636ccd75b1d1a57364b84e8eb1b23c9f1c126c057d95f3d8217b331dc4b1d5340f", + "filesize": 3694349 + }, + "WINNT_x86_64-msvc-x64": { + "alias": "WINNT_x86_64-msvc" + }, + "WINNT_x86-msvc-x86": { + "alias": "WINNT_x86-msvc" + }, + "WINNT_x86_64-msvc": { + "fileUrl": "https://cdmdownload.adobe.com/firefox/win/x64/primetime_gmp_win_x64_gmc_40673.zip", + "hashValue": "bd1e1a370c5f9dadc247c9f00dd203fab1a75ff3afed8439a0a0bfcc7e1767d0da68497140cbe48daa70e2535dde5f220dd7b344619cecd830a6b685efb9d5a0", + "filesize": 4853103 + } + }, + "version": "17" + } + }, + "hashFunction": "sha512", + "name": "CDM-17", + "schema_version": 1000 +} diff --git a/toolkit/content/gmp-sources/openh264.json b/toolkit/content/gmp-sources/openh264.json new file mode 100644 index 000000000000..a5ba3a525036 --- /dev/null +++ b/toolkit/content/gmp-sources/openh264.json @@ -0,0 +1,67 @@ +{ + "vendors": { + "gmp-gmpopenh264": { + "platforms": { + "WINNT_x86-msvc-x64": { + "alias": "WINNT_x86-msvc" + }, + "Android_x86-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-android-x86-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "8ce4d4318aa6ae9ac1376500d5fceecb3df38727aa920efd9f7829c139face4a069cab683d3902e7cdb89daad2a7e928ffba120812ae343f052a833812dad387", + "filesize": 1640053 + }, + "WINNT_x86-msvc": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "991e01c3b95fa13fac52e0512e1936f1edae42ecbbbcc55447a36915eb3ca8f836546cc780343751691e0188872e5bc56fe3ad5f23f3243e90b96a637561b89e", + "filesize": 356940 + }, + "WINNT_x86-msvc-x86": { + "alias": "WINNT_x86-msvc" + }, + "Linux_x86_64-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "e1086ee6e4fb60a1aa11b5626594b97695533a8e269d776877cebd5cf29088619e2c164e7bd1eba5486f772c943f2efec723f69cc48478ec84a11d7b61ca1865", + "filesize": 515722 + }, + "Darwin_x86-gcc3-u-i386-x86_64": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "64b0e13e6319b7a31ed35a46bea5abcfe6af04ba59a277db07677236cfb685813763731ff6b44b85e03e1489f3b15f8df0128a299a36720531b9f4ba6e1c1f58", + "filesize": 382435 + }, + "Darwin_x86_64-gcc3": { + "alias": "Darwin_x86_64-gcc3-u-i386-x86_64" + }, + "Linux_x86-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "19084f0230218c584715861f4723e072b1af02e26995762f368105f670f60ecb4082531bc4e33065a4675dd1296f6872a6cb101547ef2d19ef3e25e2e16d4dc0", + "filesize": 515857 + }, + "Darwin_x86_64-gcc3-u-i386-x86_64": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "3b52343070a2f75e91b7b0d3bb33935352237c7e1d2fdc6a467d039ffbbda6a72087f9e0a369fe95e6c4c789ff3052f0c134af721d7273db9ba66d077d85b327", + "filesize": 390308 + }, + "Android_arm-eabi-gcc3": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-android-arm-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "7a15245c781f32df310ebb88cb8a783512eab934b38ffd889d6420473d40eddbe8a89c17cc60d4e7647c156b04d20030e1ae0081e3f90a0d8f94626ec5f4d817", + "filesize": 1515895 + }, + "Darwin_x86-gcc3": { + "alias": "Darwin_x86-gcc3-u-i386-x86_64" + }, + "WINNT_x86_64-msvc-x64": { + "alias": "WINNT_x86_64-msvc" + }, + "WINNT_x86_64-msvc": { + "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-0410d336bb748149a4f560eb6108090f078254b1.zip", + "hashValue": "5030b47065e817db5c40bca9c62ac27292bbf636e24698f45dc67f03fa6420b97bd2f792c1cb39df65776c1e7597c70122ac7abf36fb2ad0603734e9e8ec4ef3", + "filesize": 404355 + } + }, + "version": "1.6" + } + }, + "hashFunction": "sha512", + "name": "OpenH264-1.6", + "schema_version": 1000 +} diff --git a/toolkit/content/gmp-sources/widevinecdm.json b/toolkit/content/gmp-sources/widevinecdm.json new file mode 100644 index 000000000000..02ef7fee5cb0 --- /dev/null +++ b/toolkit/content/gmp-sources/widevinecdm.json @@ -0,0 +1,49 @@ +{ + "vendors": { + "gmp-widevinecdm": { + "platforms": { + "WINNT_x86-msvc-x64": { + "alias": "WINNT_x86-msvc" + }, + "WINNT_x86-msvc": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-ia32.zip", + "hashValue": "d7e10d09c87a157af865f8388ba70ae672bd9e38987bdd94077af52d6b1abaa745b3db92e9f93f607af6420c68210f7cfd518a9d2c99fecf79aed3385cbcbc0b", + "filesize": 2884452 + }, + "WINNT_x86-msvc-x86": { + "alias": "WINNT_x86-msvc" + }, + "Linux_x86_64-gcc3": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-x64.zip", + "hashValue": "1edfb58a44792d2a53694f46fcc698161edafb2a1fe0e5c31b50c1d52408b5e8918d9f33271c62a19a65017694ebeacb4f390fe914688ca7b1952cdb84ed55ec", + "filesize": 2975492 + }, + "Darwin_x86_64-gcc3-u-i386-x86_64": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-mac-x64.zip", + "hashValue": "1916805e84a49e04748204f4e4c48ae52c8312f7c04afedacacd7dfab2de424412bc988a8c3e5bcb0865f8844b569c0eb9589dae51e74d9bdfe46792c9d1631f", + "filesize": 2155607 + }, + "Darwin_x86_64-gcc3": { + "alias": "Darwin_x86_64-gcc3-u-i386-x86_64" + }, + "Linux_x86-gcc3": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-linux-ia32.zip", + "hashValue": "5c4beb72ea693740a013b60bc5491a042bb82fa5ca6845a2f450579e2e1e465263f19e7ab6d08d91deb8219b30f092ab6e6745300d5adda627f270c95e5a66e0", + "filesize": 3084582 + }, + "WINNT_x86_64-msvc-x64": { + "alias": "WINNT_x86_64-msvc" + }, + "WINNT_x86_64-msvc": { + "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/903-win-x64.zip", + "hashValue": "33497f3458846e11fa52413f6477bfe1a7f502da262c3a2ce9fe6d773a4a2d023c54228596eb162444b55c87fb126de01f60fa729d897ef5e6eec73b2dfbdc7a", + "filesize": 2853777 + } + }, + "version": "1.4.8.903" + } + }, + "hashFunction": "sha512", + "name": "Widevine-1.4.8.903", + "schema_version": 1000 +} diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index e3c59ac20cd2..d632a8eee7c9 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -109,3 +109,6 @@ toolkit.jar: content/global/macWindowMenu.js #endif content/global/svg/svgBindings.xml (/layout/svg/resources/content/svgBindings.xml) + content/global/gmp-sources/eme-adobe.json (gmp-sources/eme-adobe.json) + content/global/gmp-sources/openh264.json (gmp-sources/openh264.json) + content/global/gmp-sources/widevinecdm.json (gmp-sources/widevinecdm.json) \ No newline at end of file diff --git a/toolkit/modules/FinderHighlighter.jsm b/toolkit/modules/FinderHighlighter.jsm index 768001f4927d..48efa6bfbe50 100644 --- a/toolkit/modules/FinderHighlighter.jsm +++ b/toolkit/modules/FinderHighlighter.jsm @@ -21,7 +21,8 @@ XPCOMUtils.defineLazyGetter(this, "kDebug", () => { const kContentChangeThresholdPx = 5; const kBrightTextSampleSize = 5; -const kModalHighlightRepaintFreqMs = 100; +const kModalHighlightRepaintLoFreqMs = 100; +const kModalHighlightRepaintHiFreqMs = 16; const kHighlightAllPref = "findbar.highlightAll"; const kModalHighlightPref = "findbar.modalHighlight"; const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size", @@ -1113,7 +1114,9 @@ FinderHighlighter.prototype = { /** * Doing a full repaint each time a range is delivered by the highlight iterator * is way too costly, thus we pipe the frequency down to every - * `kModalHighlightRepaintFreqMs` milliseconds. + * `kModalHighlightRepaintLoFreqMs` milliseconds. If there are dynamic ranges + * found (see `_isInDynamicContainer()` for the definition), the frequency + * will be upscaled to `kModalHighlightRepaintHiFreqMs`. * * @param {nsIDOMWindow} window * @param {Object} options Dictionary of painter hints that contains the @@ -1134,7 +1137,8 @@ FinderHighlighter.prototype = { window = window.top; let dict = this.getForWindow(window); - let repaintDynamicRanges = ((scrollOnly || contentChanged) && !!dict.dynamicRangesSet.size); + let hasDynamicRanges = !!dict.dynamicRangesSet.size; + let repaintDynamicRanges = ((scrollOnly || contentChanged) && hasDynamicRanges); // When we request to repaint unconditionally, we mean to call // `_repaintHighlightAllMask()` right after the timeout. @@ -1164,7 +1168,7 @@ FinderHighlighter.prototype = { dict.unconditionalRepaintRequested = false; this._repaintHighlightAllMask(window); } - }, kModalHighlightRepaintFreqMs); + }, hasDynamicRanges ? kModalHighlightRepaintHiFreqMs : kModalHighlightRepaintLoFreqMs); }, /** diff --git a/toolkit/modules/GMPInstallManager.jsm b/toolkit/modules/GMPInstallManager.jsm index f8484e4adbc3..c6921aefd367 100644 --- a/toolkit/modules/GMPInstallManager.jsm +++ b/toolkit/modules/GMPInstallManager.jsm @@ -39,6 +39,18 @@ XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() { return temp; }); +XPCOMUtils.defineLazyGetter(this, "isXPOrVista64", function () { + let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; + if (os != "WINNT") { + return false; + } + let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); + if (parseFloat(sysInfo.getProperty("version")) < 6) { + return true; + } + return Services.appinfo.is64Bit; +}); + XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"); @@ -80,7 +92,10 @@ GMPInstallManager.prototype = { /** * Performs an addon check. * @return a promise which will be resolved or rejected. - * The promise is resolved with an array of GMPAddons + * The promise is resolved with an object with properties: + * gmpAddons: array of GMPAddons + * usedFallback: whether the data was collected from online or + * from fallback data within the build * The promise is rejected with an object with properties: * target: The XHR request object * status: The HTTP status code @@ -104,12 +119,16 @@ GMPInstallManager.prototype = { } } - ProductAddonChecker.getProductAddonList(url, allowNonBuiltIn, certs).then((addons) => { - if (!addons) { - this._deferred.resolve([]); + let addonPromise = ProductAddonChecker + .getProductAddonList(url, allowNonBuiltIn, certs); + + addonPromise.then(res => { + if (!res || !res.gmpAddons) { + this._deferred.resolve({gmpAddons: []}); } else { - this._deferred.resolve(addons.map(a => new GMPAddon(a))); + res.gmpAddons = res.gmpAddons.map(a => new GMPAddon(a)); + this._deferred.resolve(res); } delete this._deferred; }, (ex) => { @@ -201,7 +220,7 @@ GMPInstallManager.prototype = { } try { - let gmpAddons = yield this.checkForAddons(); + let {usedFallback, gmpAddons} = yield this.checkForAddons(); this._updateLastCheck(); log.info("Found " + gmpAddons.length + " addons advertised."); let addonsToInstall = gmpAddons.filter(function(gmpAddon) { @@ -222,6 +241,19 @@ GMPInstallManager.prototype = { return false; } + if (gmpAddon.isEME && isXPOrVista64) { + log.info("Addon |" + gmpAddon.id + "| not supported on this platform."); + return false; + } + + // Do not install from fallback if already installed as it + // may be a downgrade + if (usedFallback && gmpAddon.isUpdate) { + log.info("Addon |" + gmpAddon.id + "| not installing updates based " + + "on fallback."); + return false; + } + let addonUpdateEnabled = false; if (GMP_PLUGIN_IDS.indexOf(gmpAddon.id) >= 0) { if (!this._isAddonEnabled(gmpAddon.id)) { @@ -341,6 +373,14 @@ GMPAddon.prototype = { get isEME() { return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0; }, + /** + * @return true if the addon has been previously installed and this is + * a new version, if this is a fresh install return false + */ + get isUpdate() { + return this.version && + GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id); + }, }; /** * Constructs a GMPExtractor object which is used to extract a GMP zip diff --git a/toolkit/modules/GMPUtils.jsm b/toolkit/modules/GMPUtils.jsm index db7dbfcd8a8e..9e41a7a61401 100644 --- a/toolkit/modules/GMPUtils.jsm +++ b/toolkit/modules/GMPUtils.jsm @@ -134,6 +134,7 @@ this.GMPPrefs = { KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn", KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck", KEY_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks", + KEY_UPDATE_ENABLED: "media.gmp-manager.updateEnabled", KEY_APP_DISTRIBUTION: "distribution.id", KEY_APP_DISTRIBUTION_VERSION: "distribution.version", KEY_BUILDID: "media.gmp-manager.buildID", diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js index 627dfb1e8b03..fe61deaaed59 100644 --- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js +++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js @@ -67,10 +67,8 @@ add_test(function test_checkForAddons_uninitWithoutInstall() { overrideXHR(200, ""); let installManager = new GMPInstallManager(); let promise = installManager.checkForAddons(); - promise.then(() => { - do_throw("no response should reject"); - }, err => { - do_check_true(!!err); + promise.then(res => { + do_check_true(res.usedFallback); installManager.uninit(); run_next_test(); }); @@ -83,10 +81,8 @@ add_test(function test_checkForAddons_noResponse() { overrideXHR(200, ""); let installManager = new GMPInstallManager(); let promise = installManager.checkForAddons(); - promise.then(() => { - do_throw("no response should reject"); - }, err => { - do_check_true(!!err); + promise.then(res => { + do_check_true(res.usedFallback); installManager.uninit(); run_next_test(); }); @@ -98,8 +94,8 @@ add_test(function test_checkForAddons_noResponse() { add_task(function* test_checkForAddons_noAddonsElement() { overrideXHR(200, "