Bug 1391688 - Show only messages that fit in the viewport on first console render; r=bgrins.

This allow us to have a faster first meaningful render for the user.
The other messages get rendered after ConsoleOutput mounting.

MozReview-Commit-ID: KIptXsLmTiA

--HG--
extra : rebase_source : da4ff5b1d1027f4c1b83fe6bbf7cf81963e34558
This commit is contained in:
Nicolas Chevobbe 2017-10-24 15:54:00 +02:00
Родитель 7c358c7180
Коммит b4450fc517
6 изменённых файлов: 119 добавлений и 23 удалений

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

@ -11,10 +11,11 @@ const Services = require("Services");
const {
FILTER_BAR_TOGGLE,
INITIALIZE,
PERSIST_TOGGLE,
PREFS,
TIMESTAMPS_TOGGLE,
SELECT_NETWORK_MESSAGE_TAB,
TIMESTAMPS_TOGGLE,
} = require("devtools/client/webconsole/new-console-output/constants");
function filterBarToggle(show) {
@ -51,9 +52,16 @@ function selectNetworkMessageTab(id) {
};
}
function initialize() {
return {
type: INITIALIZE
};
}
module.exports = {
filterBarToggle,
initialize,
persistToggle,
timestampsToggle,
selectNetworkMessageTab,
timestampsToggle,
};

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

