author | Lin Clark <lclark@mozilla.com> |
Sun, 02 Oct 2016 15:16:49 -0700 | |
changeset 316306 | 4a08689beee88bc554f0b26ebb1b89a6fec85985 |
parent 316305 | cf5c96ed4ae6d6d6968a5e1bf4f97a2bb070500a |
child 316307 | 56cb28b02f39f89b2b90622e2c2c8324d0d6ad91 |
push id | 30768 |
push user | cbook@mozilla.com |
push date | Tue, 04 Oct 2016 09:56:53 +0000 |
treeherder | mozilla-central@6bfa0e8a9f20 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bgrins |
bugs | 1306099 |
milestone | 52.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/webconsole/new-console-output/components/console-output.js +++ b/devtools/client/webconsole/new-console-output/components/console-output.js @@ -8,49 +8,62 @@ const { createFactory, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); const ReactDOM = require("devtools/client/shared/vendor/react-dom"); const { connect } = require("devtools/client/shared/vendor/react-redux"); const { getAllMessages, getAllMessagesUiById, getAllMessagesTableDataById } = require("devtools/client/webconsole/new-console-output/selectors/messages"); +const { getScrollSetting } = require("devtools/client/webconsole/new-console-output/selectors/ui"); const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer); const ConsoleOutput = createClass({ displayName: "ConsoleOutput", propTypes: { hudProxyClient: PropTypes.object.isRequired, messages: PropTypes.object.isRequired, messagesUi: PropTypes.object.isRequired, sourceMapService: PropTypes.object, onViewSourceInDebugger: PropTypes.func.isRequired, openNetworkPanel: PropTypes.func.isRequired, openLink: PropTypes.func.isRequired, + autoscroll: PropTypes.bool.isRequired, + }, + + componentDidMount() { + scrollToBottom(this.outputNode); }, - componentWillUpdate() { - let node = ReactDOM.findDOMNode(this); - if (node.lastChild) { - this.shouldScrollBottom = isScrolledToBottom(node.lastChild, node); + componentWillUpdate(nextProps, nextState) { + if (!this.outputNode) { + return; + } + + const outputNode = this.outputNode; + + // Figure out if we are at the bottom. If so, then any new message should be scrolled + // into view. + if (this.props.autoscroll && outputNode.lastChild) { + this.shouldScrollBottom = isScrolledToBottom(outputNode.lastChild, outputNode); } }, componentDidUpdate() { if (this.shouldScrollBottom) { - let node = ReactDOM.findDOMNode(this); - node.scrollTop = node.scrollHeight; + scrollToBottom(this.outputNode); } }, render() { let { dispatch, + autoscroll, hudProxyClient, messages, messagesUi, messagesTableData, sourceMapService, onViewSourceInDebugger, openNetworkPanel, openLink, @@ -64,33 +77,45 @@ const ConsoleOutput = createClass({ message, key: message.id, sourceMapService, onViewSourceInDebugger, openNetworkPanel, openLink, open: messagesUi.includes(message.id), tableData: messagesTableData.get(message.id), + autoscroll, }) ); }); return ( - dom.div({className: "webconsole-output"}, messageNodes) + dom.div({ + className: "webconsole-output", + ref: node => { + this.outputNode = node; + }, + }, messageNodes + ) ); } }); +function scrollToBottom(node) { + node.scrollTop = node.scrollHeight; +} + function isScrolledToBottom(outputNode, scrollNode) { let lastNodeHeight = outputNode.lastChild ? outputNode.lastChild.clientHeight : 0; return scrollNode.scrollTop + scrollNode.clientHeight >= scrollNode.scrollHeight - lastNodeHeight / 2; } function mapStateToProps(state) { return { messages: getAllMessages(state), messagesUi: getAllMessagesUiById(state), messagesTableData: getAllMessagesTableDataById(state), + autoscroll: getScrollSetting(state), }; } module.exports = connect(mapStateToProps)(ConsoleOutput);
--- a/devtools/client/webconsole/new-console-output/components/message-container.js +++ b/devtools/client/webconsole/new-console-output/components/message-container.js @@ -33,56 +33,37 @@ const MessageContainer = createClass({ propTypes: { message: PropTypes.object.isRequired, sourceMapService: PropTypes.object, onViewSourceInDebugger: PropTypes.func.isRequired, openNetworkPanel: PropTypes.func.isRequired, openLink: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, hudProxyClient: PropTypes.object.isRequired, + autoscroll: PropTypes.bool.isRequired, }, getDefaultProps: function () { return { open: false }; }, shouldComponentUpdate(nextProps, nextState) { const repeatChanged = this.props.message.repeat !== nextProps.message.repeat; const openChanged = this.props.open !== nextProps.open; const tableDataChanged = this.props.tableData !== nextProps.tableData; return repeatChanged || openChanged || tableDataChanged; }, render() { - const { - dispatch, - message, - sourceMapService, - onViewSourceInDebugger, - openNetworkPanel, - openLink, - open, - tableData, - hudProxyClient, - } = this.props; + const { message } = this.props; let MessageComponent = createFactory(getMessageComponent(message)); - return MessageComponent({ - dispatch, - message, - sourceMapService, - onViewSourceInDebugger, - openNetworkPanel, - openLink, - open, - tableData, - hudProxyClient, - }); + return MessageComponent(this.props); } }); function getMessageComponent(message) { switch (message.source) { case MESSAGE_SOURCE.CONSOLE_API: return componentMap.get("ConsoleApiCall"); case MESSAGE_SOURCE.NETWORK:
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js +++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js @@ -12,31 +12,33 @@ const { PropTypes } = require("devtools/client/shared/vendor/react"); const Message = createFactory(require("devtools/client/webconsole/new-console-output/components/message")); ConsoleCommand.displayName = "ConsoleCommand"; ConsoleCommand.propTypes = { message: PropTypes.object.isRequired, + autoscroll: PropTypes.bool.isRequired, }; /** * Displays input from the console. */ function ConsoleCommand(props) { const { source, type, level, - messageText: messageBody + messageText: messageBody, } = props.message; const childProps = { source, type, level, - messageBody + messageBody, + scrollToMessage: props.autoscroll, }; return Message(childProps); } module.exports = ConsoleCommand;
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js +++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js @@ -38,13 +38,14 @@ function EvaluationResult(props) { const topLevelClasses = ["cm-s-mozilla"]; const childProps = { source, type, level, topLevelClasses, messageBody, + scrollToMessage: props.autoscroll, }; return Message(childProps); } module.exports = EvaluationResult;
--- a/devtools/client/webconsole/new-console-output/components/message.js +++ b/devtools/client/webconsole/new-console-output/components/message.js @@ -3,123 +3,140 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // React & Redux const { + createClass, createFactory, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react"); const actions = require("devtools/client/webconsole/new-console-output/actions/index"); const CollapseButton = createFactory(require("devtools/client/webconsole/new-console-output/components/collapse-button")); const MessageIcon = createFactory(require("devtools/client/webconsole/new-console-output/components/message-icon")); const MessageRepeat = createFactory(require("devtools/client/webconsole/new-console-output/components/message-repeat")); const FrameView = createFactory(require("devtools/client/shared/components/frame")); const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace")); -Message.displayName = "Message"; +const Message = createClass({ + displayName: "Message", -Message.propTypes = { - open: PropTypes.bool, - source: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - level: PropTypes.string.isRequired, - topLevelClasses: PropTypes.array, - messageBody: PropTypes.any.isRequired, - repeat: PropTypes.any, - frame: PropTypes.any, - attachment: PropTypes.any, - stacktrace: PropTypes.any, - messageId: PropTypes.string, - onViewSourceInDebugger: PropTypes.func, - sourceMapService: PropTypes.any, -}; + propTypes: { + open: PropTypes.bool, + source: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + level: PropTypes.string.isRequired, + topLevelClasses: PropTypes.array, + messageBody: PropTypes.any.isRequired, + repeat: PropTypes.any, + frame: PropTypes.any, + attachment: PropTypes.any, + stacktrace: PropTypes.any, + messageId: PropTypes.string, + scrollToMessage: PropTypes.bool, + onViewSourceInDebugger: PropTypes.func, + sourceMapService: PropTypes.any, + }, -Message.defaultProps = { - topLevelClasses: [], -}; + getDefaultProps() { + return { + topLevelClasses: [], + }; + }, -function Message(props) { - const { - messageId, - open, - source, - type, - level, - topLevelClasses, - messageBody, - frame, - stacktrace, - onViewSourceInDebugger, - sourceMapService, - dispatch, - } = props; + componentDidMount() { + if (this.props.scrollToMessage && this.messageNode) { + this.messageNode.scrollIntoView(); + } + }, - topLevelClasses.push("message", source, type, level); - if (open) { - topLevelClasses.push("open"); - } + render() { + const { + messageId, + open, + source, + type, + level, + topLevelClasses, + messageBody, + frame, + stacktrace, + onViewSourceInDebugger, + sourceMapService, + dispatch, + } = this.props; - const icon = MessageIcon({level}); + topLevelClasses.push("message", source, type, level); + if (open) { + topLevelClasses.push("open"); + } + + const icon = MessageIcon({level}); - // Figure out if there is an expandable part to the message. - let attachment = null; - if (props.attachment) { - attachment = props.attachment; - } else if (stacktrace) { - const child = open ? StackTrace({ - stacktrace: stacktrace, - onViewSourceInDebugger: onViewSourceInDebugger - }) : null; - attachment = dom.div({ className: "stacktrace devtools-monospace" }, child); - } + // Figure out if there is an expandable part to the message. + let attachment = null; + if (this.props.attachment) { + attachment = this.props.attachment; + } else if (stacktrace) { + const child = open ? StackTrace({ + stacktrace: stacktrace, + onViewSourceInDebugger: onViewSourceInDebugger + }) : null; + attachment = dom.div({ className: "stacktrace devtools-monospace" }, child); + } - // If there is an expandable part, make it collapsible. - let collapse = null; - if (attachment) { - collapse = CollapseButton({ - open, - onClick: function () { - if (open) { - dispatch(actions.messageClose(messageId)); - } else { - dispatch(actions.messageOpen(messageId)); - } - }, - }); - } + // If there is an expandable part, make it collapsible. + let collapse = null; + if (attachment) { + collapse = CollapseButton({ + open, + onClick: function () { + if (open) { + dispatch(actions.messageClose(messageId)); + } else { + dispatch(actions.messageOpen(messageId)); + } + }, + }); + } - const repeat = props.repeat ? MessageRepeat({repeat: props.repeat}) : null; + const repeat = this.props.repeat ? MessageRepeat({repeat: this.props.repeat}) : null; - // Configure the location. - const shouldRenderFrame = frame && frame.source !== "debugger eval code"; - const location = dom.span({ className: "message-location devtools-monospace" }, - shouldRenderFrame ? FrameView({ - frame, - onClick: onViewSourceInDebugger, - showEmptyPathAsHost: true, - sourceMapService - }) : null - ); + // Configure the location. + const shouldRenderFrame = frame && frame.source !== "debugger eval code"; + const location = dom.span({ className: "message-location devtools-monospace" }, + shouldRenderFrame ? FrameView({ + frame, + onClick: onViewSourceInDebugger, + showEmptyPathAsHost: true, + sourceMapService + }) : null + ); - return dom.div({ className: topLevelClasses.join(" ") }, - // @TODO add timestamp - // @TODO add indent if necessary - icon, - collapse, - dom.span({ className: "message-body-wrapper" }, - dom.span({ className: "message-flex-body" }, - dom.span({ className: "message-body devtools-monospace" }, - messageBody + return dom.div({ + className: topLevelClasses.join(" "), + ref: node => { + this.messageNode = node; + } + }, + // @TODO add timestamp + // @TODO add indent if necessary + icon, + collapse, + dom.span({ className: "message-body-wrapper" }, + dom.span({ className: "message-flex-body" }, + dom.span({ className: "message-body devtools-monospace" }, + messageBody + ), + repeat, + location ), - repeat, - location - ), - attachment - ) - ); -} + attachment + ) + ); + } +}); module.exports = Message;
--- a/devtools/client/webconsole/new-console-output/reducers/ui.js +++ b/devtools/client/webconsole/new-console-output/reducers/ui.js @@ -1,26 +1,37 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const constants = require("devtools/client/webconsole/new-console-output/constants"); +const { + FILTER_BAR_TOGGLE, + MESSAGE_ADD, +} = require("devtools/client/webconsole/new-console-output/constants"); const Immutable = require("devtools/client/shared/vendor/immutable"); const UiState = Immutable.Record({ filterBarVisible: false, filteredMessageVisible: false, + autoscroll: true, }); function ui(state = new UiState(), action) { + // Autoscroll should be set for all action types. If the last action was not message + // add, then turn it off. This prevents us from scrolling after someone toggles a + // filter, or to the bottom of the attachement when an expandable message at the bottom + // of the list is expanded. It does depend on the MESSAGE_ADD action being the last in + // its batch, though. + state = state.set("autoscroll", action.type == MESSAGE_ADD); + switch (action.type) { - case constants.FILTER_BAR_TOGGLE: + case FILTER_BAR_TOGGLE: return state.set("filterBarVisible", !state.filterBarVisible); } return state; } module.exports = { UiState,
--- a/devtools/client/webconsole/new-console-output/selectors/ui.js +++ b/devtools/client/webconsole/new-console-output/selectors/ui.js @@ -1,12 +1,20 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; function getAllUi(state) { return state.ui; } -exports.getAllUi = getAllUi; +function getScrollSetting(state) { + return getAllUi(state).autoscroll; +} + +module.exports = { + getAllUi, + getScrollSetting, +};