author | tanhengyeow <E0032242@u.nus.edu> |
Thu, 05 Sep 2019 07:59:18 +0000 | |
changeset 491821 | fc2a5d551b3c41009c5eeb8f3598339e39c53118 |
parent 491820 | ecdf8f05db28e90c81bf8358c1dc8042d05efa01 |
child 491822 | d8a4d4ed79a9ba3fd31fd44379d9b53e245ea737 |
push id | 94531 |
push user | jodvarko@mozilla.com |
push date | Thu, 05 Sep 2019 08:02:13 +0000 |
treeherder | autoland@fc2a5d551b3c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Honza |
bugs | 1561873 |
milestone | 71.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/netmonitor/src/actions/web-sockets.js +++ b/devtools/client/netmonitor/src/actions/web-sockets.js @@ -10,16 +10,19 @@ const { WS_OPEN_FRAME_DETAILS, WS_CLEAR_FRAMES, WS_TOGGLE_FRAME_FILTER_TYPE, WS_SET_REQUEST_FILTER_TEXT, WS_TOGGLE_COLUMN, WS_RESET_COLUMNS, } = require("../constants"); +const { getDisplayedFrames } = require("../selectors/index"); +const PAGE_SIZE_ITEM_COUNT_RATIO = 5; + /** * Add frame into state. */ function addFrame(httpChannelId, data, batch) { return { type: WS_ADD_FRAME, httpChannelId, data, @@ -99,18 +102,50 @@ function resetWebSocketsColumns() { */ function toggleWebSocketsColumn(column) { return { type: WS_TOGGLE_COLUMN, column, }; } +/** + * Move the selection up to down according to the "delta" parameter. Possible values: + * - Number: positive or negative, move up or down by specified distance + * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down + * - +Infinity | -Infinity: move to the start or end of the list + */ +function selectFrameDelta(delta) { + return (dispatch, getState) => { + const state = getState(); + const frames = getDisplayedFrames(state); + + if (frames.length === 0) { + return; + } + + const selIndex = frames.findIndex( + r => r === state.webSockets.selectedFrame + ); + + if (delta === "PAGE_DOWN") { + delta = Math.ceil(frames.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } else if (delta === "PAGE_UP") { + delta = -Math.ceil(frames.length / PAGE_SIZE_ITEM_COUNT_RATIO); + } + + const newIndex = Math.min(Math.max(0, selIndex + delta), frames.length - 1); + const newItem = frames[newIndex]; + dispatch(selectFrame(newItem)); + }; +} + module.exports = { addFrame, selectFrame, openFrameDetails, clearFrames, toggleFrameFilterType, setFrameFilterText, resetWebSocketsColumns, toggleWebSocketsColumn, + selectFrameDelta, };
--- a/devtools/client/netmonitor/src/components/websockets/FrameListContent.js +++ b/devtools/client/netmonitor/src/components/websockets/FrameListContent.js @@ -46,23 +46,25 @@ class FrameListContent extends Component return { connector: PropTypes.object.isRequired, startPanelContainer: PropTypes.object, frames: PropTypes.array, selectedFrame: PropTypes.object, selectFrame: PropTypes.func.isRequired, columns: PropTypes.object.isRequired, channelId: PropTypes.number, + onSelectFrameDelta: PropTypes.func.isRequired, }; } constructor(props) { super(props); this.onContextMenu = this.onContextMenu.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); this.framesLimit = Services.prefs.getIntPref( "devtools.netmonitor.ws.displayed-frames.limit" ); this.currentTruncatedNum = 0; this.state = { checked: false, }; this.pinnedToBottom = false; @@ -91,17 +93,47 @@ class FrameListContent extends Component this.initIntersectionObserver = false; } // If a new WebSocket connection is selected, scroll to anchor. if (channelId !== prevProps.channelId && scrollAnchor) { scrollAnchor.scrollIntoView(); } - this.setupScrollToBottom(startPanelContainer, scrollAnchor); + // Do not autoscroll if the selection changed. This would cause + // the newly selected frame to jump just after clicking in. + // (not user friendly) + // + // If the selection changed, we need to ensure that the newly + // selected frame is properly scrolled into the visible area. + if (prevProps.selectedFrame === this.props.selectedFrame) { + this.setupScrollToBottom(startPanelContainer, scrollAnchor); + } else { + const head = document.querySelector("thead.ws-frames-list-headers-group"); + const selectedRow = document.querySelector( + "tr.ws-frame-list-item.selected" + ); + + if (selectedRow) { + const rowRect = selectedRow.getBoundingClientRect(); + const scrollableRect = startPanelContainer.getBoundingClientRect(); + const headRect = head.getBoundingClientRect(); + + if (rowRect.top <= scrollableRect.top) { + selectedRow.scrollIntoView(true); + + // We need to scroll a bit more to get the row out + // of the header. The header is sticky and overlaps + // part of the scrollable area. + startPanelContainer.scrollTop -= headRect.height; + } else if (rowRect.bottom > scrollableRect.bottom) { + selectedRow.scrollIntoView(false); + } + } + } } componentWillUnmount() { // Reset observables and boolean values. const scrollAnchor = this.refs.scrollAnchor; if (this.intersectionObserver) { this.intersectionObserver.unobserve(scrollAnchor); @@ -154,16 +186,53 @@ class FrameListContent extends Component evt.preventDefault(); const { connector } = this.props; this.contextMenu = new FrameListContextMenu({ connector, }); this.contextMenu.open(evt, item); } + /** + * Handler for keyboard events. For arrow up/down, page up/down, home/end, + * move the selection up or down. + */ + onKeyDown(evt) { + evt.preventDefault(); + evt.stopPropagation(); + let delta; + + switch (evt.key) { + case "ArrowUp": + case "ArrowLeft": + delta = -1; + break; + case "ArrowDown": + case "ArrowRight": + delta = +1; + break; + case "PageUp": + delta = "PAGE_UP"; + break; + case "PageDown": + delta = "PAGE_DOWN"; + break; + case "Home": + delta = -Infinity; + break; + case "End": + delta = +Infinity; + break; + } + + if (delta) { + this.props.onSelectFrameDelta(delta); + } + } + render() { const { frames, selectedFrame, connector, columns } = this.props; if (frames.length === 0) { return div( { className: "empty-notice ws-frame-list-empty-notice" }, FRAMES_EMPTY_TEXT ); @@ -195,16 +264,17 @@ class FrameListContent extends Component return div( {}, table( { className: "ws-frames-list-table" }, FrameListHeader(), tbody( { className: "ws-frames-list-body", + onKeyDown: this.onKeyDown, }, tr( { tabIndex: 0, }, td( { className: "truncated-messages-cell", @@ -272,10 +342,11 @@ class FrameListContent extends Component module.exports = connect( state => ({ selectedFrame: getSelectedFrame(state), frames: getDisplayedFrames(state), columns: state.webSockets.columns, }), dispatch => ({ selectFrame: item => dispatch(Actions.selectFrame(item)), + onSelectFrameDelta: delta => dispatch(Actions.selectFrameDelta(delta)), }) )(FrameListContent);