Bug 1555628 - Toolbar for Messages side panel. r=Honza,nchevobbe

Implement Toolbar for WebSocketsPanel.

Differential Revision: https://phabricator.services.mozilla.com/D36455

--HG--
extra : moz-landing-system : lando
This commit is contained in:
tanhengyeow 2019-07-12 07:37:23 +00:00
Родитель 9a0de7396e
Коммит b04946d40e
17 изменённых файлов: 495 добавлений и 86 удалений

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

@ -645,9 +645,9 @@ netmonitor.ws.toolbar.frameType=Type
# in the websocket frame table header, above the "size" column.
netmonitor.ws.toolbar.size=Size
# LOCALIZATION NOTE (netmonitor.ws.toolbar.payload): This is the label displayed
# in the websocket frame table header, above the "payload" column.
netmonitor.ws.toolbar.payload=Payload
# LOCALIZATION NOTE (netmonitor.ws.toolbar.data): This is the label displayed
# in the websocket frame table header, above the "data" column.
netmonitor.ws.toolbar.data=Data
# LOCALIZATION NOTE (netmonitor.ws.toolbar.opCode): This is the label displayed
# in the websocket frame table header, above the "opCode" column.
@ -665,13 +665,49 @@ netmonitor.ws.toolbar.finBit=FinBit
# in the websocket frame table header, above the "time" column.
netmonitor.ws.toolbar.time=Time
# LOCALIZATION NOTE (netmonitor.ws.toolbar.clear): This is the label displayed
# in the websocket toolbar for the "Clear" button.
netmonitor.ws.toolbar.clear=Clear
# LOCALIZATION NOTE (netmonitor.ws.toolbar.filterFreetext.label): This is the label
# displayed in the websocket toolbar for the frames filtering textbox.
netmonitor.ws.toolbar.filterFreetext.label=Filter Messages
# LOCALIZATION NOTE (netmonitor.ws.toolbar.filterFreetext.key): This is the
# shortcut key to focus on the websocket toolbar frames filtering textbox
netmonitor.ws.toolbar.filterFreetext.key=CmdOrCtrl+E
# LOCALIZATION NOTE (netmonitor.ws.context.all): This is the label displayed
# on the context menu that shows "All" WebSocket frames.
netmonitor.ws.context.all=All
# LOCALIZATION NOTE (netmonitor.ws.context.all.accesskey): This is the access key
# for the "All" menu item displayed in the context menu in the websocket toolbar.
netmonitor.ws.context.all.accesskey=A
# LOCALIZATION NOTE (netmonitor.ws.context.sent): This is the label displayed
# on the context menu that shows "Sent" WebSocket frames.
netmonitor.ws.context.sent=Sent
# LOCALIZATION NOTE (netmonitor.ws.context.sent.accesskey): This is the access key
# for the "Sent" menu item displayed in the context menu in the websocket toolbar.
netmonitor.ws.context.sent.accesskey=S
# LOCALIZATION NOTE (netmonitor.ws.context.received): This is the label displayed
# on the context menu that shows "Received" WebSocket frames.
netmonitor.ws.context.received=Received
# LOCALIZATION NOTE (netmonitor.ws.context.received.accesskey): This is the access key
# for the "Received" menu item displayed in the context menu in the websocket toolbar.
netmonitor.ws.context.received.accesskey=R
# LOCALIZATION NOTE (netmonitor.tab.headers): This is the label displayed
# in the network details pane identifying the headers tab.
netmonitor.tab.headers=Headers
# LOCALIZATION NOTE (netmonitor.tab.webSockets): This is the label displayed
# in the network details pane identifying the webSockets tab.
netmonitor.tab.webSockets=WebSockets
# LOCALIZATION NOTE (netmonitor.tab.messages): This is the label displayed
# in the network details pane identifying the messages tab.
netmonitor.tab.messages=Messages
# LOCALIZATION NOTE (netmonitor.tab.cookies): This is the label displayed
# in the network details pane identifying the cookies tab.

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

