Bug 1294499 - New console frontend: Add collapsible stacktrace for console.error/trace messages. r=linclark

MozReview-Commit-ID: LctpJdFtxX0
This commit is contained in:
Nicolas Chevobbe 2016-08-18 16:04:35 -07:00
Родитель 6a66311928
Коммит d8d0dd3e36
9 изменённых файлов: 159 добавлений и 22 удалений

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

@ -13,7 +13,9 @@ const { IdGenerator } = require("devtools/client/webconsole/new-console-output/u
const {
MESSAGE_ADD,
MESSAGES_CLEAR
MESSAGES_CLEAR,
MESSAGE_OPEN,
MESSAGE_CLOSE,
} = require("../constants");
const defaultIdGenerator = new IdGenerator();
@ -36,5 +38,21 @@ function messagesClear() {
};
}
function messageOpen(id) {
return {
type: MESSAGE_OPEN,
id
};
}
function messageClose(id) {
return {
type: MESSAGE_CLOSE,
id
};
}
exports.messageAdd = messageAdd;
exports.messagesClear = messagesClear;
exports.messageOpen = messageOpen;
exports.messageClose = messageClose;

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

@ -0,0 +1,42 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// React & Redux
const {
createClass,
DOM: dom,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const CollapseButton = createClass({
displayName: "CollapseButton",
propTypes: {
open: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
},
render: function () {
const { title, open, onClick } = this.props;
let classes = ["theme-twisty"];
if (open) {
classes.push("open");
}
return dom.a({
className: classes.join(" "),
onClick: onClick,
title
});
}
});
module.exports.CollapseButton = CollapseButton;

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

@ -12,7 +12,7 @@ const {
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { getAllMessages } = require("devtools/client/webconsole/new-console-output/selectors/messages");
const { getAllMessages, getAllMessagesUiById } = 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({
@ -41,11 +41,24 @@ const ConsoleOutput = createClass({
},
render() {
let {messages, sourceMapService, onViewSourceInDebugger} = this.props;
let {
dispatch,
messages,
messagesUi,
sourceMapService,
onViewSourceInDebugger
} = this.props;
let messageNodes = messages.map(function (message) {
return (
MessageContainer({ message, key: message.id,
sourceMapService, onViewSourceInDebugger })
MessageContainer({
dispatch,
message,
key: message.id,
sourceMapService,
onViewSourceInDebugger,
open: messagesUi.includes(message.id)
})
);
});
return (
@ -63,7 +76,8 @@ function isScrolledToBottom(outputNode, scrollNode) {
function mapStateToProps(state) {
return {
messages: getAllMessages(state)
messages: getAllMessages(state),
messagesUi: getAllMessagesUiById(state)
};
}

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

@ -33,16 +33,31 @@ const MessageContainer = createClass({
message: PropTypes.object.isRequired,
sourceMapService: PropTypes.object,
onViewSourceInDebugger: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
},
shouldComponentUpdate(nextProps, nextState) {
return this.props.message.repeat !== nextProps.message.repeat;
return this.props.message.repeat !== nextProps.message.repeat
|| this.props.open !== nextProps.open;
},
render() {
const { message, sourceMapService, onViewSourceInDebugger } = this.props;
const {
dispatch,
message,
sourceMapService,
onViewSourceInDebugger,
open
} = this.props;
let MessageComponent = createFactory(getMessageComponent(message));
return MessageComponent({ message, sourceMapService, onViewSourceInDebugger });
return MessageComponent({
dispatch,
message,
sourceMapService,
onViewSourceInDebugger,
open
});
}
});

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

@ -17,6 +17,9 @@ const StackTrace = createFactory(require("devtools/client/shared/components/stac
const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body").GripMessageBody);
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 {l10n} = require("devtools/client/webconsole/new-console-output/utils/messages");
const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
ConsoleApiCall.displayName = "ConsoleApiCall";
@ -24,11 +27,13 @@ ConsoleApiCall.propTypes = {
message: PropTypes.object.isRequired,
sourceMapService: PropTypes.object,
onViewSourceInDebugger: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
};
function ConsoleApiCall(props) {
const { message, sourceMapService, onViewSourceInDebugger } = props;
const { source, level, stacktrace, type, frame } = message;
const { dispatch, message, sourceMapService, onViewSourceInDebugger, open } = props;
const {source, level, stacktrace, type, frame } = message;
let messageBody;
if (type === "trace") {
messageBody = dom.span({className: "cm-variable"}, "console.trace()");
@ -41,6 +46,7 @@ function ConsoleApiCall(props) {
const icon = MessageIcon({level});
const repeat = MessageRepeat({repeat: message.repeat});
let collapse = "";
let attachment = "";
if (stacktrace) {
attachment = dom.div({className: "stacktrace devtools-monospace"},
@ -49,6 +55,18 @@ function ConsoleApiCall(props) {
onViewSourceInDebugger: onViewSourceInDebugger
})
);
collapse = CollapseButton({
open: open,
title: l10n.getStr("messageToggleDetails"),
onClick: function () {
if (open) {
dispatch(actions.messageClose(message.id));
} else {
dispatch(actions.messageOpen(message.id));
}
},
});
}
const classes = ["message", "cm-s-mozilla"];
@ -61,7 +79,7 @@ function ConsoleApiCall(props) {
classes.push(level);
}
if (type === "trace") {
if (open === true) {
classes.push("open");
}
@ -72,6 +90,7 @@ function ConsoleApiCall(props) {
// @TODO add timestamp
// @TODO add indent if necessary
icon,
collapse,
dom.span({className: "message-body-wrapper"},
dom.span({},
dom.span({className: "message-flex-body"},

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

@ -8,6 +8,7 @@ DIRS += [
]
DevToolsModules(
'collapse-button.js',
'console-output.js',
'filter-bar.js',
'filter-button.js',

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

@ -8,6 +8,8 @@
const actionTypes = {
MESSAGE_ADD: "MESSAGE_ADD",
MESSAGES_CLEAR: "MESSAGES_CLEAR",
MESSAGE_OPEN: "MESSAGE_OPEN",
MESSAGE_CLOSE: "MESSAGE_CLOSE",
FILTER_TOGGLE: "FILTER_TOGGLE",
FILTER_TEXT_SET: "FILTER_TEXT_SET",
FILTERS_CLEAR: "FILTERS_CLEAR",

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

@ -8,26 +8,47 @@
const Immutable = require("devtools/client/shared/vendor/immutable");
const constants = require("devtools/client/webconsole/new-console-output/constants");
function messages(state = Immutable.List(), action) {
const MessageState = Immutable.Record({
messagesById: Immutable.List(),
messagesUiById: Immutable.List(),
});
function messages(state = new MessageState(), action) {
const messagesById = state.messagesById;
const messagesUiById = state.messagesUiById;
switch (action.type) {
case constants.MESSAGE_ADD:
let newMessage = action.message;
if (newMessage.type === "clear") {
return Immutable.List([newMessage]);
return state.set("messagesById", Immutable.List([newMessage]));
}
if (newMessage.allowRepeating && state.size > 0) {
let lastMessage = state.last();
if (newMessage.allowRepeating && messagesById.size > 0) {
let lastMessage = messagesById.last();
if (lastMessage.repeatId === newMessage.repeatId) {
return state.pop().push(
newMessage.set("repeat", lastMessage.repeat + 1)
);
return state.withMutations(function (record) {
record.set("messagesById", messagesById.pop().push(
newMessage.set("repeat", lastMessage.repeat + 1)
));
});
}
}
return state.push(newMessage);
return state.withMutations(function (record) {
record.set("messagesById", messagesById.push(newMessage));
if (newMessage.type === "trace") {
record.set("messagesUiById", messagesUiById.push(newMessage.id));
}
});
case constants.MESSAGES_CLEAR:
return Immutable.List();
return state.set("messagesById", Immutable.List());
case constants.MESSAGE_OPEN:
return state.set("messagesUiById", messagesUiById.push(action.id));
case constants.MESSAGE_CLOSE:
let index = state.messagesUiById.indexOf(action.id);
return state.deleteIn(["messagesUiById", index]);
}
return state;

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

@ -12,7 +12,7 @@ const {
} = require("devtools/client/webconsole/new-console-output/constants");
function getAllMessages(state) {
let messages = state.messages;
let messages = state.messages.messagesById;
let logLimit = getLogLimit(state);
let filters = getAllFilters(state);
@ -25,6 +25,10 @@ function getAllMessages(state) {
);
}
function getAllMessagesUiById(state) {
return state.messages.messagesUiById;
}
function filterLevel(messages, filters) {
return messages.filter((message) => {
return filters[message.level] === true
@ -59,3 +63,4 @@ function prune(messages, logLimit) {
}
exports.getAllMessages = getAllMessages;
exports.getAllMessagesUiById = getAllMessagesUiById;