merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-09-28 15:55:25 +02:00
Родитель 0ad3db41b5 0dd1f6eaf9
Коммит c084656336
62 изменённых файлов: 1374 добавлений и 256 удалений

Просмотреть файл

@ -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,

Просмотреть файл

@ -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,

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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"}

Просмотреть файл

@ -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({

Просмотреть файл

@ -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);

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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
};

Просмотреть файл

@ -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;

Просмотреть файл

@ -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,
};

Просмотреть файл

@ -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',
)

Просмотреть файл

@ -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),
};
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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");

Просмотреть файл

@ -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({

Просмотреть файл

@ -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,
})
);
}

Просмотреть файл

@ -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,
});
}
});

Просмотреть файл

@ -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"];

Просмотреть файл

@ -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(/\?.+/, ""))
)
);
}

Просмотреть файл

@ -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";

Просмотреть файл

@ -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',

Просмотреть файл

@ -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",

Просмотреть файл

@ -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());
},

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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;

Просмотреть файл

@ -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,

Просмотреть файл

@ -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());
});
});

Просмотреть файл

@ -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");

Просмотреть файл

@ -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"), {});

Просмотреть файл

@ -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);
});
});

Просмотреть файл

@ -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;

Просмотреть файл

@ -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 = [

Просмотреть файл

@ -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,

Просмотреть файл

@ -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");

Просмотреть файл

@ -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]

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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
<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");
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();
});
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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")`;
}
});

Просмотреть файл

@ -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");

Просмотреть файл

@ -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", () => {

Просмотреть файл

@ -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 ? {

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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;

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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,

Просмотреть файл

@ -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");

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -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);
},
/**

Просмотреть файл

@ -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

Просмотреть файл

@ -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",

Просмотреть файл

@ -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, "<updates></updates>");
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 0);
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 0);
installManager.uninit();
});
@ -109,8 +105,8 @@ add_task(function* test_checkForAddons_noAddonsElement() {
add_task(function* test_checkForAddons_emptyAddonsElement() {
overrideXHR(200, "<updates><addons/></updates>");
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 0);
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 0);
installManager.uninit();
});
@ -121,10 +117,8 @@ add_test(function test_checkForAddons_wrongResponseXML() {
overrideXHR(200, "<digits_of_pi>3.141592653589793....</digits_of_pi>");
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
promise.then(() => {
do_throw("response with the wrong root element should reject");
}, err => {
do_check_true(!!err);
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
run_next_test();
});
@ -137,11 +131,8 @@ add_test(function test_checkForAddons_404Error() {
overrideXHR(404, "");
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
promise.then(() => {
do_throw("404 response should reject");
}, err => {
do_check_true(!!err);
do_check_eq(err.status, 404);
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
run_next_test();
});
@ -155,10 +146,8 @@ add_test(function test_checkForAddons_abort() {
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
xhr.abort();
promise.then(() => {
do_throw("abort() should reject");
}, err => {
do_check_eq(err.status, 0);
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
run_next_test();
});
@ -171,10 +160,8 @@ add_test(function test_checkForAddons_timeout() {
overrideXHR(200, "", { dropRequest: true, timeout: true });
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
promise.then(() => {
do_throw("Defensive timeout should reject");
}, err => {
do_check_eq(err.status, 0);
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
run_next_test();
});
@ -200,11 +187,8 @@ add_test(function test_checkForAddons_bad_ssl() {
overrideXHR(200, "");
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
promise.then(() => {
do_throw("Defensive timeout should reject");
}, err => {
do_check_true(err.message.includes("SSL is required and URI scheme is " +
"not https."));
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
if (PREF_KEY_URL_OVERRIDE_BACKUP) {
Preferences.set(GMPScope.GMPPrefs.KEY_URL_OVERRIDE,
@ -225,10 +209,9 @@ add_test(function test_checkForAddons_notXML() {
overrideXHR(200, "3.141592653589793....");
let installManager = new GMPInstallManager();
let promise = installManager.checkForAddons();
promise.then(() => {
do_throw("non XML response should reject");
}, err => {
do_check_true(!!err);
promise.then(res => {
do_check_true(res.usedFallback);
installManager.uninit();
run_next_test();
});
@ -251,9 +234,9 @@ add_task(function* test_checkForAddons_singleAddon() {
"</updates>"
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 1);
let gmpAddon= gmpAddons[0];
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 1);
let gmpAddon = res.gmpAddons[0];
do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
do_check_eq(gmpAddon.hashFunction, "sha256");
@ -284,9 +267,9 @@ add_task(function* test_checkForAddons_singleAddonWithSize() {
"</updates>"
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 1);
let gmpAddon = gmpAddons[0];
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 1);
let gmpAddon = res.gmpAddons[0];
do_check_eq(gmpAddon.id, "openh264-plugin-no-at-symbol");
do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
do_check_eq(gmpAddon.hashFunction, "sha256");
@ -353,9 +336,9 @@ add_task(function* test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() {
"</updates>"
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 7);
let gmpAddon= gmpAddons[0];
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 7);
let gmpAddon = res.gmpAddons[0];
do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
do_check_eq(gmpAddon.hashFunction, "sha256");
@ -364,7 +347,7 @@ add_task(function* test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() {
do_check_true(gmpAddon.isValid);
do_check_false(gmpAddon.isInstalled);
gmpAddon= gmpAddons[1];
gmpAddon = res.gmpAddons[1];
do_check_eq(gmpAddon.id, "NOT-gmp-gmpopenh264");
do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip");
do_check_eq(gmpAddon.hashFunction, "sha512");
@ -373,9 +356,9 @@ add_task(function* test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() {
do_check_true(gmpAddon.isValid);
do_check_false(gmpAddon.isInstalled);
for (let i = 2; i < gmpAddons.length; i++) {
do_check_false(gmpAddons[i].isValid);
do_check_false(gmpAddons[i].isInstalled);
for (let i = 2; i < res.gmpAddons.length; i++) {
do_check_false(res.gmpAddons[i].isValid);
do_check_false(res.gmpAddons[i].isInstalled);
}
installManager.uninit();
});
@ -401,9 +384,9 @@ add_task(function* test_checkForAddons_updatesWithAddons() {
"</updates>"
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 1);
let gmpAddon= gmpAddons[0];
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 1);
let gmpAddon = res.gmpAddons[0];
do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
do_check_eq(gmpAddon.hashFunction, "sha256");
@ -455,9 +438,9 @@ function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let gmpAddons = yield installManager.checkForAddons();
do_check_eq(gmpAddons.length, 1);
let gmpAddon = gmpAddons[0];
let res = yield installManager.checkForAddons();
do_check_eq(res.gmpAddons.length, 1);
let gmpAddon = res.gmpAddons[0];
do_check_false(gmpAddon.isInstalled);
try {
@ -588,9 +571,9 @@ add_test(function test_installAddon_noServer() {
overrideXHR(200, responseXML);
let installManager = new GMPInstallManager();
let checkPromise = installManager.checkForAddons();
checkPromise.then(gmpAddons => {
do_check_eq(gmpAddons.length, 1);
let gmpAddon= gmpAddons[0];
checkPromise.then(res => {
do_check_eq(res.gmpAddons.length, 1);
let gmpAddon = res.gmpAddons[0];
GMPInstallManager.overrideLeaveDownloadedZip = true;
let installPromise = installManager.installAddon(gmpAddon);

Просмотреть файл

@ -6,8 +6,23 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const LOCAL_EME_SOURCES = [{
"id": "gmp-eme-adobe",
"src": "chrome://global/content/gmp-sources/eme-adobe.json"
}, {
"id": "gmp-gmpopenh264",
"src": "chrome://global/content/gmp-sources/openh264.json"
}, {
"id": "gmp-widevinecdm",
"src": "chrome://global/content/gmp-sources/widevinecdm.json"
}];
this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
Cu.importGlobalProperties(["XMLHttpRequest"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/CertUtils.jsm");
@ -15,7 +30,15 @@ Cu.import("resource://gre/modules/CertUtils.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
/*globals OS*/
/*globals GMPPrefs */
XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
"resource://gre/modules/GMPUtils.jsm");
/*globals OS */
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
var logger = Log.repository.getLogger("addons.productaddons");
@ -139,13 +162,34 @@ function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
});
}
function downloadJSON(uri) {
logger.info("fetching config from: " + uri);
return new Promise((resolve, reject) => {
let xmlHttp = new XMLHttpRequest({mozAnon: true});
xmlHttp.onload = function(aResponse) {
resolve(JSON.parse(this.responseText));
};
xmlHttp.onerror = function(e) {
reject("Fetching " + uri + " results in error code: " + e.target.status);
};
xmlHttp.open("GET", uri);
xmlHttp.overrideMimeType("application/json");
xmlHttp.send();
});
}
/**
* Parses a list of add-ons from a DOM document.
*
* @param document
* The DOM document to parse.
* @return null if there is no <addons> element otherwise an array of the addons
* listed.
* @return null if there is no <addons> element otherwise an object containing
* an array of the addons listed and a field notifying whether the
* fallback was used.
*/
function parseXML(document) {
// Check that the root element is correct
@ -175,7 +219,67 @@ function parseXML(document) {
results.push(addon);
}
return results;
return {
usedFallback: false,
gmpAddons: results
};
}
/**
* If downloading from the network fails (AUS server is down),
* load the sources from local build configuration.
*/
function downloadLocalConfig() {
if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
return Promise.resolve({usedFallback: true, gmpAddons: []});
}
return Promise.all(LOCAL_EME_SOURCES.map(conf => {
return downloadJSON(conf.src).then(addons => {
let platforms = addons.vendors[conf.id].platforms;
let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
let details = null;
while (!details) {
if (!(target in platforms)) {
// There was no matching platform so return false, this addon
// will be filtered from the results below
logger.info("no details found for: " + target);
return false;
}
// Field either has the details of the binary or is an alias
// to another build target key that does
if (platforms[target].alias) {
target = platforms[target].alias;
} else {
details = platforms[target];
}
}
logger.info("found plugin: " + conf.id);
return {
"id": conf.id,
"URL": details.fileUrl,
"hashFunction": addons.hashFunction,
"hashValue": details.hashValue,
"version": addons.vendors[conf.id].version,
"size": details.filesize
};
});
})).then(addons => {
// Some filters may not match this platform so
// filter those out
addons = addons.filter(x => x !== false);
return {
usedFallback: true,
gmpAddons: addons
};
});
}
/**
@ -313,11 +417,14 @@ const ProductAddonChecker = {
* @param allowedCerts
* The list of certificate attributes to match the SSL certificate
* against or null to skip checks.
* @return a promise that resolves to the list of add-ons or rejects with a JS
* @return a promise that resolves to an object containing the list of add-ons
* and whether the local fallback was used, or rejects with a JS
* exception in case of error.
*/
getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
return downloadXML(url, allowNonBuiltIn, allowedCerts).then(parseXML);
return downloadXML(url, allowNonBuiltIn, allowedCerts)
.then(parseXML)
.catch(downloadLocalConfig);
},
/**

Просмотреть файл

@ -3083,17 +3083,17 @@ this.XPIProvider = {
url = UpdateUtils.formatUpdateURL(url);
logger.info(`Starting system add-on update check from ${url}.`);
let addonList = yield ProductAddonChecker.getProductAddonList(url);
let res = yield ProductAddonChecker.getProductAddonList(url);
// If there was no list then do nothing.
if (!addonList) {
if (!res || !res.gmpAddons) {
logger.info("No system add-ons list was returned.");
yield systemAddonLocation.cleanDirectories();
return;
}
addonList = new Map(
addonList.map(spec => [spec.id, { spec, path: null, addon: null }]));
let addonList = new Map(
res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));
let getAddonsInLocation = (location) => {
return new Promise(resolve => {

Просмотреть файл

@ -62,43 +62,23 @@ function compareFiles(file1, file2) {
}
add_task(function* test_404() {
try {
let addons = yield ProductAddonChecker.getProductAddonList(root + "404.xml");
do_throw("Should not have returned anything");
}
catch (e) {
do_check_true(true, "Expected to throw for a missing update file");
}
let res = yield ProductAddonChecker.getProductAddonList(root + "404.xml");
do_check_true(res.usedFallback);
});
add_task(function* test_not_xml() {
try {
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.txt");
do_throw("Should not have returned anything");
}
catch (e) {
do_check_true(true, "Expected to throw for a non XML result");
}
let res = yield ProductAddonChecker.getProductAddonList(root + "bad.txt");
do_check_true(res.usedFallback);
});
add_task(function* test_invalid_xml() {
try {
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.xml");
do_throw("Should not have returned anything");
}
catch (e) {
do_check_true(true, "Expected to throw for invalid XML");
}
let res = yield ProductAddonChecker.getProductAddonList(root + "bad.xml");
do_check_true(res.usedFallback);
});
add_task(function* test_wrong_xml() {
try {
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad2.xml");
do_throw("Should not have returned anything");
}
catch (e) {
do_check_true(true, "Expected to throw for a missing <updates> tag");
}
let res = yield ProductAddonChecker.getProductAddonList(root + "bad2.xml");
do_check_true(res.usedFallback);
});
add_task(function* test_missing() {
@ -107,19 +87,19 @@ add_task(function* test_missing() {
});
add_task(function* test_empty() {
let addons = yield ProductAddonChecker.getProductAddonList(root + "empty.xml");
do_check_true(Array.isArray(addons));
do_check_eq(addons.length, 0);
let res = yield ProductAddonChecker.getProductAddonList(root + "empty.xml");
do_check_true(Array.isArray(res.gmpAddons));
do_check_eq(res.gmpAddons.length, 0);
});
add_task(function* test_good_xml() {
let addons = yield ProductAddonChecker.getProductAddonList(root + "good.xml");
do_check_true(Array.isArray(addons));
let res = yield ProductAddonChecker.getProductAddonList(root + "good.xml");
do_check_true(Array.isArray(res.gmpAddons));
// There are three valid entries in the XML
do_check_eq(addons.length, 5);
do_check_eq(res.gmpAddons.length, 5);
let addon = addons[0];
let addon = res.gmpAddons[0];
do_check_eq(addon.id, "test1");
do_check_eq(addon.URL, "http://example.com/test1.xpi");
do_check_eq(addon.hashFunction, undefined);
@ -127,7 +107,7 @@ add_task(function* test_good_xml() {
do_check_eq(addon.version, undefined);
do_check_eq(addon.size, undefined);
addon = addons[1];
addon = res.gmpAddons[1];
do_check_eq(addon.id, "test2");
do_check_eq(addon.URL, "http://example.com/test2.xpi");
do_check_eq(addon.hashFunction, "md5");
@ -135,7 +115,7 @@ add_task(function* test_good_xml() {
do_check_eq(addon.version, undefined);
do_check_eq(addon.size, undefined);
addon = addons[2];
addon = res.gmpAddons[2];
do_check_eq(addon.id, "test3");
do_check_eq(addon.URL, "http://example.com/test3.xpi");
do_check_eq(addon.hashFunction, undefined);
@ -143,7 +123,7 @@ add_task(function* test_good_xml() {
do_check_eq(addon.version, "1.0");
do_check_eq(addon.size, 45);
addon = addons[3];
addon = res.gmpAddons[3];
do_check_eq(addon.id, "test4");
do_check_eq(addon.URL, undefined);
do_check_eq(addon.hashFunction, undefined);
@ -151,7 +131,7 @@ add_task(function* test_good_xml() {
do_check_eq(addon.version, undefined);
do_check_eq(addon.size, undefined);
addon = addons[4];
addon = res.gmpAddons[4];
do_check_eq(addon.id, undefined);
do_check_eq(addon.URL, "http://example.com/test5.xpi");
do_check_eq(addon.hashFunction, undefined);

Просмотреть файл

@ -289,19 +289,6 @@ const TEST_CONDITIONS = {
* if missing the test condition's initialState is used.
*/
const TESTS = {
// Test that an error response does nothing
error: {
test: function*() {
try {
yield install_system_addons("foobar");
do_throw("Expected to fail the update check");
}
catch (e) {
do_check_true(true, "Expected to fail the update check");
}
},
},
// Test that a blank response does nothing
blank: {
updateList: null,