@ -15,10 +15,11 @@ const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
/**
* Select request with a given id.
*/
function selectRequest(id) {
function selectRequest(id, httpChannelId) {
return {
type: SELECT_REQUEST,
id,
httpChannelId,
};
}

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

@ -8,8 +8,14 @@ const {
WS_ADD_FRAME,
WS_SELECT_FRAME,
WS_OPEN_FRAME_DETAILS,
WS_CLEAR_FRAMES,
WS_TOGGLE_FRAME_FILTER_TYPE,
WS_SET_REQUEST_FILTER_TEXT,
} = require("../constants");
/**
* Add frame into state.
*/
function addFrame(httpChannelId, data) {
return {
type: WS_ADD_FRAME,
@ -41,8 +47,43 @@ function openFrameDetails(open) {
};
}
/**
* Clear all frames from the FrameListContent
* component belonging to the current channelId
*/
function clearFrames() {
return {
type: WS_CLEAR_FRAMES,
};
}
/**
* Show filtered frames from the FrameListContent
* component belonging to the current channelId
*/
function toggleFrameFilterType(filter) {
return {
type: WS_TOGGLE_FRAME_FILTER_TYPE,
filter,
};
}
/**
* Set filter text in toolbar.
*
*/
function setFrameFilterText(text) {
return {
type: WS_SET_REQUEST_FILTER_TEXT,
text,
};
}
module.exports = {
addFrame,
selectFrame,
openFrameDetails,
clearFrames,
toggleFrameFilterType,
setFrameFilterText,
};

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

@ -14,11 +14,6 @@
min-height: var(--primary-toolbar-height);
}
.requests-list-filter-buttons {
white-space: nowrap;
margin: 0 7px;
}
.devtools-button.devtools-pause-icon::before,
.devtools-button.devtools-play-icon::before {
margin-bottom: 1px;

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

@ -232,9 +232,9 @@ class RequestListContent extends Component {
this.tooltip.hide();
}
onMouseDown(evt, id) {
onMouseDown(evt, id, channelId) {
if (evt.button === LEFT_MOUSE_BUTTON) {
this.props.selectRequest(id);
this.props.selectRequest(id, channelId);
} else if (evt.button === RIGHT_MOUSE_BUTTON) {
this.props.onItemRightMouseButtonDown(id);
}
@ -383,7 +383,8 @@ class RequestListContent extends Component {
onContextMenu: this.onContextMenu,
onFocusedNodeChange: this.onFocusedNodeChange,
onDoubleClick: () => this.onDoubleClick(item),
onMouseDown: evt => this.onMouseDown(evt, item.id),
onMouseDown: evt =>
this.onMouseDown(evt, item.id, item.channelId),
onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
onSecurityIconMouseDown: () =>
onSecurityIconMouseDown(item.securityState),
@ -435,7 +436,8 @@ module.exports = connect(
dispatch(Actions.selectDetailsPanelTab("stack-trace"));
}
},
selectRequest: id => dispatch(Actions.selectRequest(id)),
selectRequest: (id, channelId) =>
dispatch(Actions.selectRequest(id, channelId)),
onItemRightMouseButtonDown: id => dispatch(Actions.rightClickRequest(id)),
onItemMouseDown: id => dispatch(Actions.selectRequest(id)),
/**

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

@ -34,7 +34,7 @@ const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
const CACHE_TITLE = L10N.getStr("netmonitor.tab.cache");
const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies");
const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers");
const WEBSOCKETS_TITLE = L10N.getStr("netmonitor.tab.webSockets");
const MESSAGES_TITLE = L10N.getStr("netmonitor.tab.messages");
const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params");
const RESPONSE_TITLE = L10N.getStr("netmonitor.tab.response");
const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
@ -96,7 +96,6 @@ class TabboxPanel extends Component {
return null;
}
const channelId = request.channelId;
const showWebSocketsPanel =
request.cause.type === "websocket" &&
Services.prefs.getBoolPref("devtools.netmonitor.features.webSockets") &&
@ -135,11 +134,10 @@ class TabboxPanel extends Component {
showWebSocketsPanel &&
TabPanel(
{
id: PANELS.WEBSOCKETS,
title: WEBSOCKETS_TITLE,
id: PANELS.MESSAGES,
title: MESSAGES_TITLE,
},
WebSocketsPanel({
channelId,
connector,
})
),

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

@ -0,0 +1,85 @@
/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { L10N } = require("devtools/client/netmonitor/src/utils/l10n.js");
// Menu
loader.lazyRequireGetter(
this,
"showMenu",
"devtools/client/shared/components/menu/utils",
true
);
class FrameFilterMenu extends PureComponent {
static get propTypes() {
return {
frameFilterType: PropTypes.string.isRequired,
toggleFrameFilterType: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.onShowFilterMenu = this.onShowFilterMenu.bind(this);
}
onShowFilterMenu(event) {
const { frameFilterType, toggleFrameFilterType } = this.props;
const menuItems = [
{
id: "ws-frame-list-context-filter-all",
label: L10N.getStr("netmonitor.ws.context.all"),
accesskey: L10N.getStr("netmonitor.ws.context.all.accesskey"),
checked: frameFilterType === "all",
click: () => {
toggleFrameFilterType("all");
},
},
{
id: "ws-frame-list-context-filter-sent",
label: L10N.getStr("netmonitor.ws.context.sent"),
accesskey: L10N.getStr("netmonitor.ws.context.sent.accesskey"),
checked: frameFilterType === "sent",
click: () => {
toggleFrameFilterType("sent");
},
},
{
id: "ws-frame-list-context-filter-received",
label: L10N.getStr("netmonitor.ws.context.received"),
accesskey: L10N.getStr("netmonitor.ws.context.received.accesskey"),
checked: frameFilterType === "received",
click: () => {
toggleFrameFilterType("received");
},
},
];
showMenu(menuItems, { button: event.target });
}
render() {
const { frameFilterType } = this.props;
const title = L10N.getStr(`netmonitor.ws.context.${frameFilterType}`);
return dom.button(
{
id: "frame-filter-menu",
className: "devtools-button devtools-dropdown-button",
title,
onClick: this.onShowFilterMenu,
},
dom.span({ className: "title" }, title)
);
}
}
module.exports = FrameFilterMenu;

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

@ -10,9 +10,9 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { getFramePayload } = require("../../utils/request-utils");
/**
* Renders the "Payload" column of a WebSocket frame.
* Renders the "Data" column of a WebSocket frame.
*/
class FrameListColumnPayload extends Component {
class FrameListColumnData extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
@ -61,4 +61,4 @@ class FrameListColumnPayload extends Component {
}
}
module.exports = FrameListColumnPayload;
module.exports = FrameListColumnData;

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

@ -29,6 +29,7 @@ class FrameListColumnTime extends Component {
// Convert microseconds (DOMHighResTimeStamp) to milliseconds
const time = timeStamp / 1000;
const microseconds = (timeStamp % 1000).toString().padStart(3, "0");
return dom.td(
{
@ -36,7 +37,9 @@ class FrameListColumnTime extends Component {
className: "ws-frames-list-column ws-frames-list-time",
title: timeStamp,
},
new Date(time).toLocaleTimeString()
new Date(time).toLocaleTimeString(undefined, { hour12: false }) +
"." +
microseconds
);
}
}

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

@ -12,7 +12,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const {
connect,
} = require("devtools/client/shared/redux/visibility-handler-connect");
const { getFramesByChannelId } = require("../../selectors/index");
const { getDisplayedFrames } = require("../../selectors/index");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { table, tbody, div } = dom;
@ -38,7 +38,6 @@ const LEFT_MOUSE_BUTTON = 0;
class FrameListContent extends Component {
static get propTypes() {
return {
channelId: PropTypes.number,
connector: PropTypes.object.isRequired,
frames: PropTypes.array,
selectedFrame: PropTypes.object,
@ -59,7 +58,7 @@ class FrameListContent extends Component {
render() {
const { frames, selectedFrame, connector } = this.props;
if (!frames) {
if (frames.length === 0) {
return div(
{ className: "empty-notice ws-frame-list-empty-notice" },
FRAMES_EMPTY_TEXT
@ -89,9 +88,9 @@ class FrameListContent extends Component {
}
module.exports = connect(
(state, props) => ({
state => ({
selectedFrame: getSelectedFrame(state),
frames: getFramesByChannelId(state, props.channelId),
frames: getDisplayedFrames(state),
}),
dispatch => ({
selectFrame: item => dispatch(Actions.selectFrame(item)),

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

@ -18,8 +18,8 @@ loader.lazyGetter(this, "FrameListColumnType", function() {
loader.lazyGetter(this, "FrameListColumnSize", function() {
return createFactory(require("./FrameListColumnSize"));
});
loader.lazyGetter(this, "FrameListColumnPayload", function() {
return createFactory(require("./FrameListColumnPayload"));
loader.lazyGetter(this, "FrameListColumnData", function() {
return createFactory(require("./FrameListColumnData"));
});
loader.lazyGetter(this, "FrameListColumnOpCode", function() {
return createFactory(require("./FrameListColumnOpCode"));
@ -37,7 +37,7 @@ loader.lazyGetter(this, "FrameListColumnTime", function() {
const COLUMN_COMPONENTS = [
{ column: "type", ColumnComponent: FrameListColumnType },
{ column: "size", ColumnComponent: FrameListColumnSize },
{ column: "payload", ColumnComponent: FrameListColumnPayload },
{ column: "data", ColumnComponent: FrameListColumnData },
{ column: "opCode", ColumnComponent: FrameListColumnOpCode },
{ column: "maskBit", ColumnComponent: FrameListColumnMaskBit },
{ column: "finBit", ColumnComponent: FrameListColumnFinBit },

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

@ -0,0 +1,129 @@
/* 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 {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const {
connect,
} = require("devtools/client/shared/redux/visibility-handler-connect");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Actions = require("devtools/client/netmonitor/src/actions/index");
const {
FILTER_SEARCH_DELAY,
} = require("devtools/client/netmonitor/src/constants");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { L10N } = require("devtools/client/netmonitor/src/utils/l10n.js");
const { button, span, div } = dom;
// Components
const FrameFilterMenu = createFactory(require("./FrameFilterMenu"));
const SearchBox = createFactory(
require("devtools/client/shared/components/SearchBox")
);
// Localization
const WS_TOOLBAR_CLEAR = L10N.getStr("netmonitor.ws.toolbar.clear");
const WS_SEARCH_KEY_SHORTCUT = L10N.getStr(
"netmonitor.ws.toolbar.filterFreetext.key"
);
const WS_SEARCH_PLACE_HOLDER = L10N.getStr(
"netmonitor.ws.toolbar.filterFreetext.label"
);
/**
* WebSocketsPanel toolbar component.
*
* Toolbar contains a set of useful tools that clear the list of
* existing frames as well as filter content.
*/
class Toolbar extends Component {
static get propTypes() {
return {
searchboxRef: PropTypes.object.isRequired,
toggleFrameFilterType: PropTypes.func.isRequired,
clearFrames: PropTypes.func.isRequired,
setFrameFilterText: PropTypes.func.isRequired,
frameFilterType: PropTypes.string.isRequired,
};
}
/**
* Render a separator.
*/
renderSeparator() {
return span({ className: "devtools-separator" });
}
/**
* Render a clear button.
*/
renderClearButton(clearFrames) {
return button({
className:
"devtools-button devtools-clear-icon ws-frames-list-clear-button",
title: WS_TOOLBAR_CLEAR,
onClick: () => {
clearFrames();
},
});
}
/**
* Render the frame filter menu button.
*/
renderFrameFilterMenu() {
const { frameFilterType, toggleFrameFilterType } = this.props;
return FrameFilterMenu({
frameFilterType,
toggleFrameFilterType,
});
}
/**
* Render filter Searchbox.
*/
renderFilterBox(setFrameFilterText) {
return SearchBox({
delay: FILTER_SEARCH_DELAY,
keyShortcut: WS_SEARCH_KEY_SHORTCUT,
placeholder: WS_SEARCH_PLACE_HOLDER,
type: "filter",
ref: this.props.searchboxRef,
onChange: setFrameFilterText,
});
}
render() {
const { clearFrames, setFrameFilterText } = this.props;
return div(
{
id: "netmonitor-toolbar-container",
className: "devtools-toolbar devtools-input-toolbar",
},
this.renderClearButton(clearFrames),
this.renderSeparator(),
this.renderFrameFilterMenu(),
this.renderSeparator(),
this.renderFilterBox(setFrameFilterText)
);
}
}
module.exports = connect(
state => ({
frameFilterType: state.webSockets.frameFilterType,
}),
dispatch => ({
clearFrames: () => dispatch(Actions.clearFrames()),
toggleFrameFilterType: filter =>
dispatch(Actions.toggleFrameFilterType(filter)),
setFrameFilterText: text => dispatch(Actions.setFrameFilterText(text)),
})
)(Toolbar);

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

@ -7,8 +7,11 @@
const Services = require("Services");
const {
Component,
createRef,
createFactory,
} = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { div } = dom;
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const {
connect,
@ -25,6 +28,7 @@ const SplitBox = createFactory(
require("devtools/client/shared/components/splitter/SplitBox")
);
const FrameListContent = createFactory(require("./FrameListContent"));
const Toolbar = createFactory(require("./Toolbar"));
loader.lazyGetter(this, "FramePayload", function() {
return createFactory(require("./FramePayload"));
@ -37,33 +41,44 @@ loader.lazyGetter(this, "FramePayload", function() {
class WebSocketsPanel extends Component {
static get propTypes() {
return {
channelId: PropTypes.number,
connector: PropTypes.object.isRequired,
selectedFrame: PropTypes.object,
frameDetailsOpen: PropTypes.bool.isRequired,
openFrameDetailsTab: PropTypes.func.isRequired,
selectedFrameVisible: PropTypes.bool.isRequired,
channelId: PropTypes.number,
};
}
constructor(props) {
super(props);
this.searchboxRef = createRef();
this.clearFilterText = this.clearFilterText.bind(this);
}
componentDidUpdate() {
const { selectedFrameVisible, openFrameDetailsTab } = this.props;
componentDidUpdate(nextProps) {
const { selectedFrameVisible, openFrameDetailsTab, channelId } = this.props;
// If a new WebSocket connection is selected, clear the filter text
if (channelId !== nextProps.channelId) {
this.clearFilterText();
}
if (!selectedFrameVisible) {
openFrameDetailsTab(false);
}
}
// Reset the filter text
clearFilterText() {
if (this.searchboxRef) {
this.searchboxRef.current.onClearButtonClick();
}
}
render() {
const {
frameDetailsOpen,
channelId,
connector,
selectedFrame,
} = this.props;
const { frameDetailsOpen, connector, selectedFrame } = this.props;
const initialWidth = Services.prefs.getIntPref(
"devtools.netmonitor.ws.payload-preview-width"
@ -72,36 +87,39 @@ class WebSocketsPanel extends Component {
"devtools.netmonitor.ws.payload-preview-height"
);
return SplitBox({
className: "devtools-responsive-container",
initialWidth: initialWidth,
initialHeight: initialHeight,
minSize: "50px",
maxSize: "50%",
splitterSize: frameDetailsOpen ? 1 : 0,
startPanel: FrameListContent({ channelId, connector }),
endPanel:
frameDetailsOpen &&
FramePayload({
connector,
selectedFrame,
}),
endPanelCollapsed: !frameDetailsOpen,
endPanelControl: true,
vert: false,
});
return div(
{ className: "monitor-panel" },
Toolbar({
searchboxRef: this.searchboxRef,
}),
SplitBox({
className: "devtools-responsive-container",
initialWidth: initialWidth,
initialHeight: initialHeight,
minSize: "50px",
maxSize: "50%",
splitterSize: frameDetailsOpen ? 1 : 0,
startPanel: FrameListContent({ connector }),
endPanel:
frameDetailsOpen &&
FramePayload({
connector,
selectedFrame,
}),
endPanelCollapsed: !frameDetailsOpen,
endPanelControl: true,
vert: false,
})
);
}
}
module.exports = connect(
(state, props) => ({
selectedFrame: getSelectedFrame(state),
state => ({
channelId: state.webSockets.currentChannelId,
frameDetailsOpen: state.webSockets.frameDetailsOpen,
selectedFrameVisible: isSelectedFrameVisible(
state,
props.channelId,
getSelectedFrame(state)
),
selectedFrame: getSelectedFrame(state),
selectedFrameVisible: isSelectedFrameVisible(state),
}),
dispatch => ({
openFrameDetailsTab: open => dispatch(Actions.openFrameDetails(open)),

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

@ -3,10 +3,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'FrameFilterMenu.js',
'FrameListColumnData.js',
'FrameListColumnFinBit.js',
'FrameListColumnMaskBit.js',
'FrameListColumnOpCode.js',
'FrameListColumnPayload.js',
'FrameListColumnSize.js',
'FrameListColumnTime.js',
'FrameListColumnType.js',
@ -14,5 +15,6 @@ DevToolsModules(
'FrameListHeader.js',
'FrameListItem.js',
'FramePayload.js',
'Toolbar.js',
'WebSocketsPanel.js',
)

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

@ -37,6 +37,9 @@ const actionTypes = {
WS_ADD_FRAME: "WS_ADD_FRAME",
WS_SELECT_FRAME: "WS_SELECT_FRAME",
WS_OPEN_FRAME_DETAILS: "WS_OPEN_FRAME_DETAILS",
WS_CLEAR_FRAMES: "WS_CLEAR_FRAMES",
WS_TOGGLE_FRAME_FILTER_TYPE: "WS_TOGGLE_FRAME_FILTER_TYPE",
WS_SET_REQUEST_FILTER_TEXT: "WS_SET_REQUEST_FILTER_TEXT",
};
// Descriptions for what this frontend is currently doing.
@ -163,7 +166,7 @@ const UPDATE_PROPS = [
const PANELS = {
COOKIES: "cookies",
HEADERS: "headers",
WEBSOCKETS: "webSockets",
MESSAGES: "messages",
PARAMS: "params",
RESPONSE: "response",
CACHE: "cache",
@ -325,7 +328,7 @@ const WS_FRAMES_HEADERS = [
name: "size",
},
{
name: "payload",
name: "data",
},
{
name: "opCode",

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

@ -5,9 +5,13 @@
"use strict";
const {
SELECT_REQUEST,
WS_ADD_FRAME,
WS_SELECT_FRAME,
WS_OPEN_FRAME_DETAILS,
WS_CLEAR_FRAMES,
WS_TOGGLE_FRAME_FILTER_TYPE,
WS_SET_REQUEST_FILTER_TEXT,
} = require("../constants");
/**
@ -18,26 +22,47 @@ function WebSockets() {
return {
// Map with all requests (key = channelId, value = array of frame objects)
frames: new Map(),
frameFilterText: "",
// Default filter type is "all",
frameFilterType: "all",
selectedFrame: null,
frameDetailsOpen: false,
currentChannelId: null,
};
}
// Appending new frame into the map.
/**
* When a network request is selected,
* set the current channelId affiliated with the WebSocket connection.
*/
function setChannelId(state, action) {
return {
...state,
currentChannelId: action.httpChannelId,
// Default filter text is empty string for a new WebSocket connection
frameFilterText: "",
};
}
/**
* Appending new frame into the map.
*/
function addFrame(state, action) {
const { httpChannelId } = action;
const nextState = { ...state };
const newFrame = {
httpChannelId: action.httpChannelId,
httpChannelId,
...action.data,
};
nextState.frames = mapSet(state.frames, newFrame.httpChannelId, newFrame);
nextState.frames = mapSet(nextState.frames, newFrame.httpChannelId, newFrame);
return nextState;
}
// Select specific frame.
/**
* Select specific frame.
*/
function selectFrame(state, action) {
return {
...state,
@ -46,6 +71,9 @@ function selectFrame(state, action) {
};
}
/**
* Shows/Hides the FramePayload component.
*/
function openFrameDetails(state, action) {
return {
...state,
@ -53,6 +81,45 @@ function openFrameDetails(state, action) {
};
}
/**
* Clear WS frames of the request from the state.
*/
function clearFrames(state) {
const nextState = { ...state };
nextState.frames = new Map(nextState.frames);
nextState.frames.delete(nextState.currentChannelId);
return {
...WebSockets(),
// Preserving the Map objects as they might contain state for other channelIds
frames: nextState.frames,
// Preserving the currentChannelId as there would not be another reset of channelId
currentChannelId: nextState.currentChannelId,
frameFilterType: nextState.frameFilterType,
frameFilterText: nextState.frameFilterText,
};
}
/**
* Toggle the frame filter type of the WebSocket connection.
*/
function toggleFrameFilterType(state, action) {
return {
...state,
frameFilterType: action.filter,
};
}
/**
* Set the filter text of the current channelId.
*/
function setFrameFilterText(state, action) {
return {
...state,
frameFilterText: action.text,
};
}
/**
* Append new item into existing map and return new map.
*/
@ -73,12 +140,20 @@ function mapSet(map, key, value) {
*/
function webSockets(state = WebSockets(), action) {
switch (action.type) {
case SELECT_REQUEST:
return setChannelId(state, action);
case WS_ADD_FRAME:
return addFrame(state, action);
case WS_SELECT_FRAME:
return selectFrame(state, action);
case WS_OPEN_FRAME_DETAILS:
return openFrameDetails(state, action);
case WS_CLEAR_FRAMES:
return clearFrames(state);
case WS_TOGGLE_FRAME_FILTER_TYPE:
return toggleFrameFilterType(state, action);
case WS_SET_REQUEST_FILTER_TEXT:
return setFrameFilterText(state, action);
default:
return state;
}

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

@ -6,30 +6,52 @@
const { createSelector } = require("devtools/client/shared/vendor/reselect");
function getFramesByChannelId(state, channelId) {
return state.webSockets.frames.get(channelId);
}
/**
* Returns list of frames that are visible to the user.
* Filtered frames by types and text are factored in.
*/
const getDisplayedFrames = createSelector(
state => state.webSockets,
({ frames, frameFilterType, frameFilterText, currentChannelId }) => {
if (!currentChannelId || !frames.get(currentChannelId)) {
return [];
}
const framesArray = frames.get(currentChannelId);
if (frameFilterType === "all" && frameFilterText.length === 0) {
return framesArray;
}
return framesArray.filter(
frame =>
frame.payload.includes(frameFilterText) &&
(frameFilterType === "all" || frameFilterType === frame.type)
);
}
);
/**
* Checks if the selected frame is visible.
* If the selected frame is not visible, the SplitBox component
* should not show the FramePayload component.
*/
function isSelectedFrameVisible(state, channelId, targetFrame) {
const displayedFrames = getFramesByChannelId(state, channelId);
if (displayedFrames && targetFrame) {
return displayedFrames.some(frame => frame === targetFrame);
}
return false;
}
const isSelectedFrameVisible = createSelector(
state => state.webSockets,
getDisplayedFrames,
({ selectedFrame }, displayedFrames) =>
displayedFrames.some(frame => frame === selectedFrame)
);
/**
* Returns the current selected frame.
*/
const getSelectedFrame = createSelector(
state => state.webSockets,
({ selectedFrame }) => (selectedFrame ? selectedFrame : undefined)
);
module.exports = {
getFramesByChannelId,
getSelectedFrame,
isSelectedFrameVisible,
getDisplayedFrames,
};