@ -10,6 +10,7 @@ const {
PropTypes
} = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const {initialize} = require("devtools/client/webconsole/new-console-output/actions/ui");
const {
getAllMessagesById,
@ -23,10 +24,14 @@ const MessageContainer = createFactory(require("devtools/client/webconsole/new-c
const {
MESSAGE_TYPE,
} = require("devtools/client/webconsole/new-console-output/constants");
const {
getInitialMessageCountForViewport
} = require("devtools/client/webconsole/new-console-output/utils/messages.js");
class ConsoleOutput extends Component {
static get propTypes() {
return {
initialized: PropTypes.bool.isRequired,
messages: PropTypes.object.isRequired,
messagesUi: PropTypes.object.isRequired,
serviceContainer: PropTypes.shape({
@ -60,6 +65,11 @@ class ConsoleOutput extends Component {
if (this.props.onFirstMeaningfulPaint) {
this.props.onFirstMeaningfulPaint();
}
// Dispatching on next tick so we don't block on action execution.
setTimeout(() => {
this.props.dispatch(initialize());
}, 0);
});
}
@ -80,11 +90,18 @@ class ConsoleOutput extends Component {
nextProps.messages.size - this.props.messages.size;
// We need to scroll to the bottom if:
// - we are reacting to the "initialize" action,
// and we are already scrolled to the bottom
// - the number of messages displayed changed
// and we are already scrolled to the bottom
// - the number of messages in the store changed
// and the new message is an evaluation result.
this.shouldScrollBottom =
(
!this.props.initialized &&
nextProps.initialized &&
isScrolledToBottom(lastChild, outputNode)
) ||
(messagesDelta > 0 && nextProps.messages.last().type === MESSAGE_TYPE.RESULT) ||
(visibleMessagesDelta > 0 && isScrolledToBottom(lastChild, outputNode));
}
@ -113,8 +130,17 @@ class ConsoleOutput extends Component {
networkMessageActiveTabId,
serviceContainer,
timestampsVisible,
initialized,
} = this.props;
if (!initialized) {
const numberMessagesFitViewport = getInitialMessageCountForViewport(window);
if (numberMessagesFitViewport < visibleMessages.length) {
visibleMessages = visibleMessages.slice(
visibleMessages.length - numberMessagesFitViewport);
}
}
let messageNodes = visibleMessages.map((messageId) => MessageContainer({
dispatch,
key: messageId,
@ -155,6 +181,7 @@ function isScrolledToBottom(outputNode, scrollNode) {
function mapStateToProps(state, props) {
return {
initialized: state.ui.initialized,
messages: getAllMessagesById(state),
visibleMessages: getVisibleMessages(state),
messagesUi: getAllMessagesUiById(state),

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

@ -7,23 +7,24 @@
const actionTypes = {
BATCH_ACTIONS: "BATCH_ACTIONS",
MESSAGE_ADD: "MESSAGE_ADD",
MESSAGES_ADD: "MESSAGES_ADD",
MESSAGES_CLEAR: "MESSAGES_CLEAR",
MESSAGE_OPEN: "MESSAGE_OPEN",
MESSAGE_CLOSE: "MESSAGE_CLOSE",
NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
FILTER_TOGGLE: "FILTER_TOGGLE",
FILTER_TEXT_SET: "FILTER_TEXT_SET",
FILTERS_CLEAR: "FILTERS_CLEAR",
DEFAULT_FILTERS_RESET: "DEFAULT_FILTERS_RESET",
FILTER_BAR_TOGGLE: "FILTER_BAR_TOGGLE",
SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
FILTER_TEXT_SET: "FILTER_TEXT_SET",
FILTER_TOGGLE: "FILTER_TOGGLE",
FILTERS_CLEAR: "FILTERS_CLEAR",
INITIALIZE: "INITIALIZE",
MESSAGE_ADD: "MESSAGE_ADD",
MESSAGE_CLOSE: "MESSAGE_CLOSE",
MESSAGE_OPEN: "MESSAGE_OPEN",
MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
MESSAGES_ADD: "MESSAGES_ADD",
MESSAGES_CLEAR: "MESSAGES_CLEAR",
NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
PERSIST_TOGGLE: "PERSIST_TOGGLE",
REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
};
const prefs = {

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

@ -7,9 +7,10 @@
const {
FILTER_BAR_TOGGLE,
INITIALIZE,
PERSIST_TOGGLE,
TIMESTAMPS_TOGGLE,
SELECT_NETWORK_MESSAGE_TAB,
TIMESTAMPS_TOGGLE,
} = require("devtools/client/webconsole/new-console-output/constants");
const Immutable = require("devtools/client/shared/vendor/immutable");
@ -19,9 +20,10 @@ const {
const UiState = Immutable.Record({
filterBarVisible: false,
initialized: false,
networkMessageActiveTabId: PANELS.HEADERS,
persistLogs: false,
timestampsVisible: true,
networkMessageActiveTabId: PANELS.HEADERS,
});
function ui(state = new UiState(), action) {
@ -34,6 +36,8 @@ function ui(state = new UiState(), action) {
return state.set("timestampsVisible", action.visible);
case SELECT_NETWORK_MESSAGE_TAB:
return state.set("networkMessageActiveTabId", action.id);
case INITIALIZE:
return state.set("initialized", true);
}
return state;

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

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {
createFactory,
} = require("devtools/client/shared/vendor/react");
// Test utils.
const expect = require("expect");
const { render } = require("enzyme");
const ConsoleOutput = createFactory(require("devtools/client/webconsole/new-console-output/components/ConsoleOutput"));
const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
const {initialize} = require("devtools/client/webconsole/new-console-output/actions/ui");
const {
getInitialMessageCountForViewport
} = require("devtools/client/webconsole/new-console-output/utils/messages.js");
const MESSAGES_NUMBER = 100;
function getDefaultProps(initialized) {
const store = setupStore(
Array.from({length: MESSAGES_NUMBER})
// Alternate message so we don't trigger the repeat mechanism.
.map((_, i) => i % 2 ? "new Date(0)" : "console.log(NaN)")
);
if (initialized) {
store.dispatch(initialize());
}
return {
store,
serviceContainer,
};
}
describe("ConsoleOutput component:", () => {
it("Render only the last messages that fits the viewport when non-initialized", () => {
const rendered = render(ConsoleOutput(getDefaultProps(false)));
const messagesNumber = rendered.find(".message").length;
expect(messagesNumber).toBe(getInitialMessageCountForViewport(window));
});
it("Render every message when initialized", () => {
const rendered = render(ConsoleOutput(getDefaultProps(true)));
expect(rendered.find(".message").length).toBe(MESSAGES_NUMBER);
});
});

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

@ -355,9 +355,16 @@ function isGroupType(type) {
].includes(type);
}
exports.prepareMessage = prepareMessage;
// Export for use in testing.
exports.getRepeatId = getRepeatId;
function getInitialMessageCountForViewport(win) {
const minMessageHeight = 20;
return Math.ceil(win.innerHeight / minMessageHeight);
}
exports.l10n = l10n;
exports.isGroupType = isGroupType;
module.exports = {
getInitialMessageCountForViewport,
isGroupType,
l10n,
prepareMessage,
// Export for use in testing.
getRepeatId,